I'm updating IronPython to match CPython's behavior w/ for this issue: http://bugs.python.org/issue1683368 One thing that I've noticed is that this doesn't seem to be respecting the deletion of attributes (on 2.6.2): class x(object): pass x().__init__(2,3,4) # throws - seems right class x(object): def __new__(cls, *args): return object.__new__(cls) x().__init__(2,3,4) # doesn't throw - seems right del x.__new__ x().__init__(2,3,4) # doesn't throw - I would expect this to throw. Is this just a bug in CPython not updating whether __new__ has been defined? Or is there something that makes this behavior expected which I'm just missing?
2009/7/14 Dino Viehland
Is this just a bug in CPython not updating whether __new__ has been defined? Or is there something that makes this behavior expected which I’m just missing?
There's a wonderful comment about this in typeobject.c: /* You may wonder why object.__new__() only complains about arguments when object.__init__() is not overridden, and vice versa. Consider the use cases: 1. When neither is overridden, we want to hear complaints about excess (i.e., any) arguments, since their presence could indicate there's a bug. 2. When defining an Immutable type, we are likely to override only __new__(), since __init__() is called too late to initialize an Immutable object. Since __new__() defines the signature for the type, it would be a pain to have to override __init__() just to stop it from complaining about excess arguments. 3. When defining a Mutable type, we are likely to override only __init__(). So here the converse reasoning applies: we don't want to have to override __new__() just to stop it from complaining. 4. When __init__() is overridden, and the subclass __init__() calls object.__init__(), the latter should complain about excess arguments; ditto for __new__(). Use cases 2 and 3 make it unattractive to unconditionally check for excess arguments. The best solution that addresses all four use cases is as follows: __init__() complains about excess arguments unless __new__() is overridden and __init__() is not overridden (IOW, if __init__() is overridden or __new__() is not overridden); symmetrically, __new__() complains about excess arguments unless __init__() is overridden and __new__() is not overridden (IOW, if __new__() is overridden or __init__() is not overridden). However, for backwards compatibility, this breaks too much code. Therefore, in 2.6, we'll *warn* about excess arguments when both methods are overridden; for all other cases we'll use the above rules. */ -- Regards, Benjamin
Benjamin wrote:
There's a wonderful comment about this in typeobject.c:
This is basically the same what I've gathered from the issue description which was quite helpful. But in this case we're dealing with mutating the type object and changing whether __new__ or __init__ exist at all at runtime - and object.__new__/__init__ don't seem to be picking up on the change. I believe that the comments here w.r.t. mutability/immutability are more about whether the instances are immutable and not the type objects themselves - for example list vs tuple where tuple has __new__ but no __init__. Based upon the behavior I'm seeing it seems to me that the presence of __new__ / __init__ must be getting cached somewhere and the deletion isn't updating the cache and that's specifically what struck me as odd here.
Dino Viehland wrote:
Based upon the behavior I'm seeing it seems to me that the presence of __new__ / __init__ must be getting cached somewhere and the deletion isn't updating the cache and that's specifically what struck me as odd here.
Digging through typeobject.c, it isn't clear to me why this wouldn't be getting picked up. If all is working correctly: 1. When the class is created, tp_new and tp_init are copied from the base class (see inherit_special and inherit_slots) 2. When object_new and object_init are called they compare the values stored in the tp_new and tp_init slots with their own function addresses to decide whether or not to raise the warning 3. When either of those is deleted, the update_slots call in type_setattro should fix up the slot inheritance, and hence affect future calls to __new__ and __init__ That said, the following comments in update_one_slot() are leading me to wonder if Guido might owe Dino a beer: { /* The __new__ wrapper is not a wrapper descriptor, so must be special-cased differently. If we don't do this, creating an instance will always use slot_tp_new which will look up __new__ in the MRO which will call tp_new_wrapper which will look through the base classes looking for a static base and call its tp_new (usually PyType_GenericNew), after performing various sanity checks and constructing a new argument list. Cut all that nonsense short -- this speeds up instance creation tremendously. */ specific = (void *)type->tp_new; /* XXX I'm not 100% sure that there isn't a hole in this reasoning that requires additional sanity checks. I'll buy the first person to point out a bug in this reasoning a beer. */ } I *think* this logic may be getting confused when update_one_slot() is called immediately after the "del x.__new__" call has stuffed a NULL into the tp_new slot. In the normal class creation case the tp_new method will have been copied down from the parent class so the conditions when update_one_slot() gets called are different (ditto for when you actually *set* x.__new__ rather than delete it). If nobody else gets anywhere with this I should have a chance to actually play with it (rather than just reading code) tomorrow evening. Cheers, Nick. -- Nick Coghlan | ncoghlan@gmail.com | Brisbane, Australia ---------------------------------------------------------------
participants (3)
-
Benjamin Peterson
-
Dino Viehland
-
Nick Coghlan