22.7. Wrapping C Environment Calls
Let's move on to a more useful application of C extension modules. The handcoded C file in Example 22-9 integrates the standard C library's getenv and putenv shell environment variable calls for use in Python scripts.
Example 22-9. PP3E\Integrate\Extend\CEnviron\cenviron.c
This example is less useful now than it was in the first edition of this bookas we learned in Part II, not only can you fetch shell environment variables by indexing the os.environ table, but assigning to a key in this table automatically calls C's putenv to export the new setting to the C code layer in the process. That is, os.environ['key'] fetches the value of the shell variable 'key', and os.environ['key']=value assigns a variable both in Python and in C.
The second actionpushing assignments out to Cwas added to Python releases after the first edition of this book was published. Besides demonstrating additional extension coding techniques, though, this example still serves a practical purpose: even today, changes made to shell variables by the C code linked into a Python process are not picked up when you index os.environ in Python code. That is, once your program starts, os.environ reflects only subsequent changes made by Python code.
Moreover, although Python now has both a putenv and a getenv call in its os module, their integration seems incomplete. Changes to os.environ call os.putenv, but direct calls to os.putenv do not update os.environ, so the two can become out of sync. And os.getenv today simply translates to an os.environ fetch, and hence will not pick up environment changes made in the process outside of Python code after startup time. This may rarely, if ever, be an issue for you, but this C extension module is not completely without purpose; to truly interface environment variables with linked-in C code, we need to call the C library routines directly.[*]
The cenviron.c C file in Example 22-9 creates a Python module called cenviron that does a bit more than the prior examplesit exports two functions, sets some exception descriptions explicitly, and makes a reference count call for the Python None object (it's not created anew, so we need to add a reference before passing it to Python). As before, to add this code to Python, compile and link into an object file; the Cygwin makefile in Example 22-10 builds the C source code for dynamic binding.
Example 22-10. PP3E\Integrate\Extend\Cenviron\makefile.cenviron
To build, type make -f makefile.cenviron at your shell. To run, make sure the .dll file is in a directory on Python's module path (a period [.] works too):
.../PP3E/Integrate/Extend/Cenviron$ python >>> import cenviron >>> cenviron.getenv('USER') # like os.environ[key] but refetched 'mark' >>> cenviron.putenv('USER', 'gilligan') # like os.environ[key]=value >>> cenviron.getenv('USER') # C sees the changes too 'gilligan'
As before, cenviron is a bona fide Python module object after it is imported, with all the usual attached information:
>>> dir(cenviron) ['_ _doc_ _', '_ _file_ _', '_ _name_ _', 'getenv', 'putenv'] >>> cenviron._ _file_ _ './cenviron.dll' >>> cenviron._ _name_ _ 'cenviron' >>> cenviron.getenv <built-in function getenv> >>> cenviron <module 'cenviron' from 'cenviron.dll'> >>> cenviron.getenv('PYTHONPATH') '/cygdrive/c/Mark/PP3E-cd/Examples'
Here is an example of the problem this module addresses (but you have to pretend that some of these calls are made by linked-in C code, not by Python):
.../PP3E/Integrate/Extend/Cenviron$ python >>> import os >>> os.environ['USER'] # initialized from the shell 'skipper' >>> from cenviron import getenv, putenv # direct C library call access >>> getenv('USER') 'skipper' >>> putenv('USER', 'gilligan') # changes for C but not Python >>> getenv('USER') 'gilligan' >>> os.environ['USER'] # oops--does not fetch values again 'skipper' >>> os.getenv('USER') # ditto 'skipper'
22.7.1. Adding Wrapper Classes to Flat Libraries
As is, the C extension module exports a function-based interface, but you can wrap its functions in Python code that makes the interface look any way you like. For instance, Example 22-11 makes the functions accessible by dictionary indexing and integrates with the os.environ objectit guarantees that the object will stay in sync with fetches and changes made by calling our C extension functions.
Example 22-11. PP3E\Integrate\Extend\Cenviron\envmap.py
To use this module, clients may import its Env object using Env['var'] dictionary syntax to refer to environment variables. And Example 22-12 exports the functions as qualified attribute names rather than as callsvariables are referenced with Env.var attribute syntax. The main point to notice here is that you can graft many different sorts of interface models on top of extension functions by providing Python wrappers on top of the extension's C wrappers.
Example 22-12. PP3E\Integrate\Extend\Cenviron\envattr.py
22.7.2. But Don't Do That EitherSWIG
You can manually code extension modules like we just did, but you don't necessarily have to. Because this example really just wraps functions that already exist in standard C libraries, the entire cenviron.c C code file in Example 22-9 can be replaced with a simple SWIG input file that looks like Example 22-13.
Example 22-13. PP3E\Integrate\Extend\Swig\Environ\environ.i
And you're done. Well, almost; you still need to run this file through SWIG and compile its output. As before, simply add a SWIG step to your makefile and compile its output file into a shareable object, and you're in business. Example 22-14 is a Cygwin makefile that does the job.
Example 22-14. PP3E\Integrate\Extend\Swig\Environ\makefile.environ-swig
When run on environ.i, SWIG generates two files and two modulesenviron.py (the Python interface module we import) and environ_wrap.c (the lower-level glue code module file we compile). Because the functions being wrapped here live in standard linked-in C libraries, there is nothing to combine with the generated code; this makefile simply runs SWIG and compiles the wrapper file into a C extension module, ready to be imported:
.../PP3E/Integrate/Extend/Swig/Environ$ make -f makefile.environ-swig swig -python environ.i gcc environ_wrap.c -g -I/usr/include/python2.4 -L/usr/bin -lpython2.4 -shared -o _environ.dll
And now you're really done. The resulting C extension module is linked when imported, and it's used as before (except that SWIG handled all the gory bits):
.../PP3E/Integrate/Extend/Swig/Environ$ ls _environ.dll environ.py makefile.environ-swig environ.i environ_wrap.c .../PP3E/Integrate/Extend/Swig/Environ$ python >>> import environ >>> environ.getenv('USER') 'Mark Lutz' >>> temp = 'USER=gilligan' # use C lib call pattern now >>> environ.putenv(temp) # temp required in Cygwin 0 >>> environ.getenv('USER') 'gilligan' >>> environ._ _name_ _, environ._ _file_ _, environ ('environ', 'environ.py', <module 'environ' from 'environ.py'>) >>> dir(environ) [ ... '_environ', 'getenv', 'putenv' ... ]