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 _______________________________________________ users mailing list users@lists.ironpython.com http://lists.ironpython.com/listinfo.cgi/users-ironpython.com