Search code examples
pythonpyobjc

How do I pass an Objective C instance into a PyObjC Python function when using PyRun_File()/PyObject_CallFunction()?


I'm embedding Python in an Objective C application using PyObjC, setting the Python environment up by hand on the ObjC side (i.e. not using py2app). I'm starting the Python in a separate thread (on the ObjC side) with a call to PyRun_File(), and then one to PyObject_CallFunction() to start it doing something (specifically update some menus, register and handle menu callbacks). The Python function starts the run loop for the secondary thread and the callback class instance hangs around. All of this works. I can pass basic data such as strings to the initial Python function without problem and menu actions work as I'd like.

I'd like to provide a pre-instantiated delegate instance to the Python function for ease of configuration (from the point of view of the Objective C developer). How do I pass an Objective C instance (my delegate) to the Python function? Is it possible? Are there bridge functions I'm missing? Do I need to create a suitably configured PyObject by hand? What sort of conversion should I do on the Python side to ensure the delegate.methods() are usable from Python and proxying works as it should? Are there any memory-management issues I should be aware of (on either side of the bridge)?

I'm using Python 2.7, PyObjC 2.5 and targeting OSX 10.6. Happy to consider changing any of those if the solution specifically demands it. TIA.


Solution

  • The easiest way to ensure problem free usage of the bridge is to ensure that the delegate methods that you use don't use C arrays as arguments or return values and don't use variadic signatures ("..."). Futhermore ensure that all pass-by-reference arguments (such as the commonly used "NSError**" argument) are marked up with "in", "out" or "inout" to indicate in which direction values are passed. This ensures that the bridge can get all information it needs from the Objective-C runtime.

    There are two options to pass a preconstructed object to Python code:

    1. Create a class method that returns the object

    2. Use the PyObjC API to create the python proxy for the Objective-C object.

    The latter uses an internal PyObjC API (also used by the framework wrappers) and could break in future versions of PyObjC. That said, I don't have active plans to break the solution I describe here.

    First ensure that the right version of "pyobjc-api.h" and "pyobjc-compat.h" are available for the Objective-C compiler.

    Use #include "pyobjc-api.h" to make the API available.

    Call "PyObjC_ImportAPI" after initialising the Python interpreter, but before you use any other PyObjC function.

    Use "pyValue = PyObjC_IdToPython(objcValue)" to create a Python representation for an Objective-C object.