Enterprise IT • 52:18
PyObjC is a bridge between Python and Objective-C. It allows you to write Python scripts that use and extend existing Objective-C class libraries, and most importantly, Cocoa libraries. Specific topics from the 1.2 release covered in this session are: py2app, macho_standalone, NSBundle features, KVO/KVC support, the new scanframework script, objc.inject(), and runtime editing.
Speakers: Bill Bumgarner, Bob Ippolito
Unlisted on Apple Developer site
Transcript
This transcript was generated using Whisper, it has known transcription errors. We are working on an improved version.
Okay, Python. Does everyone here know what Python is? Python is one of the old scripting languages, but it's very cool. It's dynamically typed, just like Objective-C is, except it's entirely done at runtime. It's a powerful high-level language, so high-level that it often is identical to this pseudocode. integrates extremely well with Objective-C, so well that PyObjC applications are completely indistinguishable from their Objective-C counterparts, even to Interface Builder.
You can use it to quickly develop, extend, and test applications for Mac OS X. Because it's so dynamic, you can change things at runtime. In this session, you'll learn why you should use Python, where you can find it on Mac OS X, Python syntax for Objective-C programmers, Cocoa applications in Python, mixed Python Objective-C applications, and writing plugins entirely in Python.
Why Python? Python saves you time. Python is interpretive, interactive, object-oriented. The syntax is very powerful, but also very clear. It's got excellent support for data structures: dictionaries, lists, sets, everything. It's all there. It's dynamically typed, and it's garbage collected. You don't have to wait for Objective-C to get it. You can have it now. Python on Mac OS X has unparalleled integration with Objective-C. The bridge actually works. is already used in real shrink-wrapped applications, and it's cross-platform when you want it to be, even with this Intel stuff.
Less code. Since Python syntax is so powerful, and it does a whole lot of things for you, you'll have shorter development cycles, fewer bugs, better tests, and more features. A lot of people, even if they have Objective-C applications, are now writing their tests in Python, because it's so much clearer.
Where is it? Python ships with Mac OS X. Mac OS 10.4 comes with Python 2.3.5 as a framework build in system library frameworks. It comes with WXPython, TkEnter, it comes with an Apple proprietary core graphics extension, but it's missing some useful things like BSDDB, the Berkeley DB wrapper, and Readline, because that's got that GPL thing going on.
The current Python version is not Python 2.3.5. The current Python is 2.4.1. Python 2.4 has a lot of cool new features, decorators, built-in set type, and a lot of good things that you probably want. So, if you want to get the latest Python 2.4, you can get the installer from Python.org, and this is also a framework build, just like the Apple one, except it's the latest, least amount of bugs, etc., etc. This one comes with a working TK-Enter, but it also comes with a working BSTDB and a working readline. So, if you press up in the command interpreter, you'll get the last command and not some ASCII garbage.
You can, of course, install the readline extension for the Apple Python, and that's available at pythonmac.org/packages. We'll get to that later. So, you can use this Python 2.4 to create Mac OS 10.3 compatible apps. Currently, Python does not have good integration with the SDKs, so in order to build X.3 compatible apps on X.4, you need to build everything on X.3.
You can assemble your application on X.4, but all of its constituent components that include compiled stuff needs to be compiled on X.3. Fortunately, I've done a lot of that for you guys, and I have a wealth of extensions on pythonmac.org/packages, and also the Python 2.4.1 build is available, and that's built on X.3. This one gets installed into the user, not the user location, the system location for administrators in library frameworks Python, and a couple of sim links to the internet. interpreter and use your local bin.
[Transcript missing]
Python data structures. Here's where I get into all the Objective-C Python stuff. Python data structures are a lot like the data structures you'll find in Foundation. You've got the list, which is a mutable type that's not hashable. The list syntax is square brackets and commas between the elements. So it's just like C array, but it's a lot more convenient to do than the NSMutable array version, as you can see.
The list has quite a few methods, append, etc. They're not named the same as the Objective-C ones. They're often more terse. But it's equivalent. The tuple is the immutable version. It's hashable only if its contents are, and it's just like a normal NSArray. The syntax for a tuple is the comma.
The parentheses are only used for clarity and unambiguity, but really, the operator to create a tuple is the comma. So if you want a tuple with one element, you still need that comma. So typically, if you have a tuple with one element, it's going to be: . That confuses a lot of people early on, but you get used to it.
The dictionary in Python is one of the types that the language is basically based on. All objects have a dictionary for its instance variables and its methods, and classes have the same. The namespace for modules, it's all based on dictionaries. Therefore, the Python dictionary implementation is very, very fast. And it's very good and easy to use.
It's a mutable type, not hashable, and unlike the NSMutable dictionary and NSDictionary, it does not copy its keys. So the keys in a Python dictionary must be hashable. So that can cause you some slight issues if you're bridging Python and Objective-C code, if you pass Python dicks around, because those Python dicks are not going to copy the keys of your Objective-C stuff. However, since we do bridge the mutable string as a Python hashable immutable string, it's not often a problem.
The Unicode object in Python is what you use to store text. Unfortunately, it's not the only thing that can be used to store text. The Unicode object stores characters, not bytes. To denote a Unicode object, it's a string literal with a "u" in front of it. The Unicode object syntax supports quite a few different ways to escape it. You've got the hexadecimal escape, you've got the Unicode hexadecimal escape, and then you've got the nice named Unicode literal syntax, which makes it really obvious which character you're putting in there, because you have to name it out.
The string type in Python, unfortunately named, is an immutable type that stores bytes. But these bytes can act like characters. Hopefully this is due to just legacy reasons, because Python prior to 1.6 did not have any Unicode support at all. So they had this bytes type, which is only really good for doing English, because the encoding is completely ambiguous.
So you want to avoid this if you're representing text, unless it's just some sort of literal in your source code, like the name of a method or something like that. Because still those are based on the str type, so you can't have a method named the copyright character. It still has to be regular strings.
These are equivalent to initializing an enterprise. So you can't have an NSString with a C string, but they're also equivalent to an NSData. It really depends on how you use it. So if you're bridging data types over to Objective-C, you can't just use the str type. You'll have to wrap that in a buffer. When we bridge these stereotypes, we bridge them as the default foundation encoding, which can be whatever. But typically, as defined in the documentation right now, the default is Mac Roman.
Python numbers. Integers and floats are a mutable type. The extent of a Python integer is a C-long. The Python float is a C-double. The syntax for using these is just the obvious syntax. You write the number down. If you add floats and ints together, that works. If you divide an integer by an integer, you don't get a float yet, but you will soon.
If you want that behavior now, you can do from future import division. If you want the integer behavior back, you simply use the double division operator, the // which is integer division. The bool type in Python is just an integer, except there's two singletons of it, true and false, that have a nice representation if you print it out.
And the long type in Python is an immutable number type. It has no C equivalent because it has infinite extent. You can use this to represent really large numbers, as large as you want to. There's no limit to it other than the memory that you have and your patience.
To denote a long number, you simply put a big fat L on the end, and then it's long. Python will automatically promote numbers to longs if they get big enough. Well, integers anyway. But that behavior is relatively recent. But Mac OS 10.4 ships with a recent Python. The NUN type is a mutable singleton which is used like the NSNULL, but other times it's used like nil or null, though it won't receive messages like it does in Objective-C. The NUN is just used as a placeholder, like void, it denotes nothing there.
Okay, Python syntax. Python syntax is a little foreign because it doesn't use those squiggly braces for anything but dictionary syntax. Okay, and you're going to use indentation instead of braces. Colons and indentation denote blocks. So you put a colon there, and then you indent, and that's your block. It's equivalent to your curly braces, except you type a little less.
Because in other languages, you're going to be doing the indentation anyway, so it saves you the trouble. It also makes it nice and easy to read, because you don't have those extra characters flannel around, and if the indentation is wrong, your program simply won't run. So, if it looks good, it probably works good too. Python variables. There's no type declaration, and everything is an object, even those integers and none and everything else. There's no value types, everything is an object, everything is done by reference. It's just like everything was an id and Objective-C. That's how you would do it in Python.
Functions are first-class objects. That means that you can pass them anywhere. You don't have to pass these silly invocation objects around that bind an object and a selector and all that stuff. You simply just pass the reference to the function. This means that Python functions are instance variables because they're just first-class objects, so there was no reason to have separate namespaces for methods and IVARs. So that's another thing that you have to take note of if you're bridging Python and Objective-C, is that Python does not have a separate namespace for selectors, so you're not going to be writing these accessors that have the same IVAR name as the method name.
So what you do in Python is you have these cool first-class objects called descriptors, which can basically return something else if you get them off of an object. So you have the convenience of the getItem syntax, like object.property, but you have the power of using an accessor. So it's effectively like any attribute reference in Python can be a key-value coding path.
And Python functions take positional and/or keyword arguments. Positional arguments are like what you have in Objective-C or C. The arguments have to be all there in a row. You also have keyword arguments, which are similar in concept to selectors, except they can be given in any order like a dictionary, which also means we can't use that as the syntax for the bridge, because the order is arbitrary, since it's however it ends up being hashed. To denote positional arguments of variable length, you can just put a star in front of there. You don't have to do dot dot dot and grab things off of this crazy built-in compiler local, any of that stuff.
Classes in Python. The classes in Python have their interface and implementation as the same thing. You don't have to write your class twice. Methods are instance variables, and in every method you define the self variable explicitly. This is because methods are just functions. They just happen to be living on classes at the time. So, it's very explicit as to what happens. There's nothing magical involved. It's just the first argument is self.
Iteration in Python is very nice. You have all of these iteration concepts bundled into one thing: the iterator protocol. The iterator protocol are these objects that have a next method, and the next method returns the next object that's expected. So rather than having a couple different ways to write loops, for loops and while loops, to iterate over something, you just have one. And the syntax for that is for item in iterable.
So if you're going to do a for loop with numbers, like for zero, end here, step this way, you'll just use a range object or an x range object, which is basically the same as the C syntax. The range object takes a length, a start, and a stop, just like the C iteration syntax does. But the other cool thing is these same for loops can be used to iterate over anything. You can iterate over lists, you can iterate over dictionary keys, you can iterate over iterable objects. And you can build these iterable objects yourself.
You can iterate over generators, which are very cool Python objects. If you define a function and you write the word yield in there, the statement yield, and you do that several times, and when you call it, you get this generator object. And then every time you iterate over that generator object by calling its next, implicitly or explicitly, implicitly with the syntax like this, it'll return the next value that you yielded. So you can use this to build something like coroutines with a little more safety than you'll get if you do it in C with set jump, long jump, and all that craziness.
Getting items out of dictionaries and lists in Python uses the same syntax. It's just the square bracket syntax. So you get the first item out of a list with list . Last item out of a list, you can do -1, or you can get the length of the list -1. The syntax for dictionaries is the same. You just put the key inside of the square brackets.
In Objective-C, you have the methods obj.inx and valueForKey and all that stuff. But in Python, it's the same concept, and you do it the same way. Unlike JavaScript, the arrays and dictionaries are actually different types. So you don't have lists with holes in them, because you don't have nice syntax. The lists are the same.
Slicing is available for lists and anything that wants to implement the slicing protocol. Slicing is basically unlike anything you've ever seen anywhere else, unless you've used very esoteric languages. Slicing is an extension to this getitem syntax, where you can specify the start, stop, and step of what you want.
So if you want every item but the first element, you can say one colon. That means you start at the first item, not the zero, the first index, and then you go until the end with a step of one. If you want the first two or less items, you do the same thing, except you put colon two, which means start at the beginning and end at two.
If you want the list backwards, you can do ::-1, which means start at the beginning, end at the end, at a step of -1. And if you just want a quick way to copy a list, a shallow copy, you can just do a colon in the getitem syntax, and that'll start at the beginning, end at the end, and iterate by 1. So you'll get a shallow copy of the entire list with a three-character syntax. In Objective-C, you'd have to imagine how you would write all these things, and I guarantee it would not be pretty.
Modules in Python. Unlike Objective-C, the Python namespace is not flat. That's another thing you'll have to take note of when you're bridging. Python modules are namespaces for stuff. One module corresponds to one file. But unlike many other languages, these modules are executed at the same time that all their symbols are parsed out.
So if you do a circular import where you have interdependencies between these two modules, then it simply just won't work. You can't define two classes in one module and two classes in another module with interdependencies on each other at the module level of code and have it work. It just doesn't do that.
And Python namespaces, using Python namespaces are explicit. To bring a namespace into your namespace, you simply type import and then the name of that module. And to use functions and methods and all that stuff from that namespace, you just use the getitem syntax, object.property. Okay, you can also bring specific items from another namespace into your namespace with the from import syntax.
From another module, import another function. There's an extension to this where you can say, from another module, import star, which is just generally frowned upon because it kind of makes it hard to tell where symbols came from in your module, but it gets you the same feel that you have in C or Objective-C, where if you do an import statement, you get everything.
And this is awfully convenient if you're doing bridged code, because you can simply import from an Objective-C framework wrapper, and you'll get all the symbols, and it's just like doing it in Objective-C. PyObjC is a great, awesome, magical bridge that makes Python and Cocoa work together. Unlike the other bridges, where you have all these crazy edge cases, PyObjC bridge does everything pretty much right.
The bridge lets you use Objective-C from Python, and it lets you use Python from Objective-C, and there's no glue required. You don't have to code-generate little things, and you don't have to read other documentation. It simply just works, and it's done in a very consistent way, so you don't have to go crazy learning it.
The selectors bridged from Objective-C to Python, you need to deinterleave the arguments from the selector, which basically means replacing the colons in the selector with underscores. This is done because Python uses colons for something else. You can't use a colon in a method name, because that would mean you're trying to start a block or something crazy like that.
And also, because Python's keyword syntax can't be used since the keys are not ordered, you have to do it similar to the Java and JavaScript bridges. And basically every language bridge worth its salt does this, because other languages simply can't denote that Objective-C syntax in a clear way.
So it's different, but it's equivalent. And the thing is here is that you really do replace colons with underscores, even if it only takes one argument. So if you have... So, if you want to say append object, it's going to be append object underscore, parenthesis, and then the object you're going to append.
Subclassing Objective-C classes from Python works just as you'd expect it to. You subclass the object with the normal class syntax. What this does here is that it has to follow the same rules as Objective-C, which means that no matter what module it's done in, you can only have one class of that name if it has an Objective-C superclass. So that's another thing you have to be wary of.
However, this gives you a full class bridge to Objective-C, so if you want to find that class from Objective-C, you can use NSClass from String and the name of the class. As long as that Python code has been run, you'll be able to find it. And you can even do it from the NSBundle syntax if you happen to be loading Python stuff from a bundle.
In PyObjC, rather than just bridging method calls, we also bridge lots of other things too to make it really convenient to use. You have the NSString type and chair pointers are bridged to the Python Unicode and str types. And this bridge is bidirectional, so you can take a Python string literal, pass it over the bridge through a method call or in a collection or something like that, and it will be bridged right over to an NSString subclass. And if the Objective-C selector says that it takes a chair pointer, then the str type will be bridged right over, no problem.
The data types in Objective-C are bridged a little strangely, because in Python you'd kind of expect a data type to be bridged as a str, but we can't do that and also bridge text types correctly. So what we do here is we bridge data types as a buffer-like object and vice versa. So if you get an NSData from Objective-C, you'll have an object that implements the buffer protocol.
And that lets you do cool things like use it to initialize an array object or like a numeric array or a pill buffer or something like that. And if you want to pass a data over to Objective-C, rather than having to initialize NSData, data with bytes, and a Python string, you can simply wrap it with the Python buffer object, which saves you a copy if nothing else.
The array types are bridged to list and tuple. Since the implementation of NSArray doesn't really specify if it's going to be a mutable array or an immutable array, they're all bridged as list types, so you can't check to see if it's instance tuple to see if it's mutable or not. You really just have to try it and see if it lets you mutate it. The dictionary types are bridged to dict, but there is that caveat of not copying keys at all.
The number types, NSNumber, are bridged to dict. The number types, NSNumber, and also the C value types, bool, int, float, double, all that stuff, are bridged to the Python types equivalently, int, long, float, etc. However, if you get one of these NSNumbers, these NSNumbers don't really correctly describe themselves as to what number they actually are.
Currently in the foundation, if you initialize an NSNumber with an unsigned integer, and you ask it what type it is, it'll tell you what signed type it is. And so that gives you some strange behavior if you bridge it right over to Python. But what we do is we bridge the NSNumbers over to subclasses of int, long, and float.
And what that does is it gives you the same methods that you had on NSNumbers. So if you get one of these things over the bridge, you can call its float value or unsigned int value or whatever, and you'd get the same behavior that you'd get in Objective-C, meaning that if you call the right method, you get the right value, and everything works correctly. But if you don't, and you just use it as a regular Python object, it supports all the number stuff, but it might give you the wrong value if it's supposed to be unsigned.
You've got these NSDecimal numbers in Foundation that are bridged to a custom number-like wrapper. In Python 2.4, we could have bridged it to the decimal object, but the decimal object has very precise semantics, especially if you'd ask somebody like Tim Peters. And we decided to have a separate wrapper rather than trying to bridge it to something semantically non-equivalent. And also, for some of the Carbon types, like FSRef objects, those are bridged right over to the Python type, rather than getting this ugly little struct thing. You'll get an FSRef object in Python, and that supports the normal FSRef methods and whatnot that you have.
Wrapped frameworks. Frameworks in Objective-C are wrapped as Python packages. So that means if you have the AppKit framework and you want to get symbols out of it, you can do from AppKit import *. But this only works because we made special wrappers for them. You can't do that for anything. You can't do from Omni Foundation import *, because there isn't a package already for that. But there are packages for everything that ships with this system that we've wrapped so far, which is basically all of the useful ones: AppKit, Foundation, CoreData, etc., etc., etc.
If you want to get the frameworks that we haven't wrapped yet, we have the NSBundle API, which you can use to do it the same way you would load it dynamically in Objective-C. Or you can use our API, the objc.loadbundle, loadbundle variables, loadbundle functions, that let you pick stuff out of the bundle and import it into a namespace, which gets you all the symbols at once rather than having to NSClass from string them all.
There's also ways to generate Python packages from frameworks. However, we haven't quite released something that we like a lot yet. So that's not quite a public API, but it's going to be as soon as we get our Objective-C header parser and all that jazz to work as well as we'd like it to.
We also have Xcode templates. A lot of you like Xcode, and everyone should be using Xcode, especially now. But it's not the greatest thing in the world for Python yet. Xcode doesn't have a lot of Python support built in. And in Xcode 2.0 at least, the target support didn't let us do what we needed to do natively, so we had to add a little magic with some shell scripts and some Xcode file parsing and stuff. So the Xcode templates work, and they do the right thing, but they might be a little fragile. However, if it breaks, we'll fix it.
The magic that we used here is that we made development and deployment different targets. And we used py2app to build instead of asking Xcode to do anything. The build step is simply a shell script that runs a setup py, just like you would build any other Python application.
And that py2app script will read the Xcode project file, look in your groups, pick out the Python files, pick out the resources, and instruct py2app to build the application. And a future version of Xcode, possibly even Xcode 2.1, will be able to rework these templates to be cleaner. What py2app does is a lot of very nice things for you. It'll take care of all your Python dependencies.
It looks at your Python files and your scripts, it analyzes the bytecode, and looks for import statements. And then it looks through your Python module namespace to find all those dependencies that you have. And it makes all that stuff go into your bundle. So you don't have to figure out what you use, it'll figure it out for you.
It's not 100% reliable, but the only thing you have to do to fix it is put an import statement in your code if it doesn't find something. However, it is quite reliable as long as you're using import statements and not the special underscore, underscore, and so on. So you can use the import statement in Python to import something with a dynamic name.
It also automatically finds all dependent frameworks and and dial-ups. So if you have this Python extension that links to something crazy that's third-party, it'll actually find that you do that, and it'll copy it in, and it'll rewrite the load commands. So no install name tool, no copy files phases, it does all that for you automatically. And it gives you the bundle relative install names, so you don't have to have these crazy set-em dial-ed scripts or any mess like that. It just does it all for you, hopefully correctly.
Some of this technology that's in here can actually be used for regular Objective-C projects or any other language, basically. There's a shell script that you can just run on your application, and it'll make it standalone by doing this whole dial-ed find process. And I actually used that, heretically, possibly, to build the Frozen Bubble application, which depends on Perl and a bunch of stuff from Darwin ports and SDL.
And it just worked. So cool stuff in PyDA app, and you should look into it, even if you're not doing Python stuff yet. Because it can save you all the trouble of doing the copy files phases and all that stuff. And the tool that it ships with to do that is called mako_standalone.
All right, excuse me. So, some demos of all the stuff in action. And as Bob says, I mean, you know, we're going to do a lot of PyOpc related demos, but Python and Mac OS X with PyOpc installed, the Py2App stuff is just brilliant. It lets you package up any standard Python modules, a double clickable dot package. So, if you're distributing standard portable Python across a fleet of Macs, packaging and installation is now trivial. Oh, yay. I still have bits. Excellent.
So, this application that's running on the screen now is actually the outline edit example from the core data examples that are provided on your machine, ported to Python and PyOpc. As you can see, it all just works. And what's interesting about this is just works means KVO works, key value observation, key value coding, subclassing, delegation, nib loading, the entire Cocoa stack. Now, what does this look like under the covers? So, I'm going to go ahead and quit this. And where did I put it? Okay. Desktop. See the examples. Okay.
Data. As you can see, PyObjC has a boatload of examples with it. We go now outline edit. The actual app wrapper looks like a standard app wrapper, but because of the magic of py2app, everything, all the dependencies, dialyps, everything's wrapped up inside. Let me go ahead and open up my document.
This is actual Python code written against Cocoa. In particular, you'll see this. This is interesting. This demonstrates the dynamism of Objective-C in combination with a scripted language like Python. In this case, this is the document class for the outline edit application. How many Cocoa programmers are here? Okay, so the standard Cocoa paragraph.
What this is actually doing is reading the definition of the document, my document class from the nib file, defining the subclass on the fly, and setting it all up, and then instantiating it from within the nib file. You can also see, you know, there's, this is straight Objective-C more or less, calling methods, etc. It all really does just work. What's our-- can we go back to slides for a second? What's the next one? The next demo is PyObjc Mixed Applications.
This demo is a cool little application called RestEdit. RestEdit is a text editing application for editing a special kind of text called restructured text. Restructured text is a text format that's kind of like Markdown if you've ever used it, but it's used for basically writing Python documentation and various other documents. It can be translated to LaTeX and HTML and all kinds of cool stuff.
Can we get the demo machine, please? So it's unfortunate the font's a little small, but on the left-hand pane there, you'll see that this is a text document with some pretty minor ASCII markup, but on the right side is the rendered form of it. Now again, this is actually a mixed Python and Objective-C application, so like the split view here was an open source split view that I found that was really useful.
There's kernel queues being used in here. There's several other things happening from Objective-C that is integrated with Python, and then is also using a number of third-party Python modules. And one of those third-party Python modules is for the second one. PyObjC survived the upgrades too, by the way.
I think it's still reading the old Xcode project, though. So... Well, yeah, but we can gloss over that for the next hour or so. Okay, so what we have here is the Xcode project for restructured text. This isn't the one that's used in the release version of restructured text, but I refactored it to use the new Xcode templates. The new Xcode templates, as I said, you have separate development and deployment targets that build the application in development or deployment mode.
The cool thing about py2app's development mode is that it doesn't really compile anything. Basically, it just makes symlinks to your source code, and it runs it straight out of the tree. So you can just make a change to the source, and then restart your application. It takes like two seconds to start your application, rather than however long it would take you to recompile everything, or recompile just the unit, or what have you.
When you switch to deployment, it'll do all that crazy stuff that I talked about before. All the dependency finding, the framework finding, the header rewriting, all that stuff. And what I didn't mention before is that when you build a standalone deployment application, it really is standalone. If you're using a third-party Python, like Python 2.4.1, it'll include Python itself. It'll include PyObjective-C. It'll include everything you use. And you don't really have to think about it, because it does it all for you.
And the way this Xcode template works is it has special groups, and these groups do different slightly magical things. The resources group are the resources that end up in your application. So all your nibs, all that stuff, just throw in the resources folder and it'll end up in your application.
The classes thing, what it does is it'll actually import all these classes in the order that they are listed in the Xcode project. So if your nib depends on something, you don't have to worry about importing it somewhere in the code. You just put it in this group and it'll be imported.
And this is the mixed template. So what we have here is another target, which is all the Objective-C stuff. And all this Objective-C stuff is compiled as Xcode normally compiles it. Takes all this RB split view stuff, the KQ wrapper, and it puts it all into a nice little plugin, and that plugin ends up in the resources folder. What we have here is this automatically generated Python script that uses the objc load bundle API.
and it picks up the bundle and it imports all the classes. And this gets imported automatically, so basically you have this mixed target, you write some Objective-C code, you write some Python code, and you don't write any glue code because this is it, and this is automatically generated for you.
And to use it from your Python code, Okay, to use it from your Python code, you simply import it and... There it is. That's the Objective-C code in the project. We're using the KQ wrapper. The split view stuff, we're not even using in the code. We're just using in the nib. So we don't have to do anything in the code for that, because it's automatically imported, because the plugin loader that's automatically generated loads it for you, and then the nib finds it, and then that's it.
So mixing Objective-C and Python code is really simple. You basically just select the right Xcode template, and the rest is done for you. If you want to do that by hand, it's really not that hard. You can just look at the Xcode template and see what it does, and bring it over to another template. There's not too much magic involved with the plugin stuff.
Okay, PyObjC plugins. The next demo will be a PyInterpreter palette. Go for it, Bob. Okay, so in this, in the previous example, we had an Objective-C plugin loaded by a Python application. It's sitting on the desktop. I made sure it runs in Interface Builder. It's already built. You can just run Interface Builder if you wanted. - Okay. So what we have here is an Interface Builder plugin written entirely in Python with PyObjC. And it's this nice palette with a nice big blue thing. And what we'll do here is we'll create a new Cocoa application nib.
Drag over this guy. And this is our PyInterpreterView class. We can inspect this, and we'll check this box to import Objective-C classes, and we'll test it to see if it works. And what we have here is a Python interpreter inside of Interface Builder. And we can, since we imported all these So now this is interesting because what this means is that you can use Python via PyObjC to write a plugin for any application on Mac OS X that supports a plugin API. So screen savers, preference panes, etc.
Well, that also includes anything like the administrative apps that have the plugin APIs or anything else. So Python is obviously a wonderful cross-platform administrative tool, and now you can integrate it with the GUI apps for the admin stuff, too. And it's great for testing, because you can put this right in your app, even if it's just an Objective-C app. You can drop this interpreter palette in there, and you can inspect all your classes and call methods on things, like I can call the NSApplicationSharedApp.
Oh, that's interesting. I'd be returned something else if you do NSApplication shared application. Yeah, sorry. But that's my typical demo. If I put this in something, I'll just kill it, because that's the only selector on NSApplication that I really remember very well. And so that is a plugin written in Python, and that's using the same Xcode template, just slightly modified to build a plugin instead of an application.
And this is basically just the PyInterpreter example modified to be a plugin. To build a plugin instead of an application with py2app, you simply specify plugin instead of app in your setup UI. But the setup UIs in the Xcode templates are basically just done automatically for you and contain a little bit of magic.
However, if you're building a plugin instead of an application with the Xcode template, we currently don't ship a plugin template. So what you'll have to do here is if you're building a plugin, you go into the setup UI, and you make some small changes to the end. There's a setup options, Xcode, py2app, setup options. You get the plugin dictionary, and then you change that to change the extension to palette.
And we'll have these examples up for you later today, and you'll be able to play with it and look to see how it's done. But this is all Python code. I'm not hiding any Objective-C code here. It's all Python stuff, no C code, but it's loaded as a plug-in into an Objective-C application.
So that means you can integrate it with anything. You can make a Colloquy plug-in, a Quicksilver plug-in, an Interface builder plug-in, an Xcode plug-in, a Safari plug-in. Like when Safari 1.3 came out, I took this PyInterpreter thing and I made a WebKit plug-in out of it, which is really kind of funny because you can have it talk to JavaScript and stuff. Because JavaScript can talk to Objective-C as well, though not quite as cleanly as Python does.
So, and speaking of the interpreter, so this is subathedit. subathedit is up and running. It knows absolutely nothing about PyObjC or Python or anything. There's an example that's included that injects the interpreter into running processes. So if I hit return now, this is actually a Python interpreter that's running inside of subathedit now. So I can actually -- import the app kit, and if I do nsapp.delegate, you'll see, sure enough, it came back as app controller, which is the app delegate for subf edit. And this does not work on Intel yet.
This barely worked on Tiger. But it'll be there. So that's that. Now what's the next demo? The code importer. Yeah. Okay, cool. So we also do integration of Python with Spotlight, and in this case I wrote an importer of Objective-C code, though it's got hooks in it for Java and Python. Hint, hint if anyone wants to do the parsing there.
So what the code importer does is, this is actually a completely different way of doing this. This is embedding the Python interpreter directly without using PyObjC, and as Bob put it, writing a whole bunch of code you didn't have to write. But, you know, this is the pure Python form of it. There's not really much of anything terribly interesting in this. What's interesting is what happens when it runs.
Unless you find lots of C code doing very hairy things with global interpreter states and threads and all that stuff. Unless you find lots of C code doing very hairy things with global interpreter states and threads and all that stuff. If you find that interesting, then this example would be quite interesting to you.
Yeah. So now that this importer is installed and up and running, if I go to -- where did I put it? Here it is. Omni Foundation. I went and I grabbed all the Omni Group's wonderful bodies of code and threw it on my machine and let it chug overnight. So now that I've done that, I can do this.
You'll see that I'm getting metadata about the implementations contained in the files. So in this case, it's telling me the programming language is Objective-C, and that there's one implemented class inside of this file. Now, where this gets to be interesting is I can ask some much more difficult questions, like, I don't know if... I guess every Objective-C programmer's experienced this.
I've got an NSArray, I've linked against a bunch of frameworks, maybe some of them third-party. Someone somewhere has added a method to my class, I don't know where the hell it is, and I don't know what it does or why it's there. Let me find that implementation file. Python doesn't natively let you do this, by the way. Python does not support categories on itself.
So, this is actually a query that asks to find all of the files on the system that contain an implementation of an Objective-C category that adds methods to NSArray. And as you can see, I mean, this is really a Spotlight demo. The code for the code importer was pretty straightforward. It's advertising the metadata, the keys by name, you can set up your custom queries, you can do all that. So, yes, we support Spotlight, too.
And then finally, one last demo, is that, okay, we'll use this terminal since it's so nice and big. You can do interpretive Objective-C too. So I can do from obc import, from foundation import. And so, yeah, I'm just interpreting Objective-C at this point. And since we can inject the interpreter, great debugging tool, et cetera. To drive home that this is in fact, you know, really Objective-C and it is really dynamic and working great, I can do things like NSBundle.allframeworks.
I spelled it right. You'll see I get back an array. It's actually an NSArray, but because of the transparent bridging, I can say for x in A, print x, and you'll see it just now iterated using the Python iteration stuff, but obviously calling into Objective C. Basically, what is it? Any class that implements object enumerator is then iterable? - Yep. - Yeah, so if you implement your class with an object enumerator method, it will be iterable in Python. Now, I can also do some really frightening things.
Not that we haven't done any frightening things already. Right. Since everything in Python's an object, and the bridge is just bridging Objective-C over as a bunch of objects, that means methods are objects too. So sure enough, if I do nsobject.description, I get back the object that represents the implementation of the description method on nsobject. So I can of course define a new function, and I'm gonna make it print. Well, description returns a string, so.
Oh, nah. - You might wanna return that instead of printing it. - Oh yeah, you're right. I really do wanna return that, don't I? Bob keeps my stuff from crashing. It wouldn't crash, it just would have funny output. So yeah, sure, I made a function. That's great. Now, before we do anything mean to NSObject, you can see that yes, I can instantiate an NSObject, and I get my method backs, etc.
So now, if I want to actually override description, I just do NSObject.description = fun. And once that's done, it's overwritten. Now this is actually editing the Objective-C runtime itself, so this will all work from Objective-C too. And it lets you do really frightening things like NSArray.description equals, I don't know, NSDictionary.description. Oops. Oh. Okay. So we broke that somewhere.
But... If I were to put the NSDictionary's description into a Python function and then throw that into the NSRate, it'd work fine. Well, for some definition of work, fine. It would give you a very interesting GDB backtrace, I would say. True. Exceptions come across, they're all transparent, they get converted to Objective-C, then to Python, etc. And again, I mean, this just isn't about macho_standalone. The py2app stuff, the automatic packaging stuff just makes it so painless to use Python on the platform.
Which, before we go to Q&A, I'm sure one of the first questions is, "Why isn't PyObjC shipping in Tiger?" And the answer there is really quite simple. We looked at it, and PyObjC is so painless to install. Go grab a package, double-click it, it works. And the development moves rapidly. PyObjC is constantly evolving, it's constantly gaining new capabilities. But it's got a ton of unit tests, which are not only guaranteeing the quality, but are guaranteeing that the prior version's contract is maintained.
And all those things together made it such that we looked at it and we're like, you know, people are going to be better off just grabbing it, double-clicking it, and running with it. And because of py2app and the automatic packaging tools that are there, people that are shipping commercial applications with this stuff, of which there are a number, don't have to worry about someone grabbing some random copy and throwing it on the ground. Because py2app will take care of making sure all dependencies are resolved internally to the app wrapper. So, you know, it was just, it was easier to go with a third-party solution or leave it out there in the wild. But in any case, that's Python on Mac OS X.