Monday, December 3, 2007

Incompatabilities between classic and new-style Python classes

radix asked me if I had a blog post about why changing a class from classic to new-style is a bad idea. After I told him that I didn't he insisted that I should write one. Since doing so will take less work than finding something else to distract his attention, here it is:






  • attribute lookup


    Attributes on instances of classic classes override attributes of the same name on their class. For example:


    >>> class SimpleDescriptor(object):
    ... def __get__(self, instance, type):
    ... print 'getting'
    ... return instance.__dict__['simple']
    ... def __set__(self, instance, value):
    ... print 'setting'
    ... instance.__dict__['simple'] = value
    ...
    >>> class classic:
    ... simple = SimpleDescriptor()
    ...
    >>> x = classic()
    >>> x.simple = 10
    >>> x.simple
    10
    >>> class newstyle(object):
    ... simple = SimpleDescriptor()
    ...
    >>> x = newstyle()
    >>> x.simple
    getting
    Traceback (most recent call last):
    File "", line 1, in ?
    File "", line 4, in __get__
    KeyError: 'simple'
    >>> x.simple = 10
    setting
    >>> x.simple
    getting
    10

    As you can see, the descriptor on the new-style class can both handle the setattr and continue to operate after shadowing itself with an instance variable.




  • Special methods


    A particularly interesting consequence of the previous point is that the behavior of special methods changes in some cases:


    >>> class x:
    ... def __init__(self):
    ... self.__eq__ = lambda other: True
    ...
    >>> x() == 10
    True
    >>> class x(object):
    ... def __init__(self):
    ... self.__eq__ = lambda other: True
    ...
    >>> x() == 10
    False
    >>>




  • MRO


    Rules for determining the method resolution forbid new-style classes in places where classic classes are acceptable. Consider:


    >>> class x: pass
    ...
    >>> class y(object, x): pass
    ...
    >>> class x(object): pass
    ...
    >>> class y(object, x): pass
    ...
    Traceback (most recent call last):
    File "", line 1, in ?
    TypeError: Error when calling the metaclass bases
    Cannot create a consistent method resolution
    order (MRO) for bases object, x
    >>>







Hopefully that's enough to satisfy radix. Know of other incompatibilities? Please comment.

No comments:

Post a Comment