Skip to content

Commit 8cee534

Browse files
committed
Make getattr return __methods__ implemented in go - fixes #28
Before this change getattr and friends would only return attributes implemented in python. After this change, getattr uses reflection to see if there are any __methods__ and constructs a python bound method for them.
1 parent ee952c8 commit 8cee534

File tree

3 files changed

+105
-3
lines changed

3 files changed

+105
-3
lines changed

py/internal.go

+14-1
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,11 @@
88

99
package py
1010

11-
import "fmt"
11+
import (
12+
"fmt"
13+
"reflect"
14+
"strings"
15+
)
1216

1317
// AttributeName converts an Object to a string, raising a TypeError
1418
// if it wasn't a String
@@ -209,6 +213,15 @@ func GetAttrString(self Object, key string) (res Object, err error) {
209213
return res, err
210214
}
211215

216+
// Look up any __special__ methods as M__special__ and return a bound method
217+
if len(key) >= 5 && strings.HasPrefix(key, "__") && strings.HasSuffix(key, "__") {
218+
objectValue := reflect.ValueOf(self)
219+
methodValue := objectValue.MethodByName("M" + key)
220+
if methodValue.IsValid() {
221+
return newBoundMethod(key, methodValue.Interface())
222+
}
223+
}
224+
212225
// Look in the instance dictionary if it exists
213226
if I, ok := self.(IGetDict); ok {
214227
dict := I.GetDict()

py/method.go

+69-2
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,10 @@
99

1010
package py
1111

12+
import (
13+
"fmt"
14+
)
15+
1216
// Types for methods
1317

1418
// Called with self and a tuple of args
@@ -143,7 +147,7 @@ func (m *Method) Call(self Object, args Tuple) (Object, error) {
143147
}
144148
return f(self, args[0])
145149
}
146-
panic("Unknown method type")
150+
panic(fmt.Sprintf("Unknown method type: %T", m.method))
147151
}
148152

149153
// Call the method with the given arguments
@@ -159,7 +163,70 @@ func (m *Method) CallWithKeywords(self Object, args Tuple, kwargs StringDict) (O
159163
func(Object, Object) (Object, error):
160164
return nil, ExceptionNewf(TypeError, "%s() takes no keyword arguments", m.Name)
161165
}
162-
panic("Unknown method type")
166+
panic(fmt.Sprintf("Unknown method type: %T", m.method))
167+
}
168+
169+
// Return a new Method with the bound method passed in, or an error
170+
//
171+
// This needs to convert the methods into internally callable python
172+
// methods
173+
func newBoundMethod(name string, fn interface{}) (Object, error) {
174+
m := &Method{
175+
Name: name,
176+
}
177+
switch f := fn.(type) {
178+
case func(args Tuple) (Object, error):
179+
m.method = func(_ Object, args Tuple) (Object, error) {
180+
return f(args)
181+
}
182+
// M__call__(args Tuple, kwargs StringDict) (Object, error)
183+
case func(args Tuple, kwargs StringDict) (Object, error):
184+
m.method = func(_ Object, args Tuple, kwargs StringDict) (Object, error) {
185+
return f(args, kwargs)
186+
}
187+
// M__str__() (Object, error)
188+
case func() (Object, error):
189+
m.method = func(_ Object) (Object, error) {
190+
return f()
191+
}
192+
// M__add__(other Object) (Object, error)
193+
case func(Object) (Object, error):
194+
m.method = func(_ Object, other Object) (Object, error) {
195+
return f(other)
196+
}
197+
// M__getattr__(name string) (Object, error)
198+
case func(string) (Object, error):
199+
m.method = func(_ Object, stringObject Object) (Object, error) {
200+
name, err := StrAsString(stringObject)
201+
if err != nil {
202+
return nil, err
203+
}
204+
return f(name)
205+
}
206+
// M__get__(instance, owner Object) (Object, error)
207+
case func(Object, Object) (Object, error):
208+
m.method = func(_ Object, args Tuple) (Object, error) {
209+
var a, b Object
210+
err := UnpackTuple(args, nil, name, 2, 2, &a, &b)
211+
if err != nil {
212+
return nil, err
213+
}
214+
return f(a, b)
215+
}
216+
// M__new__(cls, args, kwargs Object) (Object, error)
217+
case func(Object, Object, Object) (Object, error):
218+
m.method = func(_ Object, args Tuple) (Object, error) {
219+
var a, b, c Object
220+
err := UnpackTuple(args, nil, name, 3, 3, &a, &b, &c)
221+
if err != nil {
222+
return nil, err
223+
}
224+
return f(a, b, c)
225+
}
226+
default:
227+
return nil, fmt.Errorf("Unknown bound method type for %q: %T", name, fn)
228+
}
229+
return m, nil
163230
}
164231

165232
// Call a method

py/tests/internal.py

+22
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
# Copyright 2018 The go-python Authors. All rights reserved.
2+
# Use of this source code is governed by a BSD-style
3+
# license that can be found in the LICENSE file.
4+
5+
from libtest import assertRaises
6+
7+
def fn(x):
8+
return x
9+
10+
doc="check internal bound methods"
11+
assert (1).__str__() == "1"
12+
assert (1).__add__(2) == 3
13+
assert fn.__call__(4) == 4
14+
assert fn.__get__(fn, None)()(1) == 1
15+
assertRaises(TypeError, fn.__get__, fn, None, None)
16+
# These tests don't work on python3.4
17+
# assert Exception().__getattr__("a") is not None # check doesn't explode only
18+
# assertRaises(TypeError, Exception().__getattr__, "a", "b")
19+
# assertRaises(ValueError, Exception().__getattr__, 42)
20+
21+
doc="finished"
22+

0 commit comments

Comments
 (0)