Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

AttributeError: 'super' object has no attribute ... #549

Closed
typemytype opened this issue Apr 21, 2023 · 14 comments
Closed

AttributeError: 'super' object has no attribute ... #549

typemytype opened this issue Apr 21, 2023 · 14 comments
Labels
bug Something isn't working

Comments

@typemytype
Copy link

Describe the bug

Calling super() in a subclasses of NSObject (like a NSView) triggers an error.

import AppKit

class Sub(AppKit.NSView):

    def viewDidMoveToWindow(self):
        super().viewDidMoveToWindow()

print("has viewDidMoveToWindow:", "viewDidMoveToWindow" in dir(AppKit.NSView))

s = Sub.alloc().init()
s.viewDidMoveToWindow()

Platform information

  • 3.9 - 3.11
  • pyobjc 9.1.1

this worked fine in pyobjc 9.0.1

thanks!!

@ronaldoussoren
Copy link
Owner

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).

@justvanrossum
Copy link

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?

@typemytype
Copy link
Author

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

@ronaldoussoren
Copy link
Owner

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?

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.

@ronaldoussoren
Copy link
Owner

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()

@typemytype
Copy link
Author

thanks for the explanation!

typemytype added a commit to robotools/vanilla that referenced this issue Apr 23, 2023
glyph added a commit to glyph/Pomodouroboros that referenced this issue Apr 25, 2023
@glyph
Copy link

glyph commented Apr 25, 2023

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?

glyph added a commit to glyph/QuickMacApp that referenced this issue Apr 25, 2023
@ronaldoussoren
Copy link
Owner

ronaldoussoren commented Apr 25, 2023

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.

@ronaldoussoren
Copy link
Owner

A linter is possible, but not something I can work on in the short term.

@ronaldoussoren
Copy link
Owner

There now is some documentation. Leaving the issue open for now to look into linter possibilities.

@ronaldoussoren
Copy link
Owner

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.

@ronaldoussoren
Copy link
Owner

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):

ronaldoussoren added a commit that referenced this issue May 7, 2023
This adds a new warning ``objc.ObjCSuperWarning`` and uses that
to warning about class definitions that use argumentless super
in Objective-C subclasses without binding ``super`` to ``objc.super``.
@ronaldoussoren
Copy link
Owner

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...)

@typemytype
Copy link
Author

super! thanks

ronaldoussoren added a commit that referenced this issue May 27, 2023
The metadata sanity check update from #554 found issues in other
bindings as well, this changeset fixes those issues.

Also fix some super usage issue in tests related to #549
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working
Projects
None yet
Development

No branches or pull requests

4 participants