Python namespace hack

If you import a module into your Python program an isolated namespace is built for it which doesn’t contain objects from the caller’s (importer’s) namespace. While this is a lot better than PHP’s everything in single namespace approach, it sometimes gets in my way too.

For example I have an application where I want an imported module to see some things from the importer’s namespace.
The application is basically a web app server written entirely in Python and it imports modules that implement specific applications.
A custom namespace is built for the modules before import and it contains some dynamic objects that are
managed by the core server – for example a public API object that all the modules can register their own API functions into, database connection pools etc.
All is good as long as the module lives in a single file but if it consists of several files it has to do imports in extravagant ways (like the server itself imported the modules) to pass these global server objects along correctly.

Here’s a simplified example:

server.py

api = {'test': 1}
 
def build_app_ns():
    return {'api': api}
 
execfile('app.py', build_app_ns())

app.py

import app_views
print "APP:",api

app_views.py

print "APP VIEWS:",api
$ python server.py
APP VIEWS:
Traceback (most recent call last):
  File "server.py", line 6, in 
    execfile('app.py', build_app_ns())
  File "app.py", line 1, in 
    import app_views
  File "app_views.py", line 1, in 
    print "APP VIEWS:",api
NameError: name 'api' is not defined

Luckily it seems to be Python’s philosophy that ugly things are not impossible but just hard to do and discouraged so I implemented a namespace helper function that I can use to import any variables from the namespaces present in the call stack, to my own namespace.

import inspect
 
def import_parent_vars(varnames):
    """Import variables with names specified in the varnames to the local namespace.
 
    We start the search from the outermost stack frame and move inwards.
    @arg varnames: a list of strings or a string containing the variable names that should be imported to the   
    caller's namespace.
    """
    if isinstance(varnames, basestring):
        varnames = [varnames]
    stack = inspect.stack()
    try:
        caller_frame = stack[1][0]
        for f in reversed(stack[2:]):
            fobj = f[0]
            for v in varnames:
                if v in fobj.f_locals:
                    caller_frame.f_locals[v] = fobj.f_locals[v]
                    varnames.remove(v)
    finally:
        del stack

Now if we use this helper function from the app_views.py to get the variable api into its namespace everything works as desired:

app_views.py

from ns_helpers import import_parent_vars
import_parent_vars('api')
 
print "APP VIEWS:",api
$ python server.py
APP VIEWS: {'test': 1}
APP: {'test': 1}

The need for this namespace hack can probably be removed altogether if we just refactored all the shared stuff into a separate module and imported that from all the application files but it would require a lot more testing and the restructuring of our existing code.

Leave a Reply

Your email address will not be published.


*