Constantize in Python

Rails offers a nice function called constantize. It is easy to rebuild this (to some extend) in python. But the price is too high.

I agree, it looks very sexy to write in rails:

my_str = "MyClass"
my_instance = my_str.constantize.new 

So how would you do this in python? We could just use the eval-function:

import inspect, re

ClassRegex = re.compile("[a-zA-Z_][a-zA-Z0-9_]*")

class ConstantizeException(Exception):
  pass

def constantize(s):
  if not ClassRegex.match(s):
    raise ConstantizeException("`%s' contains illegal characters." % str(s))
  klass = eval(str(s))
  if not inspect.isclass(klass): raise ConstantizeException("`%s' does not refer to a class." % str(s))
  return klass

Compare it to the ruby implementation:

  def constantize(camel_cased_word)
    unless /\A(?:::)?([A-Z]\w*(?:::[A-Z]\w*)*)\z/ =~ camel_cased_word
      raise NameError, "#{camel_cased_word.inspect} is not a valid constant name!"
    end

    Object.module_eval("::#{$1}", __FILE__, __LINE__)
  end

So let’s try it:

>>> class A:
...   def __repr__(self):
...     return "I'm A."
...
>>> klass=constantize("A")
>>> instance_of_a = klass()
>>> instance_of_a
I'm A.

Seems to work. The syntax is not as nice as in rails, since we can’t just monkey patch the string class.

Security

As noted in another post constantize is prone to code injections. We have two protections built in. The regular expression makes sure that eval does not execute arbitrary code while the inspect.isclass insures that only classes are constantized. Otherwise also functions could be returned:

>>> def somefunc():
...   print "called somefunc"
...
>>> klass=constantize("somefunc")
Traceback (most recent call last):
  File "", line 1, in 
  File "", line 5, in constantize
__main__.ConstantizeException: `somefunc' does not refer to a class.

It would be still reckless to use the above constantize on possibly malicious data.

You need to strongly limit the strings that your application may constantize. A straight forward solution would be:

s = "MyClass"
if s in ["MyClass", "SomeOtherNiceClass"]: klass = constantize(s)
else: raise "Unexpected class `%s' " % s

How should you do it?

But then why don’t you use some completely differrent method from the very begining? What about a dictionary?

class A:
  def __repr__(self):
    return "I'm A."

class B:
  def __repr__(self):
    return "I'm B."

str2class = { "A": A, "B": B }

It is very easy to see which classes are allowed to be constantized and there is no room for security issues in your python code:

>>> str2class = { "A":A, "B":B }
>>> s = "A"
>>> klass = str2class[s]
>>> a = klass()
>>> a
I'm A.

A nice side effect is the improved execution speed:

>>>> import timeit
>>> t=timeit.Timer("""a=constantize("A")""","from __main__ import constantize")
>>> print t.timeit()
18.7163159847
>>>
>>> t=timeit.Timer("""a=str2class["A"]""","from __main__ import str2class")
>>> print t.timeit()
0.148332118988

If you are lazy you can use the following function to fill your dictionary:

def build_str2class(lst):
  d = {}
  for klass in lst:
    d[klass.__name__]=klass
  return d

str2class = build_str2class([A,B])