[IronPython] Some incompatibilities with CPython

Žiga Seilnacht ziga.seilnacht at gmail.com
Tue May 9 00:35:47 CEST 2006


Hello,

while playing with the last version of IronPython (1.0.60420),
I noticed the following bugs (the errors are given in comments,
while the expected output is from a Python 2.4.3 and should be
usable as doctest):

  - Classes (types) are missing a __class__ attribute:

     >>> class C(object): pass
     ...
     >>> C.__class__                    # Raises AttributeError
     <type 'type'>

  - The metaclass is determined incorrectly:

     >>> class M_A(type):
     ...     def __new__(meta, name, bases, dict):
     ...         print 'metaclass:', meta.__name__, 'class:', name
     ...         return type.__new__(meta, name, bases, dict)
     ...
     >>> class M_B(M_A):
     ...     pass
     ...
     >>> class A:
     ...     __metaclass__ = M_A
     ...
     metaclass: M_A class: A
     >>> class B:
     ...     __metaclass__ = M_B
     ...
     metaclass: M_B class: B
     >>> class C(A, B):                 # prints only metaclass: M_A class: C
     ...     pass
     ...
     metaclass: M_A class: C
     metaclass: M_B class: C
     >>> type(C).__name__               # this prints 'M_A' instead
     'M_B'

    CPython uses the following rules to determine the right
    metaclass for C:

     - Since C does not have a __metaclass__ attribute,
       its type is determined from its bases.
     - A is the first base, therfore its type (M_A) is called;
       unfortunately this is not the way metaclasses are
       supposed to work; the most derived metaclass should
       be selected.
     - M_A's __new__ method calls type.__new__.
     - In type.__new__, it is determined that M_A is not
       the best type for class C; it should be actually M_B.
     - Since type.__new__ was called with wrong metaclass
       as the first argument, it calls the correct metaclass.
     - This calls M_B.__new__, which again calls type.__new__,
       but this time with M_B as the first argument, which
       is correct.

    type's __new__ method should start with the following:

     class Classic:
         pass
     ClassType = type(Classic)


     class Type(type):

         """
         Metaclass that follows CPython's behaviour in "metaclass resolution".

         Code is taken from CPython's Objects/typeobject.c file.
         """
         def __new__(meta, name, bases, dict):
             winner = meta
             for cls in bases:
                 candidate = type(cls)
                 if candidate is ClassType:
                     continue
                 if issubclass(winner, candidate):
                     continue
                 if issubclass(candidate, winner):
                     winner = candidate
                     continue
                 raise TypeError("metaclass conflict: ...")
             if winner is not meta and winner.__new__ != Type.__new__:
                 return winner.__new__(winner, name, bases, dict)
             return type.__new__(winner, name, bases, dict)

  - Coercion rules are incorrect:

    Reference manual http://docs.python.org/dev/ref/coercion-rules.html says:

    Exception to the previous item: if the left operand is an instance
    of a built-in type or a new-style class, and the right operand is an
    instance of a proper subclass of that type or class and overrides the
    base's __rop__() method, the right operand's __rop__() method is tried
    before the left operand's __op__() method.

     >>> class A(object):
     ...     def __add__(self, other):
     ...         print self.__class__.__name__
     ...     __radd__ = __add__
     ...
     >>> class B(A):
     ...     def __add__(self, other):
     ...         print self.__class__.__name__
     ...     __radd__ = __add__
     ...
     >>> class C(A): pass
     ...
     >>> a = A()
     >>> b = B()
     >>> c = C()
     >>> a + b                          # prints A
     B
     >>> a + c
     A

  - Method's member 'im_inst' has the wrong name; it should be called 'im_self':

     >>> class C(object):
     ...     def meth(self): pass
     ...
     >>> print C.meth.im_self           # raises AttributeError
     None

  - instancemethod's constructor requires the third argument:

     >>> MethodType = type(C.meth)
     >>> MethodType(C.meth.im_func, 1)  # raises TypeError
     <bound method ?.meth of 1>

  - __get__ methods require the second argument:

     >>> def f(self='spam'):
     ...     print self
     ...
     >>> cm = classmethod(f)
     >>> sm = staticmethod(f)
     >>> prop = property(f)
     >>> f.__get__(1)()                 # raises TypeError
     1
     >>> cm.__get__(1)()                # raises TypeError
     <type 'int'>
     >>> sm.__get__(1)()                # raises TypeError
     spam
     >>> prop.__get__(1)                # raises TypeError
     1

  - classmethods don't pass the class' metaclass to method constructor:

     >>> class D(object):
     ...     @classmethod
     ...     def classmeth(cls): pass
     ...
     >>> D.classmeth.im_class           # prints <class '__main__.D'>
     <type 'type'>

  - builtin object super has a lot of limitations:

     - it is missing the __self_class__ member, and doesn't expose
       any of its members as attributes:

     >>> class A(object):
     ...     def __init__(self, name):
     ...         self.__name__ = name
     ...     def meth(self):
     ...         print self.__name__
     ...     classmeth = classmethod(meth)
     ...
     >>> class B(A):
     ...     pass
     ...
     >>> b = B('b')
     >>> sup = super(B, b)
     >>> sup.__thisclass__.__name__     # raises AttributeError
     'B'
     >>> sup.__self__.__name__          # raises AttributeError
     'b'
     >>> sup.__self_class__.__name__    # raises AttributeError
     'B'

     - it works only for a limited amount of descriptors, since it
       passes itself as the second argument to the __get__ methods,
       where it should pass __self_class__:

     >>> sup.classmeth()                # raises AttributeError
     B

     - it doesn't work when both arguments are classes, since it
       allways stores the second argument in its __self__ member,
       even when it should store it in its __self_class__ member.
       As a consequence it passes the second class as the first
       argument to the __get__ methods:

     >>> sup = super(B, B)
     >>> sup.__thisclass__.__name__     # raises AttributeError
     'B'
     >>> sup.__self__.__name__          # raises AttributeError
     'B'
     >>> sup.__self_class__.__name__    # raises AttributeError
     'B'
     >>> sup.meth()                     # raises AttributeError
     Traceback (most recent call last):
         ...
     TypeError: unbound method meth() must be called with B instance as first argument (got nothing instead)
     >>> sup.classmeth()                # raises AttributeError
     B

     - unbound super objects don't act as descriptors:

     >>> class A(object):
     ...     def meth(self):
     ...         print "A.meth called"
     ...
     >>> class B(A):
     ...     def meth(self):
     ...         print "B.meth called"
     ...         return self.__super.meth()
     ...
     >>> B._B__super = super(B)
     >>> b = B()
     >>> b.meth()                       # raises TypeError
     B.meth called
     A.meth called

I hope this report helps,
Ziga



More information about the Ironpython-users mailing list