Repository navigation
3.12.0b4 Backwards incompatible change with reassignment of cls.__new__ and super() #106917
Description
Activity
- addedtype-bugAn unexpected behavior, bug, or errorAn unexpected behavior, bug, or error
on Jul 20, 2023 - added3.12only security fixesonly security fixes3.13only security fixesonly security fixes
on Jul 20, 2023 Thanks for this report!
__new__is automagically treated as a staticmethod (but only when it's part of the type at construction, not if it's assigned afterward.) So the repro case ultimately simplifies and de-sugars to this:class A: @staticmethod def some(cls, *args, **kwargs): print(f"{cls}, {args=}") assert not args assert not kwargs class B(A): def some(cls, *args, **kwargs): return super().some(cls, *args, **kwargs) class C(B): @staticmethod def some(cls): return super().some(cls) C.some(C) C.some(C)Note that
B.someis not a@staticmethod, whileA.someandC.someboth are. This is equivalent to what happens in the OP. The assignmentcls.__new__ = cls.__new__looks up the staticmethod in the right hand side (from the base class via MRO lookup, since it doesn't exist yet oncls) and "unwraps" the staticmethod in descriptor resolution, returning a plain function object, and then assigns that plain function object (not a staticmethod) as the__new__method on the new subclass. (And dynamic assignment of__new__does not magically wrap it instaticmethod.) So the middle class in the hierarchy (uint_tin the OP) ends up with a non-staticmethod__new__, while the top and bottom classes have a normal staticmethod__new__.This simplified and de-sugared repro works in 3.11, but fails in a similar way to the OP in main and 3.12:
➜ ../dbg/python superbug3.py <class '__main__.C'>, args=() <class '__main__.C'>, args=(<class '__main__.C'>,) Traceback (most recent call last): File "/home/carljm/cpython-builds/repros/superbug3.py", line 18, in <module> C.some(C) File "/home/carljm/cpython-builds/repros/superbug3.py", line 15, in some return super().some(cls) ^^^^^^^^^^^^^^^^^ File "/home/carljm/cpython-builds/repros/superbug3.py", line 10, in some return super().some(cls, *args, **kwargs) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/home/carljm/cpython-builds/repros/superbug3.py", line 5, in some assert not args AssertionErrorThe double call to
C.some(C)in the repro is necessary so that we specializeLOAD_SUPER_ATTRtoLOAD_SUPER_ATTR_METHOD, triggering the bug. The baseLOAD_SUPER_ATTRgoes through the same code path as an unoptimizedLOAD_ATTR; CALL.What we have in these examples is a "class-mode" super() call (e.g.
super(MyClass, MyClass).foo(...), where the second super arg is also a class, not an instance). (Zero-arg super() implicitly uses the first argument of the method as the second arg to super(), and in this case that first arg of the method is a class, not an instance.)In a class-mode super() call (just like for a normal lookup directly on a class), we do not pass an instance to the descriptor-getter, only a type (e.g.
func_descr_get(descr, NULL, type)instead offunc_descr_get(descr, obj, type). In the case of a regular function object, this causes the function descriptor to just return the function itself (no bound method), which is the same behavior the staticmethod descriptor always has! So as long as we are only doing class lookups, staticmethod has no effect, and therefore we can get away with this mixed staticmethod/non-staticmethod hierarchy just fine.The bug here is that the
LOAD_SUPER_ATTR_METHODspecialization tries to use theLOAD_METHODoptimization (avoid actually creating a bound method object via descriptor, and instead just detect that case and ensure the extra argument is provided), but fails to account for the fact that a class lookup of a regular function object (not a classmethod) is just a function call, not a method call.Fix coming shortly.
Reacted by Marc Mueller- addedinterpreter-core(Objects, Python, Grammar, and Parser dirs)(Objects, Python, Grammar, and Parser dirs)
on Jul 21, 2023 - linked a pull request that will close this issue[3.12] gh-106917: fix super classmethod calls to non-classmethods (GH-106977). #107204
on Jul 24, 2023 - added a commit that references this issue
on Jul 24, 2023
Bug report
Noticed this while testing
3.12.0b4. While in my particular case I can work around it, it never the less is a change in behavior to3.11.Without
cls.__new__ = cls.__new__I bisected the issue to #103497.
/CC: @carljm
Your environment
3.120b4Linked PRs