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])
Share and Enjoy:
  • description
  • Reddit
  • Digg
  • Google
  • del.icio.us
  • MisterWong

4 Comments

  1. [...] I also have some notes on Constantize in Python. Share and [...]

  2. Justin says:

    or you could just use globals()["MyClass"]

  3. Henryk says:

    @Justin
    Thank you for your comment. Your proposition looks better than an "eval",
    but you still need to check whether the class is "allowed".

  4. Justin says:

    well, being able to obtain a reference to an object in the global namespace using its name as a string is orthogonal to deciding what you want to do with it. I was just providing a better alternative to using regular expressions and eval().

    you could easily maintain a whitelist of names that you will allow lookups for. i.e.

    whitelist = ['A','B']

    def str2class(str):
    return str in whitelist and globals()[str]

Leave a Reply