Odd Old-Style vs. New-Style Class Behavior
So we have some older Python code at work that uses old-style classes. We usually try and bring those up to date when we encounter them.
The other day one of the developers did that and one of our tests started failing. A simple change from:
class Foo: # stuff here
to:
class Foo(object): # stuff here
was all that happened.
Here is some code that encapsulates the problem and runs with some interesting results:
"""Two nearly identical classes with quite different behavior.""" class LameContainerOld: def __init__(self): self._items = {'bar':'test'} def __getitem__(self, name): return self._items[name] def __getattr__(self, attr): return getattr(self._items, attr) class LameContainerNew(object): def __init__(self): self._items = {'bar':'test'} def __getitem__(self, name): return self._items[name] def __getattr__(self, attr): return getattr(self._items, attr) if __name__ == '__main__': for cls in [LameContainerOld, LameContainerNew]: container = cls() print "Testing", cls try: 'foo' in container except Exception, e: print "\tMembership in %s raised %r!" % (container.__class__, e) else: print "\tMembership in %s worked!" % container.__class__ print "\t%s" % container.__getitem__
Here is some output from running that:
Testing __main__.LameContainerOld
Membership in __main__.LameContainerOld worked!
<bound method LameContainerOld.__getitem__ of {'bar': 'test'}>
Testing <class '__main__.LameContainerNew'>
Membership in <class '__main__.LameContainerNew'> raised KeyError(0,)!
<bound method LameContainerNew.__getitem__ of <__main__.LameContainerNew object at 0xb7d1c1ac>>
From what I can tell, when a membership test happens on the old-style instance, a membership test is done on self._items and it returns False. When the membership test happens on the new-style instance, it tries to treat it like a sequence and calls the __getitem__ method with index 0.
Does that seem like a correct analysis? Does anyone know why the behavior is different there?
Also look at the output for printing container.__getitem__. Isn’t __getattr__ only supposed to be called when the attribute isn’t present on the instance? Why does it return the __getitem__ method of self._items for the old-style instance then?
Very puzzling.
cw
May 21st, 2009 at 4:19 pm
Change your getattr funcs to this:
def __getattr__(self, attr):
result = getattr(self._items, attr)
print ‘Retrieving attr %r = %r’ % (attr, result)
return result
…and you’ll start to see the differences.
May 21st, 2009 at 5:10 pm
It’s true, I do see differences, but I still can’t explain them.
The
__getattr__method seems to be called on the old-style class for__contains__and returns theself._itemsdictionary’s instance of it. Meanwhile on the new-style class,__getattr__is never called.May 21st, 2009 at 5:29 pm
A great follow-up here: http://mg.pov.lt/blog/suprising-old-style-classes
May 22nd, 2009 at 9:18 am
There’s a *lot* of old-style classes floating around, even in the standard library. I don’t understand the issue you’re seeing here, but a while back I ran into a bunch of problems when I tried to use UserDict. Since UserDict is an old-style class, my calls to super yielded some hilariously confusing error messages.