views:

1129

answers:

3

Hi! I'm working on a web application that will return a variable set of modules depending on user input. Each module is a Python class with a constructor that accepts a single parameter and has an '.html' property that contains the output.

Pulling the class dynamically from the global namespace works:

result = globals()[classname](param).html

And it's certainly more succinct than:

if classname == 'Foo':
    result = Foo(param).html
elif classname == 'Bar':
    ...

What is considered the best way to write this, stylistically? Are there risks or reasons not to use the global namespace?

+4  A: 

First of all, it sounds like you may be reinventing the wheel a little bit... most Python web frameworks (CherryPy/TurboGears is what I know) already include a way to dispatch requests to specific classes based on the contents of the URL, or the user input.

There is nothing wrong with the way that you do it, really, but in my experience it tends to indicate some kind of "missing abstraction" in your program. You're basically relying on the Python interpreter to store a list of the objects you might need, rather than storing it yourself.

So, as a first step, you might want to just make a dictionary of all the classes that you might want to call:

dispatch = {'Foo': Foo, 'Bar': Bar, 'Bizbaz': Bizbaz}

Initially, this won't make much of a difference. But as your web app grows, you may find several advantages: (a) you won't run into namespace clashes, (b) using globals() you may have security issues where an attacker can, in essence, access any global symbol in your program if they can find a way to inject an arbitrary classname into your program, (c) if you ever want to have classname be something other than the actual exact classname, using your own dictionary will be more flexible, (d) you can replace the dispatch dictionary with a more-flexible user-defined class that does database access or something like that if you find the need.

The security issues are particularly salient for a web app. Doing globals()[variable] where variable is input from a web form is just asking for trouble.

Dan
+5  A: 

A flaw with this approach is that it may give the user the ability to to more than you want them to. They can call any single-parameter function in that namespace just by providing the name. You can help guard against this with a few checks (eg. isinstance(SomeBaseClass, theClass), but its probably better to avoid this approach. Another disadvantage is that it constrains your class placement. If you end up with dozens of such classes and decide to group them into modules, your lookup code will stop working.

You have several alternative options:

  1. Create an explicit mapping:

     class_lookup = {'Class1' : Class1, ... }
     ...
     result = class_lookup[className](param).html
    

    though this has the disadvantage that you have to re-list all the classes.

  2. Nest the classes in an enclosing scope. Eg. define them within their own module, or within an outer class:

    class Namespace(object):
        class Class1(object):
            ...
        class Class2(object):
            ...
    ...
    result = getattr(Namespace, className)(param).html
    

    You do inadvertantly expose a couple of additional class variables here though (__bases__, __getattribute__ etc) - probably not exploitable, but not perfect.

  3. Construct a lookup dict from the subclass tree. Make all your classes inherit from a single baseclass. When all classes have been created, examine all baseclasses and populate a dict from them. This has the advantage that you can define your classes anywhere (eg. in seperate modules), and so long as you create the registry after all are created, you will find them.

    def register_subclasses(base):
        d={}
        for cls in base.__subclasses__():
            d[cls.__name__] = cls
            d.update(register_subclasses(cls))
        return d
    
    
    class_lookup = register_subclasses(MyBaseClass)
    

    A more advanced variation on the above is to use self-registering classes - create a metaclass than automatically registers any created classes in a dict. This is probably overkill for this case - its useful in some "user-plugins" scenarios though.

Brian
A: 

Another way to build the map between class names and classes:

When defining classes, add an attribute to any class that you want to put in the lookup table, e.g.:

class Foo:
    lookup = True
    def __init__(self, params):
        # and so on

Once this is done, building the lookup map is:

class_lookup = zip([(c, globals()[c]) for c in dir() if hasattr(globals()[c], "lookup")])
Robert Rossney