PyObjC
November 21, 2003
I tried out PyObj-C last night. PyObjC is a language binding/module that lets you use Python with Cocoa - somewhat like how AppleScript Studio lets you use AppleScript to write your Cocoa program.
Except PyObjC makes AppleScript Studio look like Apple took the worst bits of VisualBasic, layed a verbose language on top of it, and called it good. I’m so very impressed by PyObjC. I used InterfaceBuilder just like I would if I was writing a Cocoa application in Objective-C - defining subclasses, instantiating them, linking them up to actions - and your Python classes are called just as if they had been Objective-C classes.
In Objective-C/PyObj-C Cocoa, interface elements such as windows, menus, etc, live in .nib files. Except nibs don’t just contain interface element definitions - they also contain objects and their connections. Meaning that you can subclass an object (NSObject), create an action, and define things like “when that object over there is triggered, do this method”. These subclasses are “freeze-dried” (similar to pickled objects, if you’re familiar with the Python terms) in the nib file, and created when your application is launched. There are plenty of frameworks that let you create “views” where you define placements of objects, but very very few that let you establish those types of connections.
Applescript Studio takes a very different approach. You drag out your element in your nib, get info on it, name your object, define what script it belongs to, and check which actions that you want to handle. Compared to how Objective-C/PyObjC handles all of this, this method feels very slow (and very different). Part of this probably has to do with AppleScript’s poor object-oriented support.
Back to PyObjC. So, quick as a whistle I drew up a little controller class. Take a look at this:
class MyController(NibClassBuilder.AutoBaseClass, NSApplicationDelegate): """ Note that by inheriting from AutoBaseClass, PyObjC will automatically define MyController based on the definition of MyController in MainMenu.nib. The inheritance of this class will be determined by the class definition in the NIB and all outlets will automatically be defined. """ def doAction_(self, sender): """ An example of a standard target action method implementation. """ hexValue = self.hexValueEdit.stringValue() myVal = int(hexValue, 16) self.decValueEdit.setStringValue_(myVal)
This code translates a hex number (typed into an edit field) into a decimal number.
Let’s look at some of the more interested parts of this code:
def doAction_(self, sender):
The sender variable will be an Objective-C class (in this case, an NSTextField). You use this exactly like you would use it in Objective-C - all the functions are Cocoa functions.
In AppleScript Studio, this is not true at all. You get an AppleScript object passed as your parameter, and you have to talk to these in a totally different (but wholly AppleScript) way. In one way, if I’m in Applescript, I want to think in Applescript. In another way, it means that knowing Cocoa doesn’t mean you know AppleScript Studio.
hexValue = self.hexValueEdit.stringValue()
The careful reader says “hey, what’s hexValueEdit?” just about now. It’s an outlet I defined in my nib file - through an outlet I can talk to other elements in the nib. In this case, hexValueEdit is the text field where the user has typed in a hexidecimal number, waiting patiently for it to be translated into decimal format.
Also notice the stringValue() method. stringValue() is an instance function of NSTextField objects in Cocoa. Let me repeat: This is not something provided by PyObjC - it is provided by Cocoa. Stop and think about this - I’m using Python to do this. Cocoa only supports Objective-C and Java (and AppleScript) - but I’m calling Objective-C methods from Python. Amazingly cool.
self.decValueEdit.setStringValue_(myVal)
Again, notice the decValueEdit object. This is the text field where I display the results to the user. Notice the method it calls.
setStringValue_() ?
This is slightly annoying (and it took me a good half hour of fighting before I figured it out), but it’s caused by Objective-C.
See, in Objective-C, the function call would be:
[decValueEdit setStringValue: myVal];
The colon means “a variable comes after this”. There are also more complex calls such as:
[RWError errorWithString: myString going:kToFile to:filepath];
Notice that, unlike C/C++, Objective-C uses labeled parameters separated by colons. Update 12-01-03: Sort of anyway. See the comments by Bill Bumgarner (also) linked to at the bottom of the page
Update 12-01-03: See the RWError page for more about this Objective-C class
In PyObjC you would call the same function like this RWError.errorWithString_going_to_(myString, kToFile, filepath)
Notice how the labels are now in the function name itself, and separated by underscores instead of colons. (The underscores vs colons issue makes sense - colons are syntax elements in Python.)
Now Python does labelled parameters too, so I don’t understand the reasoning behind having the labels in the function name syntax. Wouldn’t something like the following be closer to what you get with Objective-C?
RWError.errorWithString(myString, going = kToFile, to= filepath)
I don’t know. Maybe they implemented it this way because of speed issues (although a transformation like that shouldn’t take much time).
Yet using Cocoa with Python is simple - if you know how to use Cocoa with Objective-C, you know how to use Cocoa with Python. Very little extra knowledge (other than knowing the language) is required. Compare this to the Using Cocoa The AppleScript Way you get with AppleScript Studio, where you have to deal with another whole API (it seems) to interact with your interface elements.
Last night I created a hexidecimal to decimal number converter application, because it was easy. I’d love to take PyObjC out for a larger project, to see what kind of benefits I would get from the Python vs Objective-C difference. Although they are both dynamically bound (for example, you can call a method in an object, and the compiler doesn’t care if that method exists or not -it may or may not warn you about it, but it won’t give an error), Objective-C has C’s syntax holding it back - semicolons, requirements of prototypes, etc. I would think that with Python, because you have less syntax to worry about, would be much faster to develop in than even Objective-C.
Again, I’m very much impressed at how much of the Cocoa paradigm is preserved by PyObjC. Very good work guys!
Update: 11-29-03: Edited for grammar, and clarified some Cocoa concepts, for those that come here with little background in that area.
Update 11-29-03: Bill Bumgarner explains more about labeled parameters (which, turns out, aren’t technically in either language). I’ve also added a link to my RWError Objective-C class.