diff --git a/runtime/core.go b/runtime/core.go index 966a2dc9..12479dc1 100644 --- a/runtime/core.go +++ b/runtime/core.go @@ -219,13 +219,23 @@ func GetItem(f *Frame, o, key *Object) (*Object, *BaseException) { // GetAttr returns the named attribute of o. Equivalent to the Python expression // getattr(o, name, def). func GetAttr(f *Frame, o *Object, name *Str, def *Object) (*Object, *BaseException) { - // TODO: Fall back to __getattr__. getAttribute := o.typ.slots.GetAttribute if getAttribute == nil { msg := fmt.Sprintf("'%s' has no attribute '%s'", o.typ.Name(), name.Value()) return nil, f.RaiseType(AttributeErrorType, msg) } + result, raised := getAttribute.Fn(f, o, name) + if raised != nil && raised.isInstance(AttributeErrorType) { + // Fall back to __getattr__ when __getattribute__ raised AttributeError + getAttr := o.typ.slots.GetAttr + if getAttr != nil { + f.RestoreExc(nil, nil) + result, raised = getAttr.Fn(f, o, name) + } + } + + // Last resort: return the default if raised != nil && raised.isInstance(AttributeErrorType) && def != nil { f.RestoreExc(nil, nil) result, raised = def, nil diff --git a/runtime/slots.go b/runtime/slots.go index b2dd2426..8d002ccf 100644 --- a/runtime/slots.go +++ b/runtime/slots.go @@ -186,6 +186,26 @@ func (s *getAttributeSlot) wrapCallable(callable *Object) bool { return true } +type getAttrSlot struct { + Fn func(*Frame, *Object, *Str) (*Object, *BaseException) +} + +func (s *getAttrSlot) makeCallable(t *Type, slotName string) *Object { + return newBuiltinFunction(slotName, func(f *Frame, args Args, kwargs KWArgs) (*Object, *BaseException) { + if raised := checkMethodArgs(f, slotName, args, t, StrType); raised != nil { + return nil, raised + } + return s.Fn(f, args[0], toStrUnsafe(args[1])) + }).ToObject() +} + +func (s *getAttrSlot) wrapCallable(callable *Object) bool { + s.Fn = func(f *Frame, o *Object, name *Str) (*Object, *BaseException) { + return callable.Call(f, Args{o, name.ToObject()}, nil) + } + return true +} + type getSlot struct { Fn func(*Frame, *Object, *Object, *Type) (*Object, *BaseException) } @@ -388,6 +408,7 @@ type typeSlots struct { GE *binaryOpSlot Get *getSlot GetAttribute *getAttributeSlot + GetAttr *getAttrSlot GetItem *binaryOpSlot GT *binaryOpSlot Hash *unaryOpSlot diff --git a/testing/class_test.py b/testing/class_test.py index bc8ec06e..f812d850 100644 --- a/testing/class_test.py +++ b/testing/class_test.py @@ -40,6 +40,8 @@ def bar(self): assert foo.baz() == 'bar' foo.b = 10 +assert foo.b == 10 + del foo.b assert not hasattr(foo, 'b') try: @@ -48,3 +50,19 @@ def bar(self): pass else: raise AssertionError + + +spamdict = {} +class Spam(object): + def __getattr__(self, key): + return spamdict[key] + def __setattr__(self, key, value): + spamdict[key] = value + +spam = Spam() +spam.eggs = 'with eggs' +assert spamdict == {'eggs': 'with eggs'}, "Change spam.eggs should update spamdict" +assert spam.eggs == 'with eggs', "spam.eggs value should came from spamdict['eggs']" + +spamdict['eggs'] = 'with more spam' +assert spam.eggs == 'with more spam', "Change spamdict['eggs'] should update spam.eggs"