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

4 Responses to “Odd Old-Style vs. New-Style Class Behavior”

  1. Robert Brewer Says:

    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. ;)

  2. christian Says:

    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 the self._items dictionary’s instance of it. Meanwhile on the new-style class, __getattr__ is never called.

  3. christian Says:

    A great follow-up here: http://mg.pov.lt/blog/suprising-old-style-classes

  4. Matt Wilson Says:

    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.

Leave a Reply