[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