[IronPython] Some incompatibilities with CPython

Dino Viehland dinov at exchange.microsoft.com
Tue May 9 09:10:04 PDT 2006

Thanks for the bugs!  This is a great list of incompatibilities, I've gone ahead and opened a bug to track these.

I don't know that we'll get all of these fixed for the next release, but we'll certainly look into fixing what we can, and getting the rest fixed shortly after that.  If some of these are more important to you than others let us know and we'll try to prioritize appropriately.

Do you want to help develop Dynamic languages on CLR? (http://members.microsoft.com/careers/search/details.aspx?JobID=6D4754DE-11F0-45DF-8B78-DC1B43134038)

-----Original Message-----
From: users-bounces at lists.ironpython.com [mailto:users-bounces at lists.ironpython.com] On Behalf Of Žiga Seilnacht
Sent: Monday, May 08, 2006 3:36 PM
To: users at lists.ironpython.com
Subject: [IronPython] Some incompatibilities with CPython


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

    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:
     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:
                 if issubclass(winner, candidate):
                 if issubclass(candidate, winner):
                     winner = candidate
                 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
     >>> a + c

  - 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

  - 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
     >>> cm.__get__(1)()                # raises TypeError
     <type 'int'>
     >>> sm.__get__(1)()                # raises TypeError
     >>> prop.__get__(1)                # raises TypeError

  - 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
     >>> sup.__self__.__name__          # raises AttributeError
     >>> sup.__self_class__.__name__    # raises AttributeError

     - 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

     - 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
     >>> sup.__self__.__name__          # raises AttributeError
     >>> sup.__self_class__.__name__    # raises AttributeError
     >>> 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

     - 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,
users mailing list
users at lists.ironpython.com

More information about the users mailing list