Skip to content

Commit

Permalink
initial
Browse files Browse the repository at this point in the history
  • Loading branch information
cpunion committed Oct 30, 2024
0 parents commit 0f7f2de
Show file tree
Hide file tree
Showing 21 changed files with 1,530 additions and 0 deletions.
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
.tool-versions
*.out
*.pyc
__pycache__
88 changes: 88 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
## Goal

- Provide automatically DecRef for Python objects.
- Wrap generic PyObject(s) to typed Python objects.
- Provide a way to define Python objects in LLGo.

## 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) {
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:

```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)
}
```
84 changes: 84 additions & 0 deletions _demo/gradio/gradio.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
package main

/*
#cgo pkg-config: python-3.12-embed
#include <Python.h>
extern PyObject* UpdateExamples2(PyObject* self, PyObject* args);
*/
import "C"

import (
"os"
"unsafe"

"github.com/cpunion/go-python"
)

/*
import gradio as gr
def update_examples(country):
if country == "USA":
return gr.Dataset(samples=[["Chicago"], ["Little Rock"], ["San Francisco"]])
else:
return gr.Dataset(samples=[["Islamabad"], ["Karachi"], ["Lahore"]])
with gr.Blocks() as demo:
dropdown = gr.Dropdown(label="Country", choices=["USA", "Pakistan"], value="USA")
textbox = gr.Textbox()
examples = gr.Examples([["Chicago"], ["Little Rock"], ["San Francisco"]], textbox)
dropdown.change(update_examples, dropdown, examples.dataset)
demo.launch()
*/

var gr python.Module

func UpdateExamples(country string) python.Object {
println("country:", country)
if country == "USA" {
return gr.CallKeywords("Dataset")(python.MakeDict(map[any]any{
"samples": [][]string{{"Chicago"}, {"Little Rock"}, {"San Francisco"}},
}))
} else {
return gr.CallKeywords("Dataset")(python.MakeDict(map[any]any{
"samples": [][]string{{"Islamabad"}, {"Karachi"}, {"Lahore"}},
}))
}
}

//export UpdateExamples2
func UpdateExamples2(self, args *C.PyObject) *C.PyObject {
argsTuple := python.FromPy((*python.PyObject)(unsafe.Pointer(args))).AsTuple()
country := argsTuple.Get(0).String()
return (*C.PyObject)(unsafe.Pointer(UpdateExamples(country).Obj()))
}

func main() {
if len(os.Args) > 2 {
// avoid gradio start subprocesses
return
}

python.Initialize()
gr = python.ImportModule("gradio")
fn := python.FuncOf("update_examples", UpdateExamples,
"update_examples(country, /)\n--\n\nUpdate examples based on country")
// fn := python.FuncOf1("update_examples", unsafe.Pointer(C.UpdateExamples2),
// "update_examples(country, /)\n--\n\nUpdate examples based on country")
// fn := python.FuncOf(UpdateExamples)
blocks := gr.Call("Blocks")
demo := python.With(blocks, func(v python.Object) {
dropdown := gr.CallKeywords("Dropdown")(python.MakeDict(map[any]any{
"label": "Country",
"choices": []string{"USA", "Pakistan"},
"value": "USA",
}))
textbox := gr.Call("Textbox")
examples := gr.Call("Examples", [][]string{{"Chicago"}, {"Little Rock"}, {"San Francisco"}}, textbox)
dataset := examples.GetAttr("dataset")
dropdown.CallMethod("change", fn, dropdown, dataset)
})
demo.CallMethod("launch")
}
39 changes: 39 additions & 0 deletions _demo/plot/plot.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package main

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

type plt struct {
python.Module
}

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

func (m plt) Plot(args ...any) func(kw any) python.Object {
return m.CallKeywords("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() {
plt := Plt()
plt.Plot(python.MakeTuple(5, 10), python.MakeTuple(10, 15))(python.DictFromPairs("color", "red"))
plt.Show()
}

func main() {
python.Initialize()
plot1()
plot2()
}
33 changes: 33 additions & 0 deletions adap_go.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package python

/*
#cgo pkg-config: python-3.12-embed
#include <Python.h>
*/
import "C"
import (
"unsafe"
)

//go:inline
func AllocCStr(s string) *C.char {
return C.CString(s)
}

func AllocWCStr(s string) *C.wchar_t {
runes := []rune(s)
wchars := make([]uint16, len(runes)+1)
for i, r := range runes {
wchars[i] = uint16(r)
}
wchars[len(runes)] = 0
return (*C.wchar_t)(unsafe.Pointer(&wchars[0]))
}

func GoString(s *C.char) string {
return C.GoString((*C.char)(s))
}

func GoStringN(s *C.char, n int) string {
return C.GoStringN((*C.char)(s), C.int(n))
}
43 changes: 43 additions & 0 deletions bool.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
package python

/*
#cgo pkg-config: python-3.12-embed
#include <Python.h>
*/
import "C"

type Bool struct {
Object
}

func newBool(obj *C.PyObject) Bool {
return Bool{newObject(obj)}
}

func MakeBool(b bool) Bool {
if b {
return True()
}
return False()
}

var trueObj Object
var falseObj Object

func True() Bool {
if trueObj.Nil() {
trueObj = MainModule().Dict().GetString("True")
}
return trueObj.AsBool()
}

func False() Bool {
if falseObj.Nil() {
falseObj = MainModule().Dict().GetString("False")
}
return falseObj.AsBool()
}

func (b Bool) Bool() bool {
return C.PyObject_IsTrue(b.obj) != 0
}
Loading

0 comments on commit 0f7f2de

Please sign in to comment.