Description
Bug report
The optimisation to LOAD_SUPER_ATTR introduced by #104270 appears to have introduced a backwards incompatibility.
The following code will reproduce the problem:
class MyInstance:
def __new__(cls, ptr, name, bases, attrs):
self = super().__new__(cls, name, bases, attrs)
print(f"{MyInstance=} {self=} {type(self)=}, {super(MyInstance, type(self))=}")
super(MyInstance, type(self)).__setattr__(self, "ptr", ptr)
return self
def __setattr__(self, name, value):
raise Exception()
class MyClass(MyInstance, type):
def __new__(cls, name):
self = super().__new__(cls, id(name), name, (MyInstance,), {})
return self
class1 = MyClass("Class1")
print(f"{class1.ptr=}")
class2 = MyClass("Class2")
print(f"{class2.ptr=}")
Under 3.12.0a7 (and previous stable Python versions going back to at least 3.7), this will succeed, outputting:
MyInstance=<class '__main__.MyInstance'> self=<class '__main__.Class1'> type(self)=<class '__main__.MyClass'>, super()=<super: <class 'MyInstance'>, <MyClass object>>
class1.ptr=4364457392
MyInstance=<class '__main__.MyInstance'> self=<class '__main__.Class2'> type(self)=<class '__main__.MyClass'>, super()=<super: <class 'MyInstance'>, <MyClass object>>
class2.ptr=4365761904
Under 3.12.0b1, it raises an error:
MyInstance=<class '__main__.MyInstance'> self=<class '__main__.Class1'> type(self)=<class '__main__.MyClass'>, super()=<super: <class 'MyInstance'>, <MyClass object>>
class1.ptr=4370144336
MyInstance=<class '__main__.MyInstance'> self=<class '__main__.Class2'> type(self)=<class '__main__.MyClass'>, super()=<super: <class 'MyInstance'>, <MyClass object>>
Traceback (most recent call last):
File "/Users/rkm/beeware/rubicon/objc/repro.py", line 24, in <module>
class2 = MyClass("Class2")
^^^^^^^^^^^^^^^^^
File "/Users/rkm/beeware/rubicon/objc/repro.py", line 16, in __new__
self = super().__new__(cls, id(name), name, (MyInstance,), {})
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/Users/rkm/beeware/rubicon/objc/repro.py", line 7, in __new__
super().__setattr__(self, "ptr", ptr)
TypeError: expected 2 arguments, got 3
That is - the first MyClass
instance can be instantiated; however, the second instance fails in mid-construction when invoking __setattr__
. The state of the objects prior to the __setattr__
invocation appear to be identical, but the __setattr__
method behaves differently on the second invocation.
Git bisect has narrowed the cause down to #104270 (CC @carljm, @markshannon).
The reproduction case also fails if the call to super
:
super(MyInstance, type(self)).__setattr__(self, "ptr", ptr)
is replaced with the simpler:
super().__setattr__(self, "ptr", ptr)
Background
This test case has been extracted from rubicon-objc, causing beeware/rubicon-objc#313. Rubicon is a wrapper around the Objective C runtime used by macOS and iOS; MyInstance
is an analog of ObjCInstance
, and MyClass
is a an analog of ObjCClass
. ObjCInstance
has an implementation of __setattr__
to redirect attribute access to the underlying ObjC calls. However, during construction, ObjCInstance
needs to store a ptr
of the underlying ObjC instance. This isn't a valid ObjC attribute, so super()
is used to access the underlying __setattr__
implementation to set the attribute.
Your environment
- CPython versions tested on: 3.7.9, 3.10.11, 3.12.0a7, 3.12.0b1
- Operating system and architecture: macOS ARM64