Forum Archive

Python Objective-C Bridge

snaggled

Hi

First of all, great product!

I'm pretty sure I can call back into Objective-C by writing a statically compiled python library with the functions I want in it (I can see examples of this in places like scene.py and canvas.py (e.g. import _canvas)). BTW, an example would be great, since those libs are compiled into the libpythonista.a lib I can't see what they look like, but I can probably figure it out.

My question is - is the reverse possible ? I want to call a python function from Objective-C. In the PyObjC world I would have imported objc and then created a python subclass of a NSObject. I was hoping you could provide any pointers on how I would go about this.

Also, how would global variables work ? If I were to call a python function and it declared a globalvariable then returned and then I was to call another function that used the same global variable, would it be available ? Would python keep its memory space around between calls ?

Any info you have would be much appreciated.

omz

The 'native' modules of Pythonista basically work like this:

→ Python documentation: Extending Python with C or C++

If you use the Xcode template, it would generally be possible to add additional native functionality that is accessible from Python in the same way, but it would NOT be possible to add such functionality to the Pythonista app itself.

As an example, this is an abridged and simplified version of the _clipboard module:

//clipboardmodule.m

#import "Python.h"

PyObject* clipboard_get(PyObject* self, PyObject* pArgs)
{
    NSString *stringValue = [[UIPasteboard generalPasteboard] string];
    if (!stringValue) {
        return PyUnicode_FromString("");
    }
    return PyUnicode_FromString([stringValue UTF8String]);
}

static PyMethodDef clipboardMethods[] = {
    {"get", clipboard_get, METH_NOARGS, "get() -- Get the clipboard's text content as a string"},
    // [...]
    {NULL, NULL, 0, NULL} //sentinel
};

PyMODINIT_FUNC init_clipboard(void)
{
    Py_InitModule("_clipboard", clipboardMethods);
}
snaggled

This is excellent. Thankyou!

gyronaut

Sorry for the naive question, but I :
- Included the “Python.h”- directory to the header include option
- Implemented such a coding like the clipboard example , called the module “_sensors”
Compiling and linking was done without any errors.
Then I added “import sensors” to my main.py file and got the error during runtime that the module is not defined.
What is missing ?

For any suggestions grateful
Stefan

dgelessus

Did you write a corresponding sensors.py file? If you implement _sensors in C (or in Python, it doesn't really matter) that doesn't automatically create a sensors module. The point of having a native C module and a separate Python wrapper is so you can write parts of your library in Python, and so you can change the C interface without worrying about breaking user code. (Of course you'd still need to provide some backwards compatibility in the Python module.) The C library is still normally available to Python code as _sensors, the underscore is used as a convention to indicate that it is a "private" library that external code should not depend on.

gyronaut

Thanks, for the reply, but it's still not working.

This my test coding of _sensor.m :

//
//  _sensor.m
//  PilotsApp
//
//
//

#import <Foundation/Foundation.h>
#import "Python.h"

PyObject* sensor_get(PyObject* self, PyObject* pArgs)
{
    NSString *stringValue = @"done";
    return PyUnicode_FromString([stringValue UTF8String]);
}

static PyMethodDef Methods[] = {
    {"get", sensor_get, METH_NOARGS, "get() -- Get the clipboard's text content as a string"},
    // [...]
    {NULL, NULL, 0, NULL} //sentinel
};

PyMODINIT_FUNC init_sensor()
{
     Py_InitModule("_sensor", Methods);
}

Then I implemented the file sensor.py :

from _sensor import *

And extacly this statement ends in "No module named _sensor"

Do I have to change any compiling or linking flags ?

omz

You have to "register" the module with the Python interpreter during startup, e.g. by calling PyImport_ExtendInittab.

This has to happen before the interpreter is initialized, e.g. in applicationDidFinishLaunching: (AppDelegate).

gyronaut

Wow. It works. Thanks, Ole!

The coding changes are indicated by ">>> Bridge":

//
//  AppDelegate.m
//  Pythonista
//
//  Created by Ole Zorn on 1/19/15.
//
//

#import "AppDelegate.h"
#import "PythonistaAppViewController.h"
// >>> Bridge
#import "Python.h"
// <<< Bridge
PyMODINIT_FUNC init_sensor(void);

@implementation AppDelegate

- (void)applicationDidFinishLaunching:(UIApplication *)application



{   // >>> Bridge
    struct _inittab pyqt_inittab[] = { {"_sensor", init_sensor},{0,0}};
    PyImport_ExtendInittab( pyqt_inittab);
    // <<< Bridge
    NSFileManager *fm = [NSFileManager defaultManager];
    NSString *docPath = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) firstObject];
gyronaut

Wow. It works. Thanks, Ole!

The coding changes are indicated by ">>> Bridge":

//
//  AppDelegate.m
//  Pythonista
//
//  Created by Ole Zorn on 1/19/15.
//
//

#import "AppDelegate.h"
#import "PythonistaAppViewController.h"
// >>> Bridge
#import "Python.h"
// <<< Bridge
PyMODINIT_FUNC init_sensor(void);

@implementation AppDelegate

- (void)applicationDidFinishLaunching:(UIApplication *)application



{   // >>> Bridge
    struct _inittab pyqt_inittab[] = { {"_sensor", init_sensor},{0,0}};
    PyImport_ExtendInittab( pyqt_inittab);
    // <<< Bridge
    NSFileManager *fm = [NSFileManager defaultManager];
    NSString *docPath = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) firstObject];