Calling Python from Objective-C

Every six months or so, I run across a question along the lines of how do I invoke some Python code from Objective-C?

Kinda like here for which I posted the same conceptually concrete but technically vague pattern that I have posted for the last decade+.

Which led to this question.

OK — enough is enough. Here is a working example.Specifically, you’ll want to grab the source:

svn co http://svn.red-bean.com/bbum/trunk/PyObjC/CallPythonFromObjectiveC/

You should be able to open the Xcode Project contained within, build it, and then (on Snow Leopard, anyway):

python call-python-from-objc.py /path/to/built/AbstractClassDefinition.bundle

And it’ll spew something like:

creating Bob
returning new instance <Concrete 4346092816 named Bob>
2009-11-20 22:44:49.485 Python[41780:d07] Created instance: <Concrete 4346092816 named Bob>
factor will be 1000000000
2009-11-20 22:44:49.486 Python[41780:d07] Set the fudge factor. Gonna do it up, now.
doing it returning 927.3
Did it! 1000000000 927.299988

The Xcode project produces an NSBundle that contains two unrelated pieces of source; an Abstract Class that is subclassed by python to provide the binding from Objective-C into Python and a class that provides an Objective-C method invoked by the python script to demonstrate calling from Obj-C into Python.

The Abstract Class is the first piece of the puzzle. It is declared as:

@interface AbstractClass : NSObject {
    NSString *name;
}
@property(copy) NSString *name;
@property unsigned long long fudgeFactor;
+ namedInstance: (NSString *) aName;
- (float) doItToIt: (BOOL) fastFlag;
@end

That is, a pretty straightforward (and totally contrived) Objective-C class. The Abstract part is that this class is not designed to be directly instantiated. Instead, it is too be subclassed and the subclass is the bit that is instantiated, as is indicated by the implementation:

void SubclassResponsibility(id classOrInstance, SEL _cmd) {
    NSString *reason = [NSString stringWithFormat: @"Must subclass %s and override the method %s.",
       object_getClassName(classOrInstance), sel_getName(_cmd)];
    @throw [NSException exceptionWithName: @"SubclassResponsibility"
       reason: reason
       userInfo: nil];
}
@implementation AbstractClass
@synthesize name;
- (void) setFudgeFactor:(unsigned long long) aShort
{
    SubclassResponsibility(self, _cmd);
}
- (unsigned long long) fudgeFactor
{
    SubclassResponsibility(self, _cmd);
    return 0;
}
+ namedInstance: (NSString *) aName
{
    SubclassResponsibility(self, _cmd);
    return nil; // not reaached
}
- (float) doItToIt: (BOOL) fastFlag;
{
    SubclassResponsibility(self, _cmd);
    return 0.0; // not reached
}
@end

Note that the Subclass Responsibility pattern used to be quantified in the Pre-OpenStep version of the what-is-now-called-Cocoa APIs. Object — the then NSObject — actually declared a -subclassResponsibility:(SEL) method!

The call-python-from-objc.py does the following:

1. Loads the bundle containing the implementation of AbstractClass:

bundle = Foundation.NSBundle.bundleWithPath_(bundlePath)
if not bundle.principalClass():
    print "%s: failed to load bundle." % sys.argv[0]

2. Creates a subclass of AbstractClass:

AbstractClass = objc.lookUpClass("AbstractClass")
class ConcreteClass(AbstractClass):
    _fudgeFactor = 0
    # factory method
    @classmethod
    def namedInstance_(self, aName):
        print "creating %s" % aName
        newInstance = ConcreteClass.new()
        newInstance.setName_(aName)
        print "returning new instance %s" % newInstance
        return newInstance
    # fudgeFactor property stored in Python subclass
    def fudgeFactor(self):
        return self._fudgeFactor
    def setFudgeFactor_(self, aFactor):
        print "factor will be %s" % aFactor
        self._fudgeFactor = aFactor
    # the method that does it all, so to speak
    def doItToIt_(self, fastFlag):
        if fastFlag:
            returnValue = 516.295
        else:
            returnValue = 927.3
        print "doing it returning %s" % returnValue
        return returnValue
    # Describe instances more pythonically
    def description(self):
        return "" % (id(self), self.name())

3. Invoke a random bit of Python code that calls an Objective-C method that calls back into Python

objectiveCCode = objc.lookUpClass("ObjectiveCCallingPython")
print objectiveCCode.callSomePython()

More specifically, the class ObjectiveCCallingPython exists solely as a demonstration of calling from Objective-C into Python. It happens to be implemented in the same bundle as AbstractClass, but it doesn’t need to be.

The callSomePython method looks like:

+ (NSString *) callSomePython;
{
    Class concreteClass = NSClassFromString(@"ConcreteClass");
    AbstractClass *concreteInstance = [concreteClass namedInstance: @"Bob"];
    NSLog(@"Created instance: %@", concreteInstance);
    concreteInstance.fudgeFactor = 1000000000;
    NSLog(@"Set the fudge factor. Gonna do it up, now.");
    float value = [concreteInstance doItToIt: NO];
    return [NSString stringWithFormat: @"Did it! %llu %f", concreteInstance.fudgeFactor, value];
}

Straightforward; grab the concrete subclass defined in Python, instantiate, configure, then tell it to do something.

The key is use Objective-C as the bridge between the compiled language (C/C++/Objective-C) and the interpreted language (this same pattern will work for other bridges). The easiest way to do that is to define an abstract class in Objective-C that is then subclasses in the interpreted language. From the compiled code, the abstract class acts as the source for all of the compiled type information the compiler needs. From the interpreted code, the abstract class’s stub method implementations provide the runtime metadata necessary to allow the interpreter to correctly convert between compiled and interpreted data types.

Or, a specific example, the fudgeFactor property has stub method implementations that enforce subclass responsibility. Beyond that, the presence of said methods ensure that the Python implemented method implementations contain the correct metadata such that the doesn’t-fit-in-an-id type of the argument to the setter and return value of the getter is correctly handled.

There are about a bazillion different variations possible, including eliminating the Abstract class. Ultimately, the key to success is ensuring that both sides of the bridge have all metadata necessary to work correctly. For the C/ObjC/C++ compiler, it needs the types of the arguments to correctly generate the call sites. For the interpreted language, it needs metadata describing the argumentation of the methods, whether provided automatically (in this case) or dynamically (see @objc.signature).



Leave a Reply

Line and paragraph breaks automatic.
XHTML allowed: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>