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])