Embedded PyObjCVirgil Dupras2010-02-08 When people think of a PyObjC application, they usually think of a Python application that uses Objective-C libraries. However, it's also possible to do the opposite: An Objective-C application that embeds Python code through a plugin. Building an application this way has advantages (speed, integration and memory usage) and should be used more often. This article explains why and how to achieve this. Why?Speed. In a GUI application, there's usually many, many calls being made all the time by the different elements of the GUI. For example, I have no benchmarks to back this. All I can tell you is that my first PyObjC application, musicGuru, was initially all Python. Scrolling was sluggish and I decided to give the Embedded Way a try. It fixed the problem and I never looked back again. Integration. Although XCode and Interface Builder offer Python integration (and since I never used them, I don't know if it works well), there's no doubt that Apple's efforts are much more axed on Objective-C. Auto-completion, help, build process, these are all designed with Objective-C in mind. Memory usage. Since PyObjC 2.0, metadata from Apple's bridge support files are loaded in memory, leading to pretty high initial memory usage (as I mentioned in my article about 64-bit PyObjC applications). When you embed Python in your Objective-C application, it usually means that your Python code use fewer Objective-C classes, thus allowing you to use neat tricks to reduce that memory usage (more about this below). I'm talking about saving tens of MB of initial memory usage here, so it's not negligible. How?Update 2010-11-23: I published a project containing a minimal application with embedded PyObjC. You might want to take a look. The PyObjC website has an old tutorial about embedding Python already, but it unfortunately doesn't explain much besides the basics. One thing it doesn't explain (because all instantiations in the example take place in the NIB) is how to locate a class in a plugin, interface it and instantiate it. This is what I'm going to do right away with an example. Let's say that we want to build a PyObjC application embedding Python that simply displays a list of strings in a class Foobar(object): def __init__(self): self.strings = ['foo', 'bar', 'baz'] def count(self): return len(self.strings) def string_at_index(self, index): return self.strings[index] This is, of course, stupidly-engineered for the purpose of the example. The next thing we have to do is to create an interface that converts calls with Objective-C conventions to calls with Python conventions: import objc from Foundation import NSObject from foobar import Foobar class PyFoobar(NSObject): def init(self): self = super(PyFoobar, self).init() self.py = Foobar() return self @objc.signature('i@:') def count(self): return self.py.count() @objc.signature('@@:i') def stringAtIndex_(self, index): return self.py.string_at_index(index) The signature decorators are required so that PyObjC correctly converts Now that we have this, we're ready to build a plugin with py2app (the old tutorial explains how to do it). Use the interface python script as the "main plugin script" in the setup config. Once you have the plugin, you can now create your Objective-C project. The first thing you should do is to create a header file describing the interface of your Python classes: @interface PyFoobar : NSObject {} - (int)count; - (NSString *)stringAtIndex:(int)aIndex; @end Now comes the "magic" part where you instantiate your python classes in Objective-C. This is done through - (void)awakeFromNib { NSString *pluginPath = [[NSBundle mainBundle] pathForResource:@"your_plugin" ofType:@"plugin"]; NSBundle *pluginBundle = [NSBundle bundleWithPath:pluginPath]; Class pyClass = [pluginBundle classNamed:@"PyFoobar"]; py = [[pyClass alloc] init]; } - (void)dealloc { [py release]; [super dealloc]; } - (NSInteger)numberOfRowsInTableView:(NSTableView *)tableView { return [py count]; } - (id)tableView:(NSTableView *)tableView objectValueForTableColumn:(NSTableColumn *)column row:(NSInteger)row { return [py stringAtRow:row]; } You now have a working Objective-C application that embeds Python code! I wrote earlier that I'd talk about a neat trick to reduce memory usage. In the interface code, our However, all we use is import objc NSObject = objc.lookUpClass('NSObject') This class, however, will have none of its methods' metadata loaded. This is not a problem for usual methods (having only | |