Skip to content

Commit 72ecd40

Browse files
committed
Merge pull request #17 from tanakad/feature/kwd-args-init
Support named arguments when initialize python instance
2 parents 30006fa + 79894e4 commit 72ecd40

9 files changed

+301
-78
lines changed

README.md

+12-8
Original file line numberDiff line numberDiff line change
@@ -54,13 +54,15 @@ env PYTHON_CONFIGURE_OPTS="--enable-shared" pyenv install -v 2.7.9
5454

5555
```python
5656
class SampleClass(object):
57-
def __init__(self, params):
58-
# params is dictionary
57+
def __init__(self, arg1, arg2='arg2', arg3='arg3', **arg4):
58+
# initializing
5959
# blow BQL sample will set like:
60-
# params['v1'] = -1
61-
# params['v2'] = 'string'
60+
# arg1 = 'arg1'
61+
# arg2 = 'arg2' # default value
62+
# arg3 = 'arg3a' # overwritten
63+
# arg4 = {'new_arg1':1, 'new_arg2':'2'} # variable named arguments
6264

63-
def sample_method(self, v1, v2):
65+
def sample_method(self, v1, v2, v3):
6466
# do something
6567

6668
def write_method(self, value):
@@ -76,8 +78,10 @@ CREATE STATE sample_module TYPE pystate
7678
class_name = 'SampleClass', -- required
7779
write_method = 'write_method', -- optional
7880
-- rest parameters are used for initializing constructor arguments.
79-
v1 = -1,
80-
v2 = 'string'
81+
arg1 = 'arg1',
82+
arg3 = 'arg3a',
83+
new_arg1 = 1,
84+
new_arg2 = '2'
8185
;
8286
```
8387

@@ -100,7 +104,7 @@ Those UDS creation query and UDF are same as following python code.
100104
import sample_module
101105

102106
# same as CREATE STATE
103-
sm = sample_module.SampleClass({'v1':-1, 'v2':'string'})
107+
sm = sample_module.SampleClass(arg1='arg1', arg3='arg3a', new_arg1=1, new_arg2='2')
104108

105109
# same as EVAL
106110
sm.sample_method(arg1, arg2, arg3)

p/_test_new_instance.py

+11
Original file line numberDiff line numberDiff line change
@@ -46,3 +46,14 @@ def get_instance_str(self):
4646
class ChildClass(PythonTest3):
4747
v = "instance_value"
4848
# ChildClass.get_class_value() will return "instance_value"
49+
50+
51+
class PythonTestForKwd(object):
52+
53+
def __init__(self, a, b=5, **c):
54+
self.a = a
55+
self.b = b
56+
self.c = c
57+
58+
def confirm_init(self):
59+
return str(self.a) + '_' + str(self.b) + '_' + str(self.c)

p/pyinstance.go

+85-4
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,107 @@
11
package p
22

3+
/*
4+
#include "Python.h"
5+
*/
6+
import "C"
37
import (
8+
"fmt"
49
"pfi/sensorbee/sensorbee/data"
10+
"runtime"
11+
"unsafe"
512
)
613

7-
// ObjectInstance is a bind of binding Python instance, used as `PyInstance`.
14+
// ObjectInstance is a bind of Python instance, used as `PyInstance`.
815
type ObjectInstance struct {
916
Object
1017
}
1118

1219
// Call calls `name` function.
1320
// argument type: ...data.Value
1421
// return type: data.Value
15-
func (ins *ObjectInstance) Call(name string, args ...data.Value) (data.Value, error) {
22+
func (ins *ObjectInstance) Call(name string, args ...data.Value) (data.Value,
23+
error) {
1624
return invoke(ins.p, name, args...)
1725
}
1826

1927
// CallDirect calls `name` function.
2028
// argument type: ...data.Value
2129
// return type: Object.
2230
//
23-
// This method is suitable for getting the instance object that called method returned.
24-
func (ins *ObjectInstance) CallDirect(name string, args ...data.Value) (Object, error) {
31+
// This method is suitable for getting the instance object that called method
32+
// returned.
33+
func (ins *ObjectInstance) CallDirect(name string, args ...data.Value) (Object,
34+
error) {
2535
return invokeDirect(ins.p, name, args...)
2636
}
37+
38+
func newInstance(m *ObjectModule, name string, kwdArgs data.Map,
39+
args ...data.Value) (ObjectInstance, error) {
40+
41+
cName := C.CString(name)
42+
defer C.free(unsafe.Pointer(cName))
43+
44+
type Result struct {
45+
val ObjectInstance
46+
err error
47+
}
48+
ch := make(chan *Result, 1)
49+
go func() {
50+
runtime.LockOSThread()
51+
state := GILState_Ensure()
52+
defer GILState_Release(state)
53+
54+
pyInstance := C.PyObject_GetAttrString(m.p, cName)
55+
if pyInstance == nil {
56+
ch <- &Result{ObjectInstance{}, fmt.Errorf(
57+
"fail to get '%v' class: %v", name, getPyErr())}
58+
return
59+
}
60+
defer C.Py_DecRef(pyInstance)
61+
62+
// named arguments
63+
var pyKwdArg *C.PyObject
64+
if kwdArgs == nil || len(kwdArgs) == 0 {
65+
pyKwdArg = nil
66+
} else {
67+
o, err := newPyObj(kwdArgs)
68+
if err != nil {
69+
ch <- &Result{ObjectInstance{}, fmt.Errorf(
70+
"fail to convert named arguments in creating '%v' instance: %v",
71+
name, err.Error())}
72+
return
73+
}
74+
pyKwdArg = o.p
75+
}
76+
77+
// no named arguments
78+
pyArg := C.PyTuple_New(C.Py_ssize_t(len(args)))
79+
if pyArg == nil {
80+
ch <- &Result{ObjectInstance{}, getPyErr()}
81+
return
82+
}
83+
defer C.Py_DecRef(pyArg)
84+
85+
for i, v := range args {
86+
o, err := newPyObj(v)
87+
if err != nil {
88+
ch <- &Result{ObjectInstance{}, fmt.Errorf(
89+
"fail to convert non named arguments in creating '%v' instance: %v",
90+
name, err.Error())}
91+
return
92+
}
93+
C.PyTuple_SetItem(pyArg, C.Py_ssize_t(i), o.p)
94+
}
95+
96+
ret := C.PyObject_Call(pyInstance, pyArg, pyKwdArg)
97+
if ret == nil {
98+
ch <- &Result{ObjectInstance{}, fmt.Errorf(
99+
"fail to create '%v' instance: %v", name, getPyErr())}
100+
return
101+
}
102+
ch <- &Result{ObjectInstance{Object{p: ret}}, nil}
103+
}()
104+
res := <-ch
105+
106+
return res.val, res.err
107+
}

p/pymodule.go

+54-49
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,8 @@ func LoadModule(name string) (ObjectModule, error) {
3434

3535
pyMdl, err := C.PyImport_ImportModule(cModule)
3636
if pyMdl == nil {
37-
ch <- &Result{ObjectModule{}, fmt.Errorf("cannot load '%v' module: %v", name, err)}
37+
ch <- &Result{ObjectModule{}, fmt.Errorf("cannot load '%v' module: %v",
38+
name, err)}
3839
return
3940
}
4041

@@ -46,54 +47,57 @@ func LoadModule(name string) (ObjectModule, error) {
4647
}
4748

4849
// NewInstance returns `name` constructor.
49-
func (m *ObjectModule) NewInstance(name string, args ...data.Value) (ObjectInstance, error) {
50-
cName := C.CString(name)
51-
defer C.free(unsafe.Pointer(cName))
52-
53-
type Result struct {
54-
val ObjectInstance
55-
err error
56-
}
57-
ch := make(chan *Result, 1)
58-
go func() {
59-
runtime.LockOSThread()
60-
state := GILState_Ensure()
61-
defer GILState_Release(state)
62-
63-
pyInstance := C.PyObject_GetAttrString(m.p, cName)
64-
if pyInstance == nil {
65-
ch <- &Result{ObjectInstance{}, fmt.Errorf("cannot create '%v' instance", name)}
66-
return
67-
}
68-
defer C.Py_DecRef(pyInstance)
69-
70-
pyArg := C.PyTuple_New(C.Py_ssize_t(len(args)))
71-
if pyArg == nil {
72-
ch <- &Result{ObjectInstance{}, getPyErr()}
73-
return
74-
}
75-
defer C.Py_DecRef(pyArg)
76-
77-
for i, v := range args {
78-
o, err := newPyObj(v)
79-
if err != nil {
80-
ch <- &Result{ObjectInstance{}, fmt.Errorf("%v at '%v'", err.Error(), name)}
81-
return
82-
}
83-
C.PyTuple_SetItem(pyArg, C.Py_ssize_t(i), o.p)
84-
}
85-
86-
// get constructor (called `__init__(self)`)
87-
ret := C.PyObject_CallObject(pyInstance, pyArg)
88-
if ret == nil {
89-
ch <- &Result{ObjectInstance{}, fmt.Errorf("cannot create '%v' instance", name)}
90-
return
91-
}
92-
ch <- &Result{ObjectInstance{Object{p: ret}}, nil}
93-
}()
94-
res := <-ch
50+
//
51+
// ```python
52+
// class Sample(object):
53+
// def __init__(self, a, b=5):
54+
// # initializing
55+
// ```
56+
//
57+
// To get that "Sample" python instance, callers use this function like:
58+
// NewInstance("Sample", data.Value, data.Int)
59+
// or
60+
// NewInstance("Sample", data.Value) // value "b" is optional and will set 5
61+
func (m *ObjectModule) NewInstance(name string, args ...data.Value) (
62+
ObjectInstance, error) {
63+
return newInstance(m, name, nil, args...)
64+
}
9565

96-
return res.val, res.err
66+
// NewInstanceWithKwd returns 'name' constructor with named arguments.
67+
//
68+
// ```python
69+
// class Sample(object):
70+
// def __init__(self, a, b=5, **c):
71+
// # initializing
72+
// ```
73+
//
74+
// To get that "Sample" python instance, callers use a map object as named
75+
// arguments, like:
76+
//
77+
// arg1 := data.Map{
78+
// "a": data.Value, // ex) data.String("v1")
79+
// "b": data.Int, // ex) data.Int(5)
80+
// "hoge1": data.Value, // ex) data.Float(100.0)
81+
// "hoge2": data.Value, // ex) data.True
82+
// }
83+
// `arg1` is same as `Sawmple(a-'v1', b=5, hoge1=100.0, hoge2=True)`.
84+
//
85+
// arg2 := data.Map{
86+
// "a": data.Value, // ex) data.String("v1")
87+
// "hoge1": data.Value, // ex) data.Float(100.0)
88+
// "hoge2": data.Value, // ex) data.True
89+
// }
90+
// `arg2` is same as `Sample(a='v1', hoge1=100.0, hoge2=True)`, and `self.b`
91+
// will be set default value (=5).
92+
//
93+
// arg3 := data.Map{
94+
// "a": data.Value, // ex) data.String("v1")
95+
// }
96+
// `arg3` is same as `Sample(a='v1')`, `self.b` will be set default value (=5),
97+
// and `self.c` will be set `{}`
98+
func (m *ObjectModule) NewInstanceWithKwd(name string, kwdArgs data.Map) (
99+
ObjectInstance, error) {
100+
return newInstance(m, name, kwdArgs)
97101
}
98102

99103
// GetClass returns `name` class instance.
@@ -114,7 +118,8 @@ func (m *ObjectModule) GetClass(name string) (ObjectInstance, error) {
114118

115119
pyInstance := C.PyObject_GetAttrString(m.p, cName)
116120
if pyInstance == nil {
117-
ch <- &Result{ObjectInstance{}, fmt.Errorf("cannot get '%v' instance", name)}
121+
ch <- &Result{ObjectInstance{}, fmt.Errorf("cannot get '%v' instance",
122+
name)}
118123
return
119124
}
120125
ch <- &Result{ObjectInstance{Object{p: pyInstance}}, nil}

0 commit comments

Comments
 (0)