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

Problem overwriting objc methods in pyobjc 8.5 #479

Open
schriftgestalt opened this issue Jun 29, 2022 · 11 comments
Open

Problem overwriting objc methods in pyobjc 8.5 #479

schriftgestalt opened this issue Jun 29, 2022 · 11 comments
Labels
bug Something isn't working

Comments

@schriftgestalt
Copy link

If I do something like this:

GSHint = objc.lookUpClass("GSHint")
GSHint.isCorner = property(lambda self: self.pyobjc_instanceMethods.isCorner())

it throws this:

AttributeError: Cannot replace selector 'isCorner' in 'GSHint' by non-selector

(the class GSHint is defined in the ObjcC part of the app)

This was working fine in pyobjc 8.4 and before.

@schriftgestalt schriftgestalt added the bug Something isn't working label Jun 29, 2022
@ronaldoussoren
Copy link
Owner

Ah, that's annoying. Being able to replace methods like this was unintentional and fixed during some cleanup work.

However, as this breaks code I'll revert this change.

@schriftgestalt
Copy link
Author

schriftgestalt commented Jul 4, 2022

Is there a better way to declare a property for ObjectiveC properties? In most cases this replacement is only used to remove the (). I like to provide a clean, python-like API.

And if you can’t replace methods, why is there pyobjc_instanceMethods?

@ronaldoussoren
Copy link
Owner

IIRC pyobjc_instanceMethods is mostly there to be able to access instance methods through classes, but I may have had other reasons to add it. I mostly forbade assigning a new value to a selector to avoid accidentally hiding them.

I don't have a fully fleshed out plan yet for exposing properties as properties, especially not in cases like this where the name of the getter method is the same as the property name. That's another reason for reverting the change in 8.5, I don't have a good alternative for your use case.

I am thinking about generating more Pythonic names in the tooling I use for the framework bindings, and that would include exposing properties as properties to Python. I haven't decided yet if I'll to the whole way to PEP8 compliant names, or if I'll keep using camelCase. Even with PEP8 compatible names I'd still have no good solution for single word names (such as NSObject.description).

And maybe I'll just bit the bullet and add a flag to PyObjC 9 to optionally expose properties as properties, with the goal to deprecate using methods instead of properties in some future version.

@ronaldoussoren
Copy link
Owner

I am thinking about generating more Pythonic names in the tooling I use for the framework bindings, and that would include exposing properties as properties to Python. I haven't decided yet if I'll to the whole way to PEP8 compliant names, or if I'll keep using camelCase. Even with PEP8 compatible names I'd still have no good solution for single word names (such as NSObject.description).

BTW. I haven't created an issue for this yet, I'll do some once I get closer to seriously think about my options here. I don't want to start a discussion before I'm ready to do something with the result.

@schriftgestalt
Copy link
Author

schriftgestalt commented Jul 4, 2022

Or add some configuration file that can map from (objc) getter/setter to python properties. Then I can decide how my properties look (if PEP8 or not). And that could also hold the doc string.
That also could be used to have nicer method names for longer selectors: NSColor.colorWithRed_green_blue_alpha_() > NSColor.rgba(red=1, green=1, blue=1, alpha=1)

This would be a manual process and only be done for frequently used methods. And the original selector would still be there to not break old code.

@ronaldoussoren
Copy link
Owner

Or add some configuration file that can map from (objc) getter/setter to python properties. Then I can decide how my properties look (if PEP8 or not). And that could also hold the doc string. That also could be used to have nicer method names for longer selectors: NSColor.colorWithRed_green_blue_alpha_() > NSColor.rgba(red=1, green=1, blue=1, alpha=1)

This would be a manual process and only be done for frequently used methods. And the original selector would still be there to not break old code.

The last part (add nicer method names) is already possible. I have plans to do this comprehensively for all framework bindings, that "just" requires a block of time to write and fine tune the scripting for this.

The problematic part is exposing ObjC properties as such in Python because of the most part the name of the property is the same as the name of the getter method and that won't work in Python. I might just accept breakage here and try to write tooling to help with the transition.

@schriftgestalt
Copy link
Author

You could implement __call__ on those properties and just return the value. Then you both, obj.someProperty (new) and obj.someProperty() (old) would work.

If you can tell me what needs to be done, I can find someone to help with the implementation.

@ronaldoussoren
Copy link
Owner

That requires creating subclasses of all values that might be the value of a property. I would already have done this if it were that easy...

To ease the discussion I'm using a boolean property below:

class NSControl (...):
    @property
    def enabled(self):
          return self.isEnabled()

     @enabled.setter
     def enabled(self, value):
         self.setEnabled_(value)

For some control c, the value c.enabled is whatever is returned from the getter, that is the return value from self.isEnabled(). To be able to __call__ it would be necessary to implement a subclass of (in this case) bool that implements __call__ as def __call__(self): return self. And that for every class that might be returned (could be done dynamically).

The really annoying part is Cocoa also has properties where the value is an Objective-C block, whose Python proxy already has an __call__ implementation (that will invoke the block).

Anyways, my focus at the moment is split between #94 and adjusting for the latest SDK beta (for PyObjC 9), and on this issue and #489 for an 8.5.1 release (hopefully during the weekend).

@schriftgestalt
Copy link
Author

I see. Not a good idea, then.

Do you know when you can fix the original issue?

@ronaldoussoren
Copy link
Owner

My plan is to do a release with a fix for this issue during the weekend. This depends a little on how warm my home office gets, its a bit warm at the moment due to a heat wave.

@schriftgestalt
Copy link
Author

thanks

ronaldoussoren added a commit that referenced this issue Aug 12, 2022
This was possible in older versions of PyObjC and
changed in some cleanup work in 8.5. Revert to the
old behaviour because this breaks applications.
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

2 participants