Closed typemytype closed 1 year ago
You must import objc.super
using from objc import super
to reliably use super with Cocoa classes.
I see I have to change something in the documentation, this should be mentioned in PyObjC's introduction but it isn't.
Background: PyObjC's proxy for Cocoa classes dynamically looks for methods as needed, both for performance reasons and because Cocoa classes can change at runtime without a way to cheaply detect this. Because of this the __dict__
of those Cocoa proxy classes tend to be incomplete. The implementation of builtin.super
assumes that the __dict__
is always up-to-date, and hence can fail when using Cocoa classes.
I've written a PEP that tries to fix this PEP 447, but now think this is too complex and that a better solution is possible. I haven't gotten around to fully flesh out a proposal for that though (and in any case, such a proposal would end up in Python 3.13 at the earliest).
Can objc.super
be reliably used for non Cocoa classes, too? That is, can we do from objc import super
in modules that use super()
for both Cocoa classes and ordinary Python classes?
this still fails with pyobjc 9.1.1 and objc.super()
import AppKit
import objc
print(objc.__version__)
class Sub(AppKit.NSObject):
def init(self):
print("init")
self = objc.super().init()
return self
s = Sub.alloc().init()
self = objc.super().init()
^^^^^^^^^^^^
RuntimeError: super(): __class__ cell not found
sys:1: UninitializedDeallocWarning: leaking an uninitialized object of type Sub
Can
objc.super
be reliably used for non Cocoa classes, too? That is, can we dofrom objc import super
in modules that usesuper()
for both Cocoa classes and ordinary Python classes?
Yes, you can. objc.super
is a subclass of builtin.super
that overrides the attribute resolution with some special casing for Cocoa classes and behaves like the builtin super class otherwise.
this still fails with pyobjc 9.1.1 and
objc.super()
import AppKit import objc print(objc.__version__) class Sub(AppKit.NSObject): def init(self): print("init") self = objc.super().init() return self s = Sub.alloc().init()
self = objc.super().init() ^^^^^^^^^^^^ RuntimeError: super(): __class__ cell not found sys:1: UninitializedDeallocWarning: leaking an uninitialized object of type Sub
That's because you really have to use from objc import super
to be able to use the zero-argument form of super. Python's compiler special-cases code generation for zero-argument calls to super()
, and not for other forms. That's the cause of the error.
import AppKit
import objc
from objc import super
print(objc.__version__)
class Sub(AppKit.NSObject):
def init(self):
print("init")
self = super().init()
return self
s = Sub.alloc().init()
thanks for the explanation!
In my case, this was baffling to discover, because it looked like it worked correctly locally in local & alias builds for me, but then failed in release builds. I wonder if it would be possible to at least have a linter for this somehow, if a clearer error when using the wrong super()
is not possible?
In my case, this was baffling to discover, because it looked like it worked correctly locally in local & alias builds for me, but then failed in release builds. I wonder if it would be possible to at least have a linter for this somehow, if a clearer error when using the wrong
super()
is not possible?
I'm afraid not. builtin.super
walks the MRO and pokes directly into the class __dict__
with PyDict_*
APIs. This means a class cannot observe that this happens.
I've considered changing the type of the __dict__
attribute to one with a __missing__
implementation but AFAIK that's not something supported by CPython. A longer term solution requires changes to CPython itself. My current idea is PEP 447 is too invasive and to add a __super__
method instead, but haven't had time yet to fully consider the impact of this, let alone write a PEP.
A linter is possible, but not something I can work on in the short term.
There now is some documentation. Leaving the issue open for now to look into linter possibilities.
Hmmm...
Because of the way calls to argument less super is implemented in CPython it might be possible to detect this at runtime. In particular: there will be a __classcell__
key in the class dict when one or more methods use argument less super. The hard part is to reliably detect what super
binds to in the class.
Something that could be good enough (in objc._transform.processClassDict
):
if "__classcell__" in class_dict:
if "__module__" in class_dict:
mod = sys.modules[class_dict["__module__"]]
if not hasattr(mod, "super") or mod.super is not objc.super:
warnings.warn("Objective-C subclass uses super() but super is not objc.super", ...)
Probably with configuring the used warning to always report. The code above is completely untested at this time.
import objc
NSObject = objc.lookUpClass("NSObject")
class MyObject(NSObject):
def init(self):
self = super().init()
if self is None:
return None
self.a = 42
return self
$ python3 a.py
/a.py:5: ObjCSuperWarning: Objective-C subclass uses super(), but super is not objc.super
class MyObject(NSObject):
Changeset d6e9b63 warns about using the wrong super
at runtime.
That's not entirely ideal because the warning will mostly be invisible when using a tool like py2app to create a standalone application, but makes it a lot easier to detect the issue (and this also found a recent bug in the PyObjC testsuite itself...)
super! thanks
Describe the bug
Calling
super()
in a subclasses of NSObject (like a NSView) triggers an error.Platform information
this worked fine in pyobjc 9.0.1
thanks!!