It's another Friday and time for another Friday Q&A. This week, fellow Amoeba Jeff Johnson suggested talking about using Cocoa from the command line using Python and PyObjC.
I assume everybody reading this knows what Cocoa is, but may not know what the other two parts are:
python into your nearest terminal window.import FrameworkName at the Python command line. However, since Python supports namespaces, this requires puttingFrameworkName. before any symbol inside that framework that you want to use. This is usually a good thing for "real" code, but if we're just going to experiment with things from the command line, it's better to avoid that. You can tell Python to import everything in the framework into the top-level namespace instead withfrom FrameworkName import *. Then you can use symbols from the framework directly. Example:>>>fromFoundationimport*>>>NSFileManager<objective-cclassNSFileManagerat0x7fff7101eb18>
>>>NSFileManager.defaultManager()<NSFileManager:0x100257900>
>>>NSFileManager.defaultManager().displayNameAtPath_('/')u'Fear'>>>NSFileManager.defaultManager().fileAttributesAtPath_traverseLink_('/',True){NSFileCreationDate="2006-08-18 08:33:34 -0400";NSFileExtensionHidden=0;NSFileGroupOwnerAccountID=80;NSFileGroupOwnerAccountName=admin;NSFileModificationDate="2009-11-16 23:58:45 -0500";NSFileOwnerAccountID=0;NSFileOwnerAccountName=root;NSFilePosixPermissions=1021;NSFileReferenceCount=38;NSFileSize=1360;NSFileSystemFileNumber=2;NSFileSystemNumber=234881026;NSFileType=NSFileTypeDirectory;}
>>>'abc'.length()Traceback(mostrecentcalllast):File"<stdin>",line1,in<module>AttributeError:'str'objecthasnoattribute'length'
>>>NSString.stringWithString_('abc').length()3
len('abc').)Errors
Cocoa methods that returnNSError instances by reference get special treatment by PyObjC. Python cleanly supports returning multiple values from a method, but doesn't cleanly support return-by-reference, so PyObjC translates theNSError return by reference into a multiple return. You just assign two variables to the result of the method, and then passNone (Python's version ofnil for theNSError argument. Example:
>>>string,error=NSString.stringWithContentsOfFile_encoding_error_('/',NSUTF8StringEncoding,None)>>>string>>>error.description().encode('utf8')'Error Domain=NSCocoaErrorDomain Code=257 UserInfo=0x11994b610 "The file\xe2\x80\x9cFear\xe2\x80\x9d couldn\xe2\x80\x99t be opened because you don\xe2\x80\x99t have permission to view it." Underlying Error=(Error Domain=NSPOSIXErrorDomain Code=13 "The operation couldn\xe2\x80\x99t be completed. Permission denied")'
error directly, Python will complain that its description can't be converted to ASCII, so I have to manually get the description and convert it to UTF-8 for printing.Arrays and Dictionaries
A Python array can be written like this:
['a','b','c']
>>>data,error=NSPropertyListSerialization.dataFromPropertyList_format_errorDescription_(['hello','world'],NSPropertyListXMLFormat_v1_0,None)>>>NSString.alloc().initWithData_encoding_(data,NSUTF8StringEncoding)u'<?xml version="1.0" encoding="UTF-8"?>\n<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">\n<plist version="1.0">\n<array>\n\t<string>hello</string>\n\t<string>world</string>\n</array>\n</plist>\n'
Dictionaries are written like this:
{'key':'value','key2':'value2'}
>>>data,error=NSPropertyListSerialization.dataFromPropertyList_format_errorDescription_({'key':'value','key2':'value2'},NSPropertyListXMLFormat_v1_0,None)>>>NSString.alloc().initWithData_encoding_(data,NSUTF8StringEncoding)u'<?xml version="1.0" encoding="UTF-8"?>\n<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">\n<plist version="1.0">\n<dict>\n\t<key>key</key>\n\t<string>value</string>\n\t<key>key2</key>\n\t<string>value2</string>\n</dict>\n</plist>\n'
Custom Frameworks
A fun thing with PyObjC is that it's not limited to system frameworks. You can load your own frameworks! It's trivial to do, because you can do it exactly as you would do it in a Cocoa program: just use NSBundle to load the framework and start getting and manipulating classes. Here's an example of doing this with a system framework, but it works just the same for your own:
>>>bundle=NSBundle.bundleWithPath_('/System/Library/Frameworks/WebKit.framework')>>>bundle.principalClass()<objective-cclassWebPlaceholderModalWindowat0x7fff7095cc40>>>>bundle.classNamed_('WebView').alloc().init()<WebView:0x100212490>
Note that on 10.6 and 64-bit capable machines, Python loads as 64-bit by default, so if your framework is 32-bit only then this won't work. You can load Python in 32-bit mode by starting it with the following command (assuming you use the default bash shell):
VERSIONER_PYTHON_PREFER_32_BIT=yespython
AppKit
So far we've just been working with Foundation-like objects, but PyObjC supports AppKit as well:
>>>fromAppKitimport*
>>>NSApplicationLoad()True>>>window=NSWindow.alloc().init()>>>window.makeKeyAndOrderFront_(None)
>>>image=NSImage.alloc().initWithContentsOfFile_('brakes.jpg')>>>image<NSImage0x1198b6f70Size={483,450}Reps=("NSBitmapImageRep 0x1198bcfa0 Size={483, 450} ColorSpace=(not yet loaded) BPS=8 BPP=(not yet loaded) Pixels=483x450 Alpha=NO Planar=NO Format=(not yet loaded) CurrentBacking=nil (faulting) CGImageSource=0x1198bc220")>>>>scaledImage=NSImage.alloc().initWithSize_((32,32))>>>scaledImage.lockFocus()>>>image.drawInRect_fromRect_operation_fraction_(((0,0),(32,32)),NSZeroRect,NSCompositeCopy,1.0)>>>scaledImage.unlockFocus()>>>scaledImage.TIFFRepresentation().writeToFile_atomically_('/tmp/brakes_thumb.tiff',True)True
Conclusion
This article barely scratches the surface of what's possible with PyObjC, but it should get you started. You can manipulate QuickTime movies, put up windows, test frameworks, and even write full-blown applications. For further reading, check outthe PyObjC documentation andthe Python documentation. This is a valuable tool to have in your kit, whether for testing, rapid prototyping, or just trying things out.
That's it for this week. Come back in seven days for another exciting edition. Until then,send in your ideas for topics to cover. Friday Q&A is driven by your ideas, so send them in!
bundle.classNamed_('WebView')Add your thoughts, post a comment:
Spam and off-topic posts will be deleted without notice. Culprits may be publicly humiliated at my sole discretion.