├── .gitignore
├── .pydevproject
├── LICENSE
├── README.md
├── common.go
├── concurrency.go
├── examples
└── hello-world
│ ├── api
│ ├── functions.go
│ ├── go_.go
│ ├── module.go
│ └── py_.go
│ ├── foo.py
│ └── main.go
├── exception.go
├── general.go
├── go.mod
├── go.sum
├── import.go
├── module.go
├── object.go
├── path.go
├── primitive.go
├── reference.go
├── scripts
├── _env
├── _functions
├── _trap
├── example
├── format
└── refresh-mod
└── type.go
/.gitignore:
--------------------------------------------------------------------------------
1 | .project
2 | __pycache__
3 |
4 | dist/
5 |
--------------------------------------------------------------------------------
/.pydevproject:
--------------------------------------------------------------------------------
1 |
2 |
3 | Default
4 | python interpreter
5 |
6 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Apache License
2 | Version 2.0, January 2004
3 | http://www.apache.org/licenses/
4 |
5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6 |
7 | 1. Definitions.
8 |
9 | "License" shall mean the terms and conditions for use, reproduction,
10 | and distribution as defined by Sections 1 through 9 of this document.
11 |
12 | "Licensor" shall mean the copyright owner or entity authorized by
13 | the copyright owner that is granting the License.
14 |
15 | "Legal Entity" shall mean the union of the acting entity and all
16 | other entities that control, are controlled by, or are under common
17 | control with that entity. For the purposes of this definition,
18 | "control" means (i) the power, direct or indirect, to cause the
19 | direction or management of such entity, whether by contract or
20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the
21 | outstanding shares, or (iii) beneficial ownership of such entity.
22 |
23 | "You" (or "Your") shall mean an individual or Legal Entity
24 | exercising permissions granted by this License.
25 |
26 | "Source" form shall mean the preferred form for making modifications,
27 | including but not limited to software source code, documentation
28 | source, and configuration files.
29 |
30 | "Object" form shall mean any form resulting from mechanical
31 | transformation or translation of a Source form, including but
32 | not limited to compiled object code, generated documentation,
33 | and conversions to other media types.
34 |
35 | "Work" shall mean the work of authorship, whether in Source or
36 | Object form, made available under the License, as indicated by a
37 | copyright notice that is included in or attached to the work
38 | (an example is provided in the Appendix below).
39 |
40 | "Derivative Works" shall mean any work, whether in Source or Object
41 | form, that is based on (or derived from) the Work and for which the
42 | editorial revisions, annotations, elaborations, or other modifications
43 | represent, as a whole, an original work of authorship. For the purposes
44 | of this License, Derivative Works shall not include works that remain
45 | separable from, or merely link (or bind by name) to the interfaces of,
46 | the Work and Derivative Works thereof.
47 |
48 | "Contribution" shall mean any work of authorship, including
49 | the original version of the Work and any modifications or additions
50 | to that Work or Derivative Works thereof, that is intentionally
51 | submitted to Licensor for inclusion in the Work by the copyright owner
52 | or by an individual or Legal Entity authorized to submit on behalf of
53 | the copyright owner. For the purposes of this definition, "submitted"
54 | means any form of electronic, verbal, or written communication sent
55 | to the Licensor or its representatives, including but not limited to
56 | communication on electronic mailing lists, source code control systems,
57 | and issue tracking systems that are managed by, or on behalf of, the
58 | Licensor for the purpose of discussing and improving the Work, but
59 | excluding communication that is conspicuously marked or otherwise
60 | designated in writing by the copyright owner as "Not a Contribution."
61 |
62 | "Contributor" shall mean Licensor and any individual or Legal Entity
63 | on behalf of whom a Contribution has been received by Licensor and
64 | subsequently incorporated within the Work.
65 |
66 | 2. Grant of Copyright License. Subject to the terms and conditions of
67 | this License, each Contributor hereby grants to You a perpetual,
68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
69 | copyright license to reproduce, prepare Derivative Works of,
70 | publicly display, publicly perform, sublicense, and distribute the
71 | Work and such Derivative Works in Source or Object form.
72 |
73 | 3. Grant of Patent License. Subject to the terms and conditions of
74 | this License, each Contributor hereby grants to You a perpetual,
75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
76 | (except as stated in this section) patent license to make, have made,
77 | use, offer to sell, sell, import, and otherwise transfer the Work,
78 | where such license applies only to those patent claims licensable
79 | by such Contributor that are necessarily infringed by their
80 | Contribution(s) alone or by combination of their Contribution(s)
81 | with the Work to which such Contribution(s) was submitted. If You
82 | institute patent litigation against any entity (including a
83 | cross-claim or counterclaim in a lawsuit) alleging that the Work
84 | or a Contribution incorporated within the Work constitutes direct
85 | or contributory patent infringement, then any patent licenses
86 | granted to You under this License for that Work shall terminate
87 | as of the date such litigation is filed.
88 |
89 | 4. Redistribution. You may reproduce and distribute copies of the
90 | Work or Derivative Works thereof in any medium, with or without
91 | modifications, and in Source or Object form, provided that You
92 | meet the following conditions:
93 |
94 | (a) You must give any other recipients of the Work or
95 | Derivative Works a copy of this License; and
96 |
97 | (b) You must cause any modified files to carry prominent notices
98 | stating that You changed the files; and
99 |
100 | (c) You must retain, in the Source form of any Derivative Works
101 | that You distribute, all copyright, patent, trademark, and
102 | attribution notices from the Source form of the Work,
103 | excluding those notices that do not pertain to any part of
104 | the Derivative Works; and
105 |
106 | (d) If the Work includes a "NOTICE" text file as part of its
107 | distribution, then any Derivative Works that You distribute must
108 | include a readable copy of the attribution notices contained
109 | within such NOTICE file, excluding those notices that do not
110 | pertain to any part of the Derivative Works, in at least one
111 | of the following places: within a NOTICE text file distributed
112 | as part of the Derivative Works; within the Source form or
113 | documentation, if provided along with the Derivative Works; or,
114 | within a display generated by the Derivative Works, if and
115 | wherever such third-party notices normally appear. The contents
116 | of the NOTICE file are for informational purposes only and
117 | do not modify the License. You may add Your own attribution
118 | notices within Derivative Works that You distribute, alongside
119 | or as an addendum to the NOTICE text from the Work, provided
120 | that such additional attribution notices cannot be construed
121 | as modifying the License.
122 |
123 | You may add Your own copyright statement to Your modifications and
124 | may provide additional or different license terms and conditions
125 | for use, reproduction, or distribution of Your modifications, or
126 | for any such Derivative Works as a whole, provided Your use,
127 | reproduction, and distribution of the Work otherwise complies with
128 | the conditions stated in this License.
129 |
130 | 5. Submission of Contributions. Unless You explicitly state otherwise,
131 | any Contribution intentionally submitted for inclusion in the Work
132 | by You to the Licensor shall be under the terms and conditions of
133 | this License, without any additional terms or conditions.
134 | Notwithstanding the above, nothing herein shall supersede or modify
135 | the terms of any separate license agreement you may have executed
136 | with Licensor regarding such Contributions.
137 |
138 | 6. Trademarks. This License does not grant permission to use the trade
139 | names, trademarks, service marks, or product names of the Licensor,
140 | except as required for reasonable and customary use in describing the
141 | origin of the Work and reproducing the content of the NOTICE file.
142 |
143 | 7. Disclaimer of Warranty. Unless required by applicable law or
144 | agreed to in writing, Licensor provides the Work (and each
145 | Contributor provides its Contributions) on an "AS IS" BASIS,
146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
147 | implied, including, without limitation, any warranties or conditions
148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
149 | PARTICULAR PURPOSE. You are solely responsible for determining the
150 | appropriateness of using or redistributing the Work and assume any
151 | risks associated with Your exercise of permissions under this License.
152 |
153 | 8. Limitation of Liability. In no event and under no legal theory,
154 | whether in tort (including negligence), contract, or otherwise,
155 | unless required by applicable law (such as deliberate and grossly
156 | negligent acts) or agreed to in writing, shall any Contributor be
157 | liable to You for damages, including any direct, indirect, special,
158 | incidental, or consequential damages of any character arising as a
159 | result of this License or out of the use or inability to use the
160 | Work (including but not limited to damages for loss of goodwill,
161 | work stoppage, computer failure or malfunction, or any and all
162 | other commercial damages or losses), even if such Contributor
163 | has been advised of the possibility of such damages.
164 |
165 | 9. Accepting Warranty or Additional Liability. While redistributing
166 | the Work or Derivative Works thereof, You may choose to offer,
167 | and charge a fee for, acceptance of support, warranty, indemnity,
168 | or other liability obligations and/or rights consistent with this
169 | License. However, in accepting such obligations, You may act only
170 | on Your own behalf and on Your sole responsibility, not on behalf
171 | of any other Contributor, and only if You agree to indemnify,
172 | defend, and hold each Contributor harmless for any liability
173 | incurred by, or claims asserted against, such Contributor by reason
174 | of your accepting any such warranty or additional liability.
175 |
176 | END OF TERMS AND CONDITIONS
177 |
178 | APPENDIX: How to apply the Apache License to your work.
179 |
180 | To apply the Apache License to your work, attach the following
181 | boilerplate notice, with the fields enclosed by brackets "{}"
182 | replaced with your own identifying information. (Don't include
183 | the brackets!) The text should be enclosed in the appropriate
184 | comment syntax for the file format. We also recommend that a
185 | file or class name and description of purpose be included on the
186 | same "printed page" as the copyright notice for easier
187 | identification within third-party archives.
188 |
189 | Copyright {yyyy} {name of copyright owner}
190 |
191 | Licensed under the Apache License, Version 2.0 (the "License");
192 | you may not use this file except in compliance with the License.
193 | You may obtain a copy of the License at
194 |
195 | http://www.apache.org/licenses/LICENSE-2.0
196 |
197 | Unless required by applicable law or agreed to in writing, software
198 | distributed under the License is distributed on an "AS IS" BASIS,
199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
200 | See the License for the specific language governing permissions and
201 | limitations under the License.
202 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | py4go
2 | =====
3 |
4 | [](https://opensource.org/licenses/Apache-2.0)
5 | [](https://goreportcard.com/report/github.com/tliron/py4go)
6 |
7 | Call Python 3 functions and methods from within your Go program while exposing Go functions and
8 | methods to Python.
9 |
10 | This is *not* an implementation of Python in Go. Rather, py4go works by embedding the CPython
11 | runtime into your Go program using [cgo](https://github.com/golang/go/wiki/cgo) functionality.
12 |
13 | The expected use cases are not low-latency integration, but rather *tight* bidirectional
14 | integration. You can combine the full Go ecosystem with the full Python ecosystem.
15 |
16 | Though you can achieve some integration by using Go's [exec](https://pkg.go.dev/os/exec)
17 | package to run `python`, with py4go you get fine-grained access to individual functions, objects,
18 | methods, and variables.
19 |
20 | To get started try running [`scripts/example`](scripts/example/). Note that you need the Python
21 | development libraries installed. E.g. in Fedora:
22 |
23 | sudo dnf install python3-devel
24 |
25 |
26 | Example Usage
27 | -------------
28 |
29 | ```go
30 | package main
31 |
32 | import (
33 | "fmt"
34 | "github.com/tliron/py4go"
35 | )
36 |
37 | func main() {
38 | // Initialize Python
39 | python.Initialize()
40 | defer python.Finalize()
41 |
42 | // Import Python code (foo.py)
43 | foo, _ := python.Import("foo")
44 | defer foo.Release()
45 |
46 | // Get access to a Python function
47 | hello, _ := foo.GetAttr("hello")
48 | defer hello.Release()
49 |
50 | // Call the function with arguments
51 | r, _ := hello.Call("myargument")
52 | defer r.Release()
53 | fmt.Printf("Returned: %s\n", r.String())
54 |
55 | // Expose a Go function to Python via a C wrapper
56 | // (Just use "import api" from Python)
57 | api, _ := python.CreateModule("api")
58 | defer api.Release()
59 | api.AddModuleCFunctionNoArgs("my_function", C.api_my_function)
60 | api.EnableModule()
61 | }
62 | ```
63 |
64 | Calling Python code from Go is relatively straightforward because Python is a dynamic language and
65 | CPython is an interpreted runtime. Exposing Go code to Python is more involved as it requires writing
66 | wrapper functions in C, which we omitted in the example above. See the [examples](examples/) directory
67 | for more detail.
68 |
69 | In the future we are hoping to make this easier, perhaps via a C code generator based on static
70 | analysis of function signatures and types.
71 |
72 |
73 | Caveats
74 | -------
75 |
76 | There are several issues to be aware of:
77 |
78 | * It's more difficult to distribute your Go program because you *must* have the CPython library
79 | available on the target operating system with a specific name. Because different operating systems
80 | have their own conventions for naming this library, to create a truly portable distribution it may
81 | be best to distribute your program as a packaged container, e.g. using Flatpak, Docker, or Snap.
82 | * It is similarly more difficult to *build* your Go program. We are using `pkg-config: python3-embed` to
83 | locate the CPython SDK, which works on Fedora-based operating systems. But, because where you
84 | *build* will determine the requirements for where you will *run*, it may be best to build on
85 | Fedora, either directly or in a virtual machine or container. Unfortunately cgo does not let us
86 | parameterize that `pkg-config` directive, thus you will have to modify our source files in order to
87 | build on/for other operating systems.
88 | * Calling functions and passing data between these two high-level language's runtime environments
89 | obviously incurs some overhead. Notably strings are sometimes copied multiple times internally,
90 | and may be encoded and decoded (Go normally uses UTF-8, Python defaults to UCS4). If you are
91 | frequently calling back and forth be aware of possible performance degradation. As always, if you
92 | experience a problem measure first and identify the bottleneck before prematurely optimizing!
93 | * Similarly, be aware that you are simultaneously running two memory management runtimes, each with
94 | its own heap allocation and garbage collection threads, and that Go is unaware of Python's. Your
95 | Go code will thus need to explicitly call `Release` on all Python references to ensure that they are
96 | garbage collected. Luckily, the `defer` keyword makes this easy enough in many circumstances.
97 | * Concurrency is a bit tricky in Python due to its infamous Global Interpreter Lock (GIL). If
98 | you are calling Python code from a Goroutine make sure to call `python.SaveThreadState` and
99 | `python.EnsureGilState` as appropriate. See the examples for more detail.
100 |
101 |
102 | References
103 | ----------
104 |
105 | * [go-python](https://github.com/sbinet/go-python) is a similar and more mature project for Python
106 | 2.
107 | * [goPy](https://github.com/qur/gopy) is a much older project for Python 2.
108 | * [gopy](https://github.com/go-python/gopy) generates Python wrappers for Go functions.
109 | * [setuptools-golang](https://github.com/asottile/setuptools-golang) allows you to include Go
110 | libraries in Python packages.
111 |
--------------------------------------------------------------------------------
/common.go:
--------------------------------------------------------------------------------
1 | package python
2 |
3 | // See:
4 | // https://docs.python.org/3/c-api/
5 | // https://github.com/golang/go/wiki/cgo
6 | // https://www.datadoghq.com/blog/engineering/cgo-and-python/
7 | // https://github.com/sbinet/go-python
8 |
9 | // #cgo pkg-config: python3-embed
10 | // #cgo LDFLAGS: -lpython3
11 | import "C"
12 |
--------------------------------------------------------------------------------
/concurrency.go:
--------------------------------------------------------------------------------
1 | package python
2 |
3 | // See:
4 | // https://docs.python.org/3/c-api/init.html
5 |
6 | /*
7 | #define PY_SSIZE_T_CLEAN
8 | #include
9 | */
10 | import "C"
11 |
12 | //
13 | // ThreadState
14 | //
15 |
16 | type ThreadState struct {
17 | State *C.PyThreadState
18 | }
19 |
20 | func SaveThreadState() *ThreadState {
21 | return &ThreadState{C.PyEval_SaveThread()}
22 | }
23 |
24 | func (self *ThreadState) Restore() {
25 | C.PyEval_RestoreThread(self.State)
26 | }
27 |
28 | //
29 | // GilState
30 | //
31 |
32 | type GilState struct {
33 | State C.PyGILState_STATE
34 | }
35 |
36 | func EnsureGilState() *GilState {
37 | return &GilState{C.PyGILState_Ensure()}
38 | }
39 |
40 | func (self *GilState) Release() {
41 | C.PyGILState_Release(self.State)
42 | }
43 |
--------------------------------------------------------------------------------
/examples/hello-world/api/functions.go:
--------------------------------------------------------------------------------
1 | package api
2 |
3 | // Here we define our functions in plain Go
4 |
5 | import (
6 | "fmt"
7 | )
8 |
9 | func sayGoodbye() {
10 | fmt.Println("Go >> Goodbye from Go!")
11 | }
12 |
13 | func concat(a string, b string) string {
14 | fmt.Printf("Go >> Concatenating %q and %q\n", a, b)
15 | return a + " " + b
16 | }
17 |
--------------------------------------------------------------------------------
/examples/hello-world/api/go_.go:
--------------------------------------------------------------------------------
1 | package api
2 |
3 | // Here we export cgo wrappers for our plain Go functions
4 | // They handle the conversion between Go and C types
5 |
6 | // Note: cgo exports cannot be in the same file as cgo preamble funtions,
7 | // which is why this file cannot be combined with "py_.go"
8 |
9 | import "C"
10 |
11 | //export go_api_sayGoodbye
12 | func go_api_sayGoodbye() {
13 | // Note that we could have just exported sayGoodbye directly because it has no arguments,
14 | // and thus nothing to convert, But for completion we are adding this straightforward
15 | // wrapper
16 | sayGoodbye()
17 | }
18 |
19 | //export go_api_concat
20 | func go_api_concat(a *C.char, b *C.char) *C.char {
21 | return C.CString(concat(C.GoString(a), C.GoString(b)))
22 | }
23 |
--------------------------------------------------------------------------------
/examples/hello-world/api/module.go:
--------------------------------------------------------------------------------
1 | package api
2 |
3 | // Here we add our "py_" C functions to the module
4 |
5 | // Note that this file could have been combined with "py_.go", but we preferred a clean separation
6 | // The cost is that we must forward-declare the "py_" functions in the cgo preamble here
7 |
8 | import (
9 | python "github.com/tliron/py4go"
10 | )
11 |
12 | /*
13 | #cgo pkg-config: python3-embed
14 |
15 | #define PY_SSIZE_T_CLEAN
16 | #include
17 |
18 | PyObject *py_api_sayGoodbye(PyObject *self, PyObject *unused);
19 | PyObject *py_api_concat(PyObject *self, PyObject *args);
20 | PyObject *py_api_concat_fast(PyObject *self, PyObject **args, Py_ssize_t nargs);
21 | */
22 | import "C"
23 |
24 | func CreateModule() (*python.Reference, error) {
25 | if module, err := python.CreateModule("api"); err == nil {
26 | if err := module.AddModuleCFunctionNoArgs("say_goodbye", C.py_api_sayGoodbye); err != nil {
27 | module.Release()
28 | return nil, err
29 | }
30 |
31 | if err := module.AddModuleCFunctionArgs("concat", C.py_api_concat); err != nil {
32 | module.Release()
33 | return nil, err
34 | }
35 |
36 | if err := module.AddModuleCFunctionFastArgs("concat_fast", C.py_api_concat_fast); err != nil {
37 | module.Release()
38 | return nil, err
39 | }
40 |
41 | return module, nil
42 | } else {
43 | return nil, err
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/examples/hello-world/api/py_.go:
--------------------------------------------------------------------------------
1 | package api
2 |
3 | // Here we define Python wrappers in C for our "go_" functions
4 | // They handle the conversion between Python and C types
5 |
6 | // We are demonstrating the two supported Python ABIs:
7 | // 1) the more common PyCFunction ABI, which passes arguments as Python tuples and dicts, and
8 | // 2) the newer _PyCFunctionFast ABI, which passes arguments as a more efficient stack
9 |
10 | // Unfortunately we must use C and not Go to write these functions because:
11 | // 1) the "PyArg_Parse_" functions all use variadic arguments, which are not supported by cgo, and
12 | // 2) the "PyArg_Parse_" functions unpack arguments to pointers, which we cannot implement in Go
13 |
14 | // Note: cgo exports cannot be in the same file as cgo preamble functions,
15 | // which is why this file cannot be combined with "go_.go"
16 | // and is also why must forward-declare the "go_" functions in the cgo preamble here
17 |
18 | // See:
19 | // https://docs.python.org/3/c-api/arg.html
20 |
21 | /*
22 | #cgo pkg-config: python3-embed
23 |
24 | #define PY_SSIZE_T_CLEAN
25 | #include
26 |
27 | void go_api_sayGoodbye();
28 | char *go_api_concat(char*, char*);
29 |
30 | // PyCFunction signature
31 | PyObject *py_api_sayGoodbye(PyObject *self, PyObject *unused) {
32 | go_api_sayGoodbye();
33 | return Py_None;
34 | }
35 |
36 | // PyCFunction signature
37 | PyObject *py_api_concat(PyObject *self, PyObject *args) {
38 | char *arg1 = NULL, *arg2 = NULL;
39 | PyArg_ParseTuple(args, "ss", &arg1, &arg2);
40 | char *r = go_api_concat(arg1, arg2);
41 | return PyUnicode_FromString(r);
42 | }
43 |
44 | // _PyCFunctionFast signature
45 | PyObject *py_api_concat_fast(PyObject *self, PyObject **args, Py_ssize_t nargs) {
46 | char *arg1 = NULL, *arg2 = NULL;
47 | _PyArg_ParseStack(args, nargs, "ss", &arg1, &arg2);
48 | char *r = go_api_concat(arg1, arg2);
49 | return PyUnicode_FromString(r);
50 | }
51 | */
52 | import "C"
53 |
--------------------------------------------------------------------------------
/examples/hello-world/foo.py:
--------------------------------------------------------------------------------
1 | import api
2 |
3 | def hello(name):
4 | print("Python >> Hello, " + name)
5 | return "You are " + name
6 |
7 | def goodbye():
8 | print("Python >> Calling Go functions")
9 | api.say_goodbye()
10 |
11 | def bad():
12 | raise Exception("this is a Python exception")
13 |
14 | def say_name():
15 | print("Python >> The name is " + api.concat("Tal", "Liron"))
16 |
17 | def say_name_fast():
18 | print("Python >> The name is " + api.concat_fast("Tal", "Liron"))
19 |
20 | size = 0
21 |
22 | def grow(count):
23 | global size
24 | size += count
25 |
26 | class Person:
27 | def __init__(self, name):
28 | self.name = name
29 |
30 | def greet(self):
31 | print("Python >> Greetings, " + self.name)
32 |
33 | person = Person("Linus")
34 |
--------------------------------------------------------------------------------
/examples/hello-world/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "fmt"
5 | "sync"
6 |
7 | python "github.com/tliron/py4go"
8 | "github.com/tliron/py4go/examples/hello-world/api"
9 | )
10 |
11 | func version() {
12 | fmt.Printf("Go >> Python version:\n%s\n", python.Version())
13 | }
14 |
15 | func typeChecking() {
16 | fmt.Println("Go >> Type checking:")
17 | float, _ := python.NewPrimitiveReference(1.0)
18 | fmt.Printf("Go >> IsFloat: %t\n", float.IsFloat())
19 | }
20 |
21 | func callPythonFunction(module *python.Reference) {
22 | fmt.Println("Go >> Calling a Python function:")
23 |
24 | hello, _ := module.GetAttr("hello")
25 | defer hello.Release()
26 |
27 | r, _ := hello.Call("Tal")
28 | defer r.Release()
29 |
30 | r_, _ := r.ToString()
31 | fmt.Printf("Go >> Python function returned: %s\n", r_)
32 | }
33 |
34 | func callPythonMethod(module *python.Reference) {
35 | fmt.Println("Go >> Calling a Python method:")
36 |
37 | person, _ := module.GetAttr("person")
38 | defer person.Release()
39 |
40 | greet, _ := person.GetAttr("greet")
41 | defer greet.Release()
42 |
43 | greet.Call()
44 | }
45 |
46 | func getPythonException(module *python.Reference) {
47 | fmt.Println("Go >> Python exception as Go error:")
48 |
49 | bad, _ := module.GetAttr("bad")
50 | defer bad.Release()
51 |
52 | if _, err := bad.Call(); err != nil {
53 | fmt.Printf("Go >> Error message: %s\n", err)
54 | }
55 | }
56 |
57 | func callGoFromPython(module *python.Reference) {
58 | goodbye, _ := module.GetAttr("goodbye")
59 | defer goodbye.Release()
60 | goodbye.Call()
61 |
62 | sayName, _ := module.GetAttr("say_name")
63 | defer sayName.Release()
64 | sayName.Call()
65 |
66 | sayNameFast, _ := module.GetAttr("say_name_fast")
67 | defer sayNameFast.Release()
68 | sayNameFast.Call()
69 | }
70 |
71 | func concurrency(module *python.Reference) {
72 | fmt.Println("Go >> Concurrency:")
73 |
74 | grow, _ := module.GetAttr("grow")
75 | defer grow.Release()
76 |
77 | func() {
78 | // Release Python's lock on our main thread, allowing other threads to execute
79 | // (Without this our calls to "grow", from other threads, will block forever)
80 | threadState := python.SaveThreadState()
81 | defer threadState.Restore()
82 |
83 | // Parallel work:
84 |
85 | var waitGroup sync.WaitGroup
86 | defer waitGroup.Wait()
87 |
88 | for i := 0; i < 5; i++ {
89 | waitGroup.Add(1)
90 |
91 | go func() {
92 | defer waitGroup.Done()
93 |
94 | for i := 0; i < 100; i++ {
95 | // We must manually acquire Python's Global Interpreter Lock (GIL),
96 | // because Python doesn't know about our Go "threads" (goroutines)
97 | gs := python.EnsureGilState()
98 | defer gs.Release()
99 |
100 | // (Note: We could also have acquired the GIL outside of this for-loop;
101 | // it's up to how we want to balance concurrency with the cost of context
102 | // switching)
103 |
104 | grow.Call(1)
105 | }
106 | }()
107 | }
108 | }()
109 |
110 | size, _ := module.GetAttr("size")
111 | defer size.Release()
112 |
113 | size_, _ := size.ToInt64()
114 | fmt.Printf("Go >> Size is %d\n", size_)
115 | }
116 |
117 | func main() {
118 | python.PrependPythonPath(".")
119 |
120 | python.Initialize()
121 | defer python.Finalize()
122 |
123 | version()
124 | fmt.Println()
125 |
126 | typeChecking()
127 | fmt.Println()
128 |
129 | api, _ := api.CreateModule()
130 | defer api.Release()
131 | api.EnableModule()
132 |
133 | foo, _ := python.Import("foo")
134 | defer foo.Release()
135 |
136 | callPythonFunction(foo)
137 | fmt.Println()
138 |
139 | callPythonMethod(foo)
140 | fmt.Println()
141 |
142 | getPythonException(foo)
143 | fmt.Println()
144 |
145 | callGoFromPython(foo)
146 | fmt.Println()
147 |
148 | concurrency(foo)
149 | }
150 |
--------------------------------------------------------------------------------
/exception.go:
--------------------------------------------------------------------------------
1 | package python
2 |
3 | // See:
4 | // https://docs.python.org/3/c-api/exceptions.html
5 |
6 | import (
7 | "errors"
8 | )
9 |
10 | /*
11 | #define PY_SSIZE_T_CLEAN
12 | #include
13 | */
14 | import "C"
15 |
16 | func HasException() bool {
17 | return C.PyErr_Occurred() != nil
18 | }
19 |
20 | func GetError() error {
21 | if exception := FetchException(); exception != nil {
22 | return exception
23 | } else {
24 | return errors.New("Python error without an exception")
25 | }
26 | }
27 |
28 | //
29 | // Exception
30 | //
31 |
32 | type Exception struct {
33 | Type *Reference
34 | Value *Reference
35 | Traceback *Reference
36 | }
37 |
38 | func FetchException() *Exception {
39 | var type_, value, traceback *C.PyObject
40 | C.PyErr_Fetch(&type_, &value, &traceback)
41 | if type_ != nil {
42 | defer C.PyErr_Restore(type_, value, traceback)
43 |
44 | var type__, value_, traceback_ *Reference
45 |
46 | if type_ != nil {
47 | type__ = NewReference(type_)
48 | }
49 |
50 | if value != nil {
51 | value_ = NewReference(value)
52 | }
53 |
54 | if traceback != nil {
55 | traceback_ = NewReference(traceback)
56 | }
57 |
58 | return NewExceptionRaw(type__, value_, traceback_)
59 | } else {
60 | return nil
61 | }
62 | }
63 |
64 | func NewExceptionRaw(type_ *Reference, value *Reference, traceback *Reference) *Exception {
65 | return &Exception{
66 | Type: type_,
67 | Value: value,
68 | Traceback: traceback,
69 | }
70 | }
71 |
72 | // error signature
73 | func (self *Exception) Error() string {
74 | // TODO: include traceback?
75 | if self.Value != nil {
76 | return self.Value.String()
77 | } else if self.Type != nil {
78 | return self.Type.String()
79 | } else {
80 | return "malformed Python exception"
81 | }
82 | }
83 |
--------------------------------------------------------------------------------
/general.go:
--------------------------------------------------------------------------------
1 | package python
2 |
3 | // See:
4 | // https://docs.python.org/3/c-api/init.html
5 |
6 | /*
7 | #define PY_SSIZE_T_CLEAN
8 | #include
9 | */
10 | import "C"
11 |
12 | func Initialize() {
13 | C.Py_Initialize()
14 | }
15 |
16 | func Finalize() error {
17 | if C.Py_FinalizeEx() == 0 {
18 | return nil
19 | } else {
20 | return GetError()
21 | }
22 | }
23 |
24 | func Version() string {
25 | return C.GoString(C.Py_GetVersion())
26 | }
27 |
--------------------------------------------------------------------------------
/go.mod:
--------------------------------------------------------------------------------
1 | module github.com/tliron/py4go
2 |
3 | go 1.17
4 |
--------------------------------------------------------------------------------
/go.sum:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tliron/py4go/2a45aeb39bc7dbdc5653026800fd21ad122c529f/go.sum
--------------------------------------------------------------------------------
/import.go:
--------------------------------------------------------------------------------
1 | package python
2 |
3 | // See:
4 | // https://docs.python.org/3/c-api/import.html
5 |
6 | import (
7 | "unsafe"
8 | )
9 |
10 | /*
11 | #define PY_SSIZE_T_CLEAN
12 | #include
13 | */
14 | import "C"
15 |
16 | func Import(name string) (*Reference, error) {
17 | name_ := C.CString(name)
18 | defer C.free(unsafe.Pointer(name_))
19 |
20 | if import_ := C.PyImport_ImportModule(name_); import_ != nil {
21 | return NewReference(import_), nil
22 | } else {
23 | return nil, GetError()
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/module.go:
--------------------------------------------------------------------------------
1 | package python
2 |
3 | import (
4 | "unsafe"
5 | )
6 |
7 | /*
8 | #define PY_SSIZE_T_CLEAN
9 | #include
10 | */
11 | import "C"
12 |
13 | var ModuleType = NewType(&C.PyModule_Type)
14 |
15 | func CreateModule(name string) (*Reference, error) {
16 | name_ := C.CString(name)
17 | defer C.free(unsafe.Pointer(name_))
18 |
19 | var definition C.PyModuleDef
20 | definition.m_name = name_
21 |
22 | if module := C.PyModule_Create2(&definition, C.PYTHON_ABI_VERSION); module != nil {
23 | return NewReference(module), nil
24 | } else {
25 | return nil, GetError()
26 | }
27 | }
28 |
29 | func NewModuleRaw(name string) (*Reference, error) {
30 | name_ := C.CString(name)
31 | defer C.free(unsafe.Pointer(name_))
32 |
33 | if module := C.PyModule_New(name_); module != nil {
34 | return NewReference(module), nil
35 | } else {
36 | return nil, GetError()
37 | }
38 | }
39 |
40 | func (self *Reference) GetModuleName() (string, error) {
41 | if name := C.PyModule_GetName(self.Object); name != nil {
42 | defer C.free(unsafe.Pointer(name)) // TODO: need this?
43 |
44 | return C.GoString(name), nil
45 | } else {
46 | return "", GetError()
47 | }
48 | }
49 |
50 | func (self *Reference) EnableModule() error {
51 | if name := C.PyModule_GetNameObject(self.Object); name != nil {
52 | name_ := NewReference(name)
53 | defer name_.Release()
54 |
55 | if moduleDict := C.PyImport_GetModuleDict(); moduleDict != nil {
56 | moduleDict_ := NewReference(moduleDict)
57 | defer moduleDict_.Release()
58 |
59 | return moduleDict_.SetDictItem(name_, self)
60 | } else {
61 | return GetError()
62 | }
63 | } else {
64 | return GetError()
65 | }
66 | }
67 |
68 | // PyCFunction signature, second argument unused
69 | func (self *Reference) AddModuleCFunctionNoArgs(name string, function unsafe.Pointer) error {
70 | return self.addModuleCFunction(name, function, C.METH_NOARGS)
71 | }
72 |
73 | // PyCFunction signature, second argument is the Python argument
74 | func (self *Reference) AddModuleCFunctionOneArg(name string, function unsafe.Pointer) error {
75 | return self.addModuleCFunction(name, function, C.METH_O)
76 | }
77 |
78 | // PyCFunction signature, second argument is tuple of Python arguments
79 | func (self *Reference) AddModuleCFunctionArgs(name string, function unsafe.Pointer) error {
80 | return self.addModuleCFunction(name, function, C.METH_VARARGS)
81 | }
82 |
83 | // PyCFunctionWithKeywords signature
84 | func (self *Reference) AddModuleCFunctionArgsAndKeywords(name string, function unsafe.Pointer) error {
85 | return self.addModuleCFunction(name, function, C.METH_VARARGS|C.METH_KEYWORDS)
86 | }
87 |
88 | // _PyCFunctionFast signature
89 | func (self *Reference) AddModuleCFunctionFastArgs(name string, function unsafe.Pointer) error {
90 | return self.addModuleCFunction(name, function, C.METH_FASTCALL)
91 | }
92 |
93 | // _PyCFunctionFastWithKeywords signature
94 | func (self *Reference) AddModuleCFunctionFastArgsAndKeywords(name string, function unsafe.Pointer) error {
95 | return self.addModuleCFunction(name, function, C.METH_FASTCALL|C.METH_KEYWORDS)
96 | }
97 |
98 | func (self *Reference) addModuleCFunction(name string, function unsafe.Pointer, flags C.int) error {
99 | name_ := C.CString(name)
100 | defer C.free(unsafe.Pointer(name_))
101 |
102 | methodDef := []C.PyMethodDef{
103 | {
104 | ml_name: name_,
105 | ml_meth: C.PyCFunction(function),
106 | ml_flags: flags,
107 | },
108 | {}, // NULL end of array
109 | }
110 |
111 | if C.PyModule_AddFunctions(self.Object, &methodDef[0]) == 0 {
112 | return nil
113 | } else {
114 | return GetError()
115 | }
116 | }
117 |
--------------------------------------------------------------------------------
/object.go:
--------------------------------------------------------------------------------
1 | package python
2 |
3 | // See:
4 | // https://docs.python.org/3/c-api/object.html
5 |
6 | import (
7 | "unsafe"
8 | )
9 |
10 | /*
11 | #define PY_SSIZE_T_CLEAN
12 | #include
13 | */
14 | import "C"
15 |
16 | // fmt.Stringer interface
17 | func (self *Reference) String() string {
18 | if str := C.PyObject_Str(self.Object); str != nil {
19 | if data := C.PyUnicode_AsUTF8String(str); data != nil {
20 | defer C.Py_DecRef(data)
21 | if string_ := C.PyBytes_AsString(data); string_ != nil {
22 | return C.GoString(string_)
23 | } else {
24 | return ""
25 | }
26 | } else {
27 | return ""
28 | }
29 | } else {
30 | return ""
31 | }
32 | }
33 |
34 | func (self *Reference) Acquire() {
35 | C.Py_IncRef(self.Object)
36 | }
37 |
38 | func (self *Reference) Release() {
39 | C.Py_DecRef(self.Object)
40 | }
41 |
42 | func (self *Reference) Str() (*Reference, error) {
43 | if str := C.PyObject_Str(self.Object); str != nil {
44 | return NewReference(str), nil
45 | } else {
46 | return nil, GetError()
47 | }
48 | }
49 |
50 | func (self *Reference) GetAttr(name string) (*Reference, error) {
51 | name_ := C.CString(name)
52 | defer C.free(unsafe.Pointer(name_))
53 |
54 | if attr := C.PyObject_GetAttrString(self.Object, name_); attr != nil {
55 | return NewReference(attr), nil
56 | } else {
57 | return nil, GetError()
58 | }
59 | }
60 |
61 | func (self *Reference) SetAttr(name string, reference *Reference) error {
62 | name_ := C.CString(name)
63 | defer C.free(unsafe.Pointer(name_))
64 |
65 | if C.PyObject_SetAttrString(self.Object, name_, reference.Object) == 0 {
66 | return nil
67 | } else {
68 | return GetError()
69 | }
70 | }
71 |
72 | func (self *Reference) Call(args ...interface{}) (*Reference, error) {
73 | if args_, err := NewTuple(args...); err == nil {
74 | if kw, err := NewDict(); err == nil {
75 | return self.CallRaw(args_, kw)
76 | } else {
77 | return nil, err
78 | }
79 | } else {
80 | return nil, err
81 | }
82 | }
83 |
84 | func (self *Reference) CallRaw(args *Reference, kw *Reference) (*Reference, error) {
85 | if r := C.PyObject_Call(self.Object, args.Object, kw.Object); r != nil {
86 | return NewReference(r), nil
87 | } else {
88 | return nil, GetError()
89 | }
90 | }
91 |
--------------------------------------------------------------------------------
/path.go:
--------------------------------------------------------------------------------
1 | package python
2 |
3 | import (
4 | "os"
5 | "path/filepath"
6 | )
7 |
8 | const PYTHONPATH = "PYTHONPATH"
9 |
10 | func SetPythonPath(path ...string) {
11 | path_ := filepath.Join(path...)
12 | os.Setenv(PYTHONPATH, path_)
13 | }
14 |
15 | func AppendPythonPath(path ...string) {
16 | path_ := filepath.SplitList(os.Getenv(PYTHONPATH))
17 | path_ = append(path_, path...)
18 | path__ := filepath.Join(path_...)
19 | os.Setenv(PYTHONPATH, path__)
20 | }
21 |
22 | func PrependPythonPath(path ...string) {
23 | path_ := filepath.SplitList(os.Getenv(PYTHONPATH))
24 | path_ = append(path, path_...)
25 | path__ := filepath.Join(path_...)
26 | os.Setenv(PYTHONPATH, path__)
27 | }
28 |
--------------------------------------------------------------------------------
/primitive.go:
--------------------------------------------------------------------------------
1 | package python
2 |
3 | // See:
4 | // https://docs.python.org/3/c-api/concrete.html
5 |
6 | import (
7 | "fmt"
8 | "unsafe"
9 | )
10 |
11 | /*
12 | #include "stdint.h"
13 | #define PY_SSIZE_T_CLEAN
14 | #include
15 | */
16 | import "C"
17 |
18 | func NewPrimitiveReference(value interface{}) (*Reference, error) {
19 | if value == nil {
20 | return None, nil
21 | }
22 |
23 | switch value_ := value.(type) {
24 | case *Reference:
25 | return value_, nil
26 | case bool:
27 | if value_ {
28 | return True, nil
29 | } else {
30 | return False, nil
31 | }
32 | case int64:
33 | return NewLong(value_)
34 | case int32:
35 | return NewLong(int64(value_))
36 | case int8:
37 | return NewLong(int64(value_))
38 | case int:
39 | return NewLong(int64(value_))
40 | case float64:
41 | return NewFloat(value_)
42 | case float32:
43 | return NewFloat(float64(value_))
44 | case string:
45 | return NewUnicode(value_)
46 | case []interface{}:
47 | return NewList(value_...)
48 | case []byte:
49 | return NewBytes(value_)
50 | }
51 |
52 | return nil, fmt.Errorf("unsupported primitive: %s", value)
53 | }
54 |
55 | //
56 | // None
57 | //
58 |
59 | var None = NewReference(C.Py_None)
60 |
61 | //
62 | // Bool
63 | //
64 |
65 | var BoolType = NewType(&C.PyBool_Type)
66 |
67 | var True = NewReference(C.Py_True)
68 | var False = NewReference(C.Py_False)
69 |
70 | func (self *Reference) IsBool() bool {
71 | return self.Type().IsSubtype(BoolType)
72 | }
73 |
74 | func (self *Reference) ToBool() bool {
75 | switch self.Object {
76 | case C.Py_True:
77 | return true
78 | case C.Py_False:
79 | return false
80 | }
81 | return false
82 | }
83 |
84 | //
85 | // Long
86 | //
87 |
88 | var LongType = NewType(&C.PyLong_Type)
89 |
90 | func NewLong(value int64) (*Reference, error) {
91 | if long := C.PyLong_FromLong(C.long(value)); long != nil {
92 | return NewReference(long), nil
93 | } else {
94 | return nil, GetError()
95 | }
96 | }
97 |
98 | func (self *Reference) IsLong() bool {
99 | // More efficient to use the flag
100 | return self.Type().HasFlag(C.Py_TPFLAGS_LONG_SUBCLASS)
101 | }
102 |
103 | func (self *Reference) ToInt64() (int64, error) {
104 | if long := C.PyLong_AsLong(self.Object); !HasException() {
105 | return int64(long), nil
106 | } else {
107 | return 0, GetError()
108 | }
109 | }
110 |
111 | //
112 | // Float
113 | //
114 |
115 | var FloatType = NewType(&C.PyFloat_Type)
116 |
117 | func NewFloat(value float64) (*Reference, error) {
118 | if float := C.PyFloat_FromDouble(C.double(value)); float != nil {
119 | return NewReference(float), nil
120 | } else {
121 | return nil, GetError()
122 | }
123 | }
124 |
125 | func (self *Reference) IsFloat() bool {
126 | return self.Type().IsSubtype(FloatType)
127 | }
128 |
129 | func (self *Reference) ToFloat64() (float64, error) {
130 | if double := C.PyFloat_AsDouble(self.Object); !HasException() {
131 | return float64(double), nil
132 | } else {
133 | return 0.0, GetError()
134 | }
135 | }
136 |
137 | //
138 | // Unicode
139 | //
140 |
141 | var UnicodeType = NewType(&C.PyUnicode_Type)
142 |
143 | func NewUnicode(value string) (*Reference, error) {
144 | value_ := C.CString(value)
145 | defer C.free(unsafe.Pointer(value_))
146 |
147 | if unicode := C.PyUnicode_FromString(value_); unicode != nil {
148 | return NewReference(unicode), nil
149 | } else {
150 | return nil, GetError()
151 | }
152 | }
153 |
154 | func (self *Reference) IsUnicode() bool {
155 | // More efficient to use the flag
156 | return self.Type().HasFlag(C.Py_TPFLAGS_UNICODE_SUBCLASS)
157 | }
158 |
159 | func (self *Reference) ToString() (string, error) {
160 | if utf8stringBytes := C.PyUnicode_AsUTF8String(self.Object); utf8stringBytes != nil {
161 | defer C.Py_DecRef(utf8stringBytes)
162 |
163 | if utf8string := C.PyBytes_AsString(utf8stringBytes); utf8string != nil {
164 | return C.GoString(utf8string), nil
165 | } else {
166 | return "", GetError()
167 | }
168 | } else {
169 | return "", GetError()
170 | }
171 | }
172 |
173 | //
174 | // Tuple
175 | //
176 |
177 | var TupleType = NewType(&C.PyTuple_Type)
178 |
179 | func NewTuple(items ...interface{}) (*Reference, error) {
180 | if tuple, err := NewTupleRaw(len(items)); err == nil {
181 | for index, item := range items {
182 | if item_, err := NewPrimitiveReference(item); err == nil {
183 | if err := tuple.SetTupleItem(index, item_); err != nil {
184 | return nil, err
185 | }
186 | } else {
187 | return nil, err
188 | }
189 | }
190 | return tuple, nil
191 | } else {
192 | return nil, GetError()
193 | }
194 | }
195 |
196 | func NewTupleRaw(size int) (*Reference, error) {
197 | if tuple := C.PyTuple_New(C.int64_t(size)); tuple != nil {
198 | return NewReference(tuple), nil
199 | } else {
200 | return nil, GetError()
201 | }
202 | }
203 |
204 | func (self *Reference) IsTuple() bool {
205 | // More efficient to use the flag
206 | return self.Type().HasFlag(C.Py_TPFLAGS_TUPLE_SUBCLASS)
207 | }
208 |
209 | func (self *Reference) SetTupleItem(index int, item *Reference) error {
210 | if C.PyTuple_SetItem(self.Object, C.int64_t(index), item.Object) == 0 {
211 | return nil
212 | } else {
213 | return GetError()
214 | }
215 | }
216 |
217 | //
218 | // List
219 | //
220 |
221 | var ListType = NewType(&C.PyList_Type)
222 |
223 | func NewList(items ...interface{}) (*Reference, error) {
224 | if list, err := NewListRaw(len(items)); err == nil {
225 | for index, item := range items {
226 | if item_, err := NewPrimitiveReference(item); err == nil {
227 | if err := list.SetListItem(index, item_); err != nil {
228 | return nil, err
229 | }
230 | } else {
231 | return nil, err
232 | }
233 | }
234 | return list, nil
235 | } else {
236 | return nil, GetError()
237 | }
238 | }
239 |
240 | func NewListRaw(size int) (*Reference, error) {
241 | if list := C.PyList_New(C.int64_t(size)); list != nil {
242 | return NewReference(list), nil
243 | } else {
244 | return nil, GetError()
245 | }
246 | }
247 |
248 | func (self *Reference) IsList() bool {
249 | // More efficient to use the flag
250 | return self.Type().HasFlag(C.Py_TPFLAGS_LIST_SUBCLASS)
251 | }
252 |
253 | func (self *Reference) SetListItem(index int, item *Reference) error {
254 | if C.PyList_SetItem(self.Object, C.int64_t(index), item.Object) == 0 {
255 | return nil
256 | } else {
257 | return GetError()
258 | }
259 | }
260 |
261 | //
262 | // Dict
263 | //
264 |
265 | var DictType = NewType(&C.PyDict_Type)
266 |
267 | func NewDict() (*Reference, error) {
268 | if dict := C.PyDict_New(); dict != nil {
269 | return NewReference(dict), nil
270 | } else {
271 | return nil, GetError()
272 | }
273 | }
274 |
275 | func (self *Reference) IsDict() bool {
276 | // More efficient to use the flag
277 | return self.Type().HasFlag(C.Py_TPFLAGS_DICT_SUBCLASS)
278 | }
279 |
280 | func (self *Reference) SetDictItem(key *Reference, value *Reference) error {
281 | if C.PyDict_SetItem(self.Object, key.Object, value.Object) == 0 {
282 | return nil
283 | } else {
284 | return GetError()
285 | }
286 | }
287 |
288 | //
289 | // Set (mutable)
290 | //
291 |
292 | var SetType = NewType(&C.PySet_Type)
293 |
294 | func NewSet(iterable *Reference) (*Reference, error) {
295 | if set := C.PySet_New(iterable.Object); set != nil {
296 | return NewReference(set), nil
297 | } else {
298 | return nil, GetError()
299 | }
300 | }
301 |
302 | func (self *Reference) IsSet() bool {
303 | return self.Type().IsSubtype(SetType)
304 | }
305 |
306 | //
307 | // Frozen set (immutable)
308 | //
309 |
310 | var FrozenSetType = NewType(&C.PyFrozenSet_Type)
311 |
312 | func NewFrozenSet(iterable *Reference) (*Reference, error) {
313 | if frozenSet := C.PyFrozenSet_New(iterable.Object); frozenSet != nil {
314 | return NewReference(frozenSet), nil
315 | } else {
316 | return nil, GetError()
317 | }
318 | }
319 |
320 | func (self *Reference) IsFrozenSet() bool {
321 | return self.Type().IsSubtype(FrozenSetType)
322 | }
323 |
324 | //
325 | // Bytes (immutable)
326 | //
327 |
328 | var BytesType = NewType(&C.PyBytes_Type)
329 |
330 | func NewBytes(value []byte) (*Reference, error) {
331 | size := len(value)
332 | value_ := C.CBytes(value)
333 | defer C.free(value_) // TODO: check this!
334 |
335 | if bytes := C.PyBytes_FromStringAndSize((*C.char)(value_), C.int64_t(size)); bytes != nil {
336 | return NewReference(bytes), nil
337 | } else {
338 | return nil, GetError()
339 | }
340 | }
341 |
342 | func (self *Reference) IsBytes() bool {
343 | // More efficient to use the flag
344 | return self.Type().HasFlag(C.Py_TPFLAGS_BYTES_SUBCLASS)
345 | }
346 |
347 | func (self *Reference) ToBytes() ([]byte, error) {
348 | if pointer, size, err := self.AccessBytes(); err == nil {
349 | return C.GoBytes(pointer, C.int(size)), nil
350 | } else {
351 | return nil, err
352 | }
353 | }
354 |
355 | func (self *Reference) AccessBytes() (unsafe.Pointer, int, error) {
356 | if string_ := C.PyBytes_AsString(self.Object); string_ != nil {
357 | size := C.PyBytes_Size(self.Object)
358 | return unsafe.Pointer(string_), int(size), nil
359 | } else {
360 | return nil, 0, GetError()
361 | }
362 | }
363 |
364 | //
365 | // Byte array (mutable)
366 | //
367 |
368 | var ByteArrayType = NewType(&C.PyByteArray_Type)
369 |
370 | func NewByteArray(value []byte) (*Reference, error) {
371 | size := len(value)
372 | value_ := C.CBytes(value)
373 | defer C.free(value_) // TODO: check this!
374 |
375 | if byteArray := C.PyByteArray_FromStringAndSize((*C.char)(value_), C.int64_t(size)); byteArray != nil {
376 | return NewReference(byteArray), nil
377 | } else {
378 | return nil, GetError()
379 | }
380 | }
381 |
382 | func (self *Reference) IsByteArray() bool {
383 | return self.Type().IsSubtype(ByteArrayType)
384 | }
385 |
386 | func (self *Reference) ByteArrayToBytes() ([]byte, error) {
387 | if pointer, size, err := self.AccessByteArray(); err == nil {
388 | return C.GoBytes(pointer, C.int(size)), nil
389 | } else {
390 | return nil, err
391 | }
392 | }
393 |
394 | func (self *Reference) AccessByteArray() (unsafe.Pointer, int, error) {
395 | if string_ := C.PyByteArray_AsString(self.Object); string_ != nil {
396 | size := C.PyByteArray_Size(self.Object)
397 | return unsafe.Pointer(string_), int(size), nil
398 | } else {
399 | return nil, 0, GetError()
400 | }
401 | }
402 |
--------------------------------------------------------------------------------
/reference.go:
--------------------------------------------------------------------------------
1 | package python
2 |
3 | /*
4 | #define PY_SSIZE_T_CLEAN
5 | #include
6 | */
7 | import "C"
8 |
9 | //
10 | // Reference
11 | //
12 |
13 | type Reference struct {
14 | Object *C.PyObject
15 | }
16 |
17 | func NewReference(pyObject *C.PyObject) *Reference {
18 | return &Reference{pyObject}
19 | }
20 |
21 | func (self *Reference) Type() *Type {
22 | return NewType(self.Object.ob_type)
23 | }
24 |
--------------------------------------------------------------------------------
/scripts/_env:
--------------------------------------------------------------------------------
1 |
2 | _HERE=$(dirname "$(readlink --canonicalize "$BASH_SOURCE")")
3 |
4 | . "$_HERE/_functions"
5 |
6 | MODULE=github.com/tliron/py4go
7 |
8 | ROOT=$(readlink --canonicalize "$_HERE/..")
9 |
10 | GOPATH=${GOPATH:-$HOME/go}
11 |
12 | export TMPDIR=/Depot/Temporary
13 |
--------------------------------------------------------------------------------
/scripts/_functions:
--------------------------------------------------------------------------------
1 |
2 | RED='\033[0;31m'
3 | GREEN='\033[0;32m'
4 | BLUE='\033[0;34m'
5 | CYAN='\033[0;36m'
6 | RESET='\033[0m'
7 |
8 | # Colored messages (blue is the default)
9 | # Examples:
10 | # m "hello world"
11 | # m "hello world" "$GREEN"
12 | function m () {
13 | local COLOR=${2:-$BLUE}
14 | echo -e "$COLOR$1$RESET"
15 | }
16 |
17 | function git_version () {
18 | VERSION=$(git -C "$ROOT" describe --tags --always)
19 | REVISION=$(git -C "$ROOT" rev-parse HEAD)
20 | TIMESTAMP=$(date +"%Y-%m-%d %H:%M:%S %Z")
21 | GO_VERSION=$(go version | { read _ _ v _; echo ${v#go}; })
22 | }
23 |
--------------------------------------------------------------------------------
/scripts/_trap:
--------------------------------------------------------------------------------
1 |
2 | function goodbye () {
3 | local DURATION=$(date --date=@$(( "$(date +%s)" - "$TRAP_START_TIME" )) --utc +%T)
4 | local CODE=$1
5 | cd "$TRAP_DIR"
6 | if [ "$CODE" == 0 ]; then
7 | m "$(realpath --relative-to="$ROOT" "$0") succeeded! $DURATION" "$GREEN"
8 | elif [ "$CODE" == abort ]; then
9 | m "Aborted $(realpath --relative-to="$ROOT" "$0")! $DURATION" "$RED"
10 | else
11 | m "Oh no! $(realpath --relative-to="$ROOT" "$0") failed! $DURATION" "$RED"
12 | fi
13 | }
14 |
15 | function trap_EXIT () {
16 | local ERR=$?
17 | goodbye "$ERR"
18 | exit "$ERR"
19 | }
20 |
21 | function trap_INT () {
22 | goodbye abort
23 | trap - EXIT
24 | exit 1
25 | }
26 |
27 | TRAP_DIR=$PWD
28 | TRAP_START_TIME=$(date +%s)
29 |
30 | trap trap_INT INT
31 |
32 | trap trap_EXIT EXIT
33 |
--------------------------------------------------------------------------------
/scripts/example:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | set -e
3 |
4 | HERE=$(dirname "$(readlink --canonicalize "$BASH_SOURCE")")
5 | . "$HERE/_env"
6 | . "$HERE/_trap"
7 |
8 | function run () {
9 | local NAME=$1
10 | pushd "$ROOT/$NAME" > /dev/null
11 | go run .
12 | popd > /dev/null
13 | }
14 |
15 | run examples/hello-world
16 |
--------------------------------------------------------------------------------
/scripts/format:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | set -e
3 |
4 | HERE=$(dirname "$(readlink --canonicalize "$BASH_SOURCE")")
5 | . "$HERE/_env"
6 |
7 | gofmt -w -s -e \
8 | "$ROOT" \
9 | "$ROOT/examples/hello-world/" \
10 | "$ROOT/examples/hello-world/api"
11 |
--------------------------------------------------------------------------------
/scripts/refresh-mod:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | set -e
3 |
4 | HERE=$(dirname "$(readlink --canonicalize "$BASH_SOURCE")")
5 | . "$HERE/_env"
6 |
7 | rm --force "$ROOT/go.mod" "$ROOT/go.sum"
8 |
9 | cd "$ROOT"
10 | go mod init "$MODULE"
11 | go mod tidy
12 |
13 | "$HERE/build"
14 |
15 | go mod tidy
16 |
--------------------------------------------------------------------------------
/type.go:
--------------------------------------------------------------------------------
1 | package python
2 |
3 | /*
4 | #define PY_SSIZE_T_CLEAN
5 | #include
6 | */
7 | import "C"
8 |
9 | //
10 | // Type
11 | //
12 |
13 | type Type struct {
14 | Object *C.PyTypeObject
15 | }
16 |
17 | func NewType(pyTypeObject *C.PyTypeObject) *Type {
18 | return &Type{pyTypeObject}
19 | }
20 |
21 | func (self *Type) IsSubtype(type_ *Type) bool {
22 | return C.PyType_IsSubtype(self.Object, type_.Object) != 0
23 | }
24 |
25 | func (self *Type) HasFlag(flag C.ulong) bool {
26 | return C.PyType_GetFlags(self.Object)&flag != 0
27 | }
28 |
--------------------------------------------------------------------------------