The Python Tourist #5: Replacing sys.version_info with pyconfig

If you've spent any time writing Python code that is meant to be portable across multiple versions of Python, you've most likely written a few statements like this:
Using sys.version_info
# am I running on Python 2.2 and up?
if sys.version_info[0] >= 2 and sys.version_info[1] >= 2:
    # do stuff for Python 2.2
else:
    # do stuff for earlier versions
The problem I see with this is that it is ultra-verbose, and doesn't actually tell you what capability you require here. Although you can chop down the verbosity with a statement like:
Better, but ...
if sys.version_info[:2] >= (2,2):
    ...
That doesn't take care of the fact that you are relying on a hardcoded version number. There are a few downsides to this: For the sake of argument, lets say that IronPython has a different feature set than CPython and Jython. Picture writing code like this:
Check for implementation as well as version
if is_CPython():
    if sys.version_info[:2] == (2,1):
        # do 2.1 stuff
    elif sys.version_info[:2] >= 2.2:
        # do 2.2 stuff
        
elif is_Jython():
    # do the Jython version checking ...
    
elif is_IronPython():
    # and more version checking ...
After a while, it begins to feel like writing C-style #ifdefs instead of Python.

The dynamic nature of Python means you can do all sort of neat introspective things, including introspection of runtime capabilities. Want to know if the current Python understands a particular piece of code? Run it and see!

There is a little module called pyconfig that I wrote while working on xml.pickle (part of Gnosis_Utils). It is sort of like an autoconf for Python, except it works at runtime. It is bundled with Gnosis_Utils (since it uses it internally), but can be used as a stand-alone module, as there are no external dependencies.

The pyconfig module provides a set of prewritten tests to let you check for capabilities of the Python interpreter, instead of relying on version numbers.

Compare the following two code segments:
Without pyconfig
# need generator expressions
if sys.version_info[:2] >= (2,4):
    # do something with generator expressions ...

# are True/False builtin?
if not (sys.version_info[:2] >= (2,2)):
    # define my own True/False

# is 'enumerate()' available?
if sys.version_info[:2] >= (2,3):
    # do something with enumerate()    
Compare to the pyconfig-based code:
With pyconfig
from gnosis.pyconfig import pyconfig

# need generator expressions
if pyconfig.Have_GeneratorExpressions():
    # do something with generator expressions ...

# are True/False builtin?
if not pyconfig.Have_TrueFalse():
    # define my own True/False

# is 'enumerate()' available?
if pyconfig.Have_Enumerate():
    # do something with enumerate()    
In the second case, it is clear exactly what capability is needed. Also, the code is now robust across any nonstandard versions of Python that it might be running on.

If you import pyconfig as from pyconfig import pyconfig, then all test results will be automatically cached. This allows you to use the tests inline with a minimum speed penalty.

pyconfig is written as set of small tests, with the reusable parts modularized. This makes it very easy to write any new tests you need. If nothing else, the source code to pyconfig is an interesting historical reference of the various PEPs that have been included over the evolution of Python.

Getting pyconfig

As mentioned, pyconfig comes bundled with Gnosis_Utils: Gnosis_Utils

Or if you prefer, you can grab it as a separate module: pyconfig.py
NOTE
The version bundled with Gnosis_Utils is the "stable" version. The standalone is the latest snapshot I've uploaded here, and may have newer features and/or bugs.
Written in WikklyText.