Embedded PyObjC

Embedded PyObjC

Virgil Dupras
2010-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, NSTableView's datasource and delegate methods are called tons of times at each redraw. Each calls having to pass through the PyObjC bridge is inherently slower than a native call. While machines are usually fast enough for this not to be noticeable most of the time, there might be situations where it's not the case, scrolling a large list 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 aNSTableView. Let's first write our Python class:

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 ints. Methods that have nothing but NSObjectsubclasses as arguments or return values don't need any signature.

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 NSBundle. Let's imagine that we have a NIB-based NSWindowController with a table view that has itself as its datasource and aPyFoobar *py member. Its implementation would look like that:

- (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 Foundation import will cost about 13mb (in 32-bit) of memory usage. We can consider ourselves lucky that we embed Python, or else we'd need to import NSTableView from AppKit, which would increase memory usage even further.

However, all we use is NSObject and we don't call anything on it. Couldn't we find a way to avoid that Foundationimport altogether? The answer is yes. All you have to do 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 onlyNSObject arguments and return value), but for the rest of the methods, you'll need this metadata to properly call them. In this example, we don't need any metadata, but if you ever need it, know that it's possible to manually set this metadata without importing the memory-hungry Foundation and AppKit. I won't go into details here, but you can look at my own unit to see how it's done.

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值