Skip to content

Commit

Permalink
Merge pull request #2 from cpunion/update
Browse files Browse the repository at this point in the history
update docs and demo
  • Loading branch information
cpunion authored Oct 30, 2024
2 parents cbf5642 + 89dfe27 commit 3129ff5
Show file tree
Hide file tree
Showing 4 changed files with 125 additions and 80 deletions.
84 changes: 84 additions & 0 deletions DESIGN.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
# Design

## Python types wrapper design

To automatically DecRef Python objects, we need to wrap them in a Go struct that will call DecRef when it is garbage collected. This is done by embedding a PyObject in a Go struct and registering a finalizer on the Go struct. Below is an example of how this is done:

```go
type pyObject struct {
obj *C.PyObject
}

func newObject(obj *C.PyObject) *pyObject {
o := &pyObject{obj}
runtime.SetFinalizer(o, func(o *pyObject) {
C.Py_DecRef(o.obj)
})
return o
}
```

To wrap generic PyObject(s) to typed Python objects, the best way is using alias types. Below is an example of how this is done:

```go
type Object *pyObject

func (o Object) GetAttrString(name string) Object {
return newObject(o.obj.GetAttrString(name))
}

type Dict Object

func (d Dict) SetItemString(name string, value Object) {
d.obj.SetItemString(name, value.obj)
}
```

Unfortunately, Go does not allow defining methods on alias types like the above.

```shell
invalid receiver type PyObject (pointer or interface type)
invalid receiver type PyDict (pointer or interface type)
```

We can define a new type that embeds the alias type and define methods on the new type. Below is an example of how this is done:

```go
type Object struct {
*pyObject
}

func (o *Object) GetAttrString(name string) *Object {
return &Object{newObject(o.obj.GetAttrString(name))}
}

type Dict struct {
*Object
}

func (d *Dict) SetItemString(name string, value *Object) {
d.obj.SetItemString(name, value.obj)
}
```

But allocating a `PyDict` object will allocate a `PyObject` object and a `pyObject` object. This is not efficient.

We can use a `struct` instead of a `pointer` to avoid this. Below is an example of how this is done:

```go
type Object struct {
*pyObject
}

func (o Object) GetAttrString(name string) Object {
return Object{newObject(o.obj.GetAttrString(name))}
}

type Dict struct {
Object
}

func (d Dict) SetItemString(name string, value Object) {
d.obj.SetItemString(name, value.obj)
}
```
90 changes: 31 additions & 59 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,85 +8,57 @@ Make Go and Python code inter-operable.
- Wrap generic PyObject(s) to typed Python objects.
- Provide a way to define Python objects in Go.

## Python types wrapper design
## Usage

To automatically DecRef Python objects, we need to wrap them in a Go struct that will call DecRef when it is garbage collected. This is done by embedding a PyObject in a Go struct and registering a finalizer on the Go struct. Below is an example of how this is done:
See the [examples](_demo).

```go
type pyObject struct {
obj *C.PyObject
}

func newObject(obj *PyObject) *pyObject {
o := &pyObject{obj}
runtime.SetFinalizer(o, func(o *pyObject) {
o.obj.DecRef()
})
return o
}
```

To wrap generic PyObject(s) to typed Python objects, the best way is using alias types. Below is an example of how this is done:
### Hello World

```go
type Object *pyObject
package main

func (o Object) GetAttrString(name string) Object {
return newObject(o.obj.GetAttrString(name))
}

type Dict Object
import gp "github.com/cpunion/go-python"

func (d Dict) SetItemString(name string, value Object) {
d.obj.SetItemString(name, value.obj)
func main() {
gp.Initialize()
plt := gp.ImportModule("matplotlib.pyplot")
plt.Call("plot", gp.MakeTuple(5, 10), gp.MakeTuple(10, 15), gp.KwArgs{"color": "red"})
plt.Call("show")
}
```

Unfortunately, Go does not allow defining methods on alias types like the above.

```shell
invalid receiver type PyObject (pointer or interface type)
invalid receiver type PyDict (pointer or interface type)
```

We can define a new type that embeds the alias type and define methods on the new type. Below is an example of how this is done:
### Typed Python Objects

```go
type Object struct {
*pyObject
}
package main

func (o *Object) GetAttrString(name string) *Object {
return &Object{newObject(o.obj.GetAttrString(name))}
}
import gp "github.com/cpunion/go-python"

type Dict struct {
*Object
type plt struct {
gp.Module
}

func (d *Dict) SetItemString(name string, value *Object) {
d.obj.SetItemString(name, value.obj)
}
```

But allocating a `PyDict` object will allocate a `PyObject` object and a `pyObject` object. This is not efficient.

We can use a `struct` instead of a `pointer` to avoid this. Below is an example of how this is done:

```go
type Object struct {
*pyObject
func Plt() plt {
return plt{gp.ImportModule("matplotlib.pyplot")}
}

func (o Object) GetAttrString(name string) Object {
return Object{newObject(o.obj.GetAttrString(name))}
func (m plt) Plot(args ...any) gp.Object {
return m.Call("plot", args...)
}

type Dict struct {
Object
func (m plt) Show() {
m.Call("show")
}

func (d Dict) SetItemString(name string, value Object) {
d.obj.SetItemString(name, value.obj)
func main() {
gp.Initialize()
defer gp.Finalize()
plt := Plt()
plt.Plot(gp.MakeTuple(5, 10), gp.MakeTuple(10, 15), gp.KwArgs{"color": "red"})
plt.Show()
}
```

### Define Python Objects

To be written.
1 change: 1 addition & 0 deletions _demo/gradio/gradio.go
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ func main() {
gr = gp.ImportModule("gradio")
fn := gp.FuncOf(UpdateExamples,
"update_examples(country, /)\n--\n\nUpdate examples based on country")
// Would be (in the future):
// fn := gp.FuncOf(UpdateExamples)
demo := gp.With(gr.Call("Blocks"), func(v gp.Object) {
dropdown := gr.Call("Dropdown", gp.KwArgs{
Expand Down
30 changes: 9 additions & 21 deletions _demo/plot/plot.go
Original file line number Diff line number Diff line change
@@ -1,39 +1,27 @@
package main

import (
"github.com/cpunion/go-python"
)
import gp "github.com/cpunion/go-python"

type plt struct {
python.Module
gp.Module
}

func Plt() plt {
return plt{python.ImportModule("matplotlib.pyplot")}
return plt{gp.ImportModule("matplotlib.pyplot")}
}

func (m plt) Plot(args ...any) func(kw any) python.Object {
return m.CallKeywords("plot", args...)
func (m plt) Plot(args ...any) gp.Object {
return m.Call("plot", args...)
}

func (m plt) Show() {
m.Call("show")
}

func plot1() {
plt := python.ImportModule("matplotlib.pyplot")
plt.CallKeywords("plot", python.MakeTuple(5, 10), python.MakeTuple(10, 15))(python.DictFromPairs("color", "red"))
plt.Call("show")
}

func plot2() {
func main() {
gp.Initialize()
defer gp.Finalize()
plt := Plt()
plt.Plot(python.MakeTuple(5, 10), python.MakeTuple(10, 15))(python.DictFromPairs("color", "red"))
plt.Plot(gp.MakeTuple(5, 10), gp.MakeTuple(10, 15), gp.KwArgs{"color": "red"})
plt.Show()
}

func main() {
python.Initialize()
plot1()
plot2()
}

0 comments on commit 3129ff5

Please sign in to comment.