Using decorators around PyObjC methods

Issue #143 resolved
Mateusz Mikusz
created an issue

Hi all,

First of all, thank you everyone for putting so much work into the project and keeping it going with the newest releases of OS X! I'd like to share my latest experiences on using decorators with PyObjC and I hope that this contributes to the project.

After porting an old project to the newest releases of Python and PyObjC I ran into some problems with using decorators. It appears that methods in classes that inherit from PyObjC classes can’t have *args and **kwargs as arguments (which would be necessary to write a method decorator).

You can reproduce it with the following code sample:

import AppKit
import objc


class RandomNSObject(AppKit.NSObject):
   def init(self):
       self = objc.super(RandomNSObject, self).init()
       return self

   def randomFunction_args_(*args, **kwargs):
       print(*args, **kwargs)

t = RandomNSObject.alloc().init()
t.randomFunction_args_("foo")

This code fails with the following error message: TypeError: Objective-C callable methods must take at least one argument. What I would expect from the above code sample (and the print statement inside randomFunction_args_) is to print out self and the string foo instead.

Now you may be wondering why it would be useful to be able to define functions with *args and **kwargs. The reason for that are Python decorators. If arguments and keyword arguments are not passed through dynamically, we are not able to write a wrapper around a PyObjC method that takes any arguments and passes there right through to the original method. With the newest version of PyObjC, I'm not able to use decorators anymore unless I specify the arguments in the decorator function explicitly (instead of using args and kwargs to make the decorator work with different kinds of functions). It used to work fine in the past (many years ago), so I believe the way how functions are translated into Obj-C must have changed somewhere on the way.

I am submitting this as a bug although I'm not sure if this really is a bug or maybe expected or even desired behavior. However, it doesn't allow to write code in a pythonic way if we are unable to use decorators.

Comments (6)

  1. Ronald Oussoren repo owner

    It should be possible to add support for this. I agree that it would be nice to be able to use generic decorators.

    Note that the following does work:

    class MyObject (NSObject):
      def method_(self, *args, **kwds): pass 
    
  2. Ronald Oussoren repo owner

    Well actually, the code I wrote above doesn't really work.... It turns out that this generates an Objective-C method that has no arguments because the argument count (excluding *args) is used to determine the amount of arguments the Objective-C selector should have.

    That's a bug in itself, the code should count the number of colons in the Objective-C selector.

  3. Ronald Oussoren repo owner

    This is a partial fix for this issue.

    The reason I haven't committed the patch yet is that there are some failing tests in pyobjc-core due to the change in how the default method signature is calculated.

    I think the new behavior is better, but the failing tests raise some backward compatibility concerns that I need to think about.

    Also: I haven't tested the patch beyond running the testsuite for pyobjc-core, at the very least this needs to be tested with the rest of the testsuite as well. I'd also prefer to test the impact on sample code included with PyObjC as well as other code using PyObjC (both public and private)

  4. Mateusz Mikusz reporter

    Thanks so much for your help with this! I will try out your patch on a simple example and also our more complex project and will let you know if/how it impacts other things.

  5. Ronald Oussoren repo owner

    Change calculation of default method signature to be more sane

    In previous releases the default Objective-C method signature was calculated from the Python method signarure. In this version the signature is calculated from the implied Objective-C selector.

    This makes it possible to use varargs in the defition of selectors:

    def someMethod_arg_(self, *args): ...

    This in turn makes it a lot easier to use generic decorators in Cocoa classes.

    This is a backward incompabible change, a number of patterns will cause an exception at class definition time instead of generating an Objective-C method signature that doesn't match the implied prototype of the Objective-C selector. An example of that:

    def myMethod_arg_(self, a): ...

    More importantly, this will cause problems when mixing plain python methods and Objective-C selectors in a class definition. The workaround is to add objc.python_method as a decorator for methods that are only called from Python:

    @objc.python_method def myhelper(self, a, b): ...

    Fixes #143

    → <<cset 8e018c9b9c0f>>

  6. Log in to comment