Apple has included one or more versions of python with OS X since Jaguar (10.2). Their latest version, Mountain Lion (10.8), includes python versions 2.5, 2.6, and 2.7. This presence of python on a clean install of OS X makes it a compelling language choice for automation and information gathering scripts – especially when combined with pyObjC and Scripting Bridge. In fact, various portions of OS X rely on the included python environments to function correctly. Attempts to remove python from OS X or re-link /usr/bin/python to an alternate python installation could result in unexpected effects.
If you plan on using python with OS X, you should also know that different versions of OS X include different selections of python version. Snow Leopard (10.6), for instance, only offers python 2.5 and 2.6. Knowing which version of python you’re running in can have a major effect on how you write your scripts. Certain language features and module functions are not available in earlier versions.
Here’s just a small list of gotchas that I’ve run into:
So – how do you pick which python version to use?
You can write your scripts to call a particular version of python directly by name (python2.6, python2.7, etc.), but on OS X there’s another option.
In Snow Leopard (10.6), Apple made an undocumented change to /usr/bin/python and pythonw. They replaced the symlink to python with a helper executable. This executable redirects the running script based on a environment variables and user/system preferences to the desired python version (located within the /System/Library/Frameworks/Python.framework bundle). With the release of Lion (10.7), Apple updated the manpage documentation to reflect these changes:
A quick summary of the controls Apple added include:
- Version selection, per user: defaults write com.apple.versioner.python Version 2.7
- Version selection, all users: defaults write /Library/Preferences/com.apple.versioner.python Version 2.7
- Version env variable (trumps pref): export VERSIONER_PYTHON_VERSION=2.7
- 32/64-bit selection, per user (2.6+): defaults write com.apple.versioner.python Prefer-32-Bit -bool yes
- 32/64-bit selection, all users (2.6+): defaults write /Library/Preferences/com.apple.versioner.python Prefer-32-Bit -bool yes
- 32/64-bit env variable (trumps pref): VERSIONER_PYTHON_PREFER_32_BIT=yes
If no preference is specified, /usr/bin/python will default to choosing python 2.6.
Apple also notes that these preferences and variables are ignored if the python executables (python2.5, python2.6, python2.7) are called directly. This makes sense, considering it’s the /usr/bin/python helper application that’s the one paying attention to them.
… There is, however, one minor piece of documentation they left out of the manpage.
I discovered this while answering a question in the ##osx-server IRC channel on Freenode:
“rmanly: so I just noticed that xattr is a Python script and I am looking at it in vim and I can’t see where it is actually doing the “work” all I see is error messages and regular expressions, and globs. Can someone enlighten me por favor.”
rmanly was correct – the python script located at /usr/bin/xattr seemed to contain nothing but code for generating error messages. In addition, there were 3 other xattr scripts located in the same directory (xattr-2.5, xattr-2.6, xattr-2.7) which appeared to have the real code for working with extended attributes. Yet looking at the logic in /usr/bin/xattr, there was no clear indicator of how it was calling the version-named scripts.
In an attempt to figure out what was going on, I modified the /usr/bin/xattr script to include a single line:
print "/usr/bin/xattr was called"
I ran xattr again – and no message printed out. As it turns out, the script wasn’t even getting called – the others were being called directly.
Apple appears to have included one additional versioning mechanism in the /usr/bin/python helper. It only kicks in for scripts running out of /usr/bin. When the script is being called with /usr/bin/python, if another executable is located in /usr/bin named ‘scriptname-pythonversion’ that matches the version you’re using (i.e. /usr/bin/script-2.7 for /usr/bin/script, with python 2.7), it will transparently call this alternate script. The script without a version appended to its name will only be called if no ‘versioned’ alternative is available.
You can try this yourself by making a simple script:
#!/usr/bin/python print __file__
Copy it to /usr/bin/scriptname, /usr/bin/scriptname-2.6, /usr/bin/scriptname-2.7, etc. Then see what happens when you call your script – and then call it again after removing the version named after your current python version.
As interesting as this is, there are only two executables currently in /usr/bin (xattr and easy_install) that take advantage of it. Additionally, I’ve been unable to find any other paths that the behavior occurs in. This might explain why Apple did not include documentation about it in the manpage.
Now that you know more about the internals of python execution on OS X, take a good look at your supported OS versions before attempting to come up with scripts that are compatible across all of your systems. If you’re going to use version-specific language features or modules, make sure to explicitly be calling the appropriate python executable (python2.6, python2.7, etc.). Otherwise, consider targeting language and module features present in python 2.6 for the largest compatibility between current versions of OS X.