Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

What is the advantage in using `exec` over `type()` when creating classes at runtime?

I want to dynamically create classes at runtime in python.

For example, I want to replicate the code below:

>>> class RefObj(object):
...     def __init__(self, ParentClassName):
...         print "Created RefObj with ties to %s" % ParentClassName
... class Foo1(object):
...     ref_obj = RefObj("Foo1")
... class Foo2(object):
...     ref_obj = RefObj("Foo2")
... 
Created RefObj with ties to Foo1
Created RefObj with ties to Foo2
>>>

... but I want the Foo1, Foo2, Foo classes to be created dynamically (ie: during execution instead of on first-pass compile).

One way to achieve this is with type(), like so:

>>> class RefObj(object):
...     def __init__(self, ParentClassName):
...         print "Created RefObj with ties to %s" % ParentClassName
... def make_foo_class(index):
...     name = "Foo%s" % index
...     return type(name, (object, ), dict(ref_obj = RefObj(name)))
... 
>>> Foo1 = make_foo_class(1)
Created RefObj with ties to Foo1
>>> Foo2 = make_foo_class(2)
Created RefObj with ties to Foo2
>>> type(Foo1()), type(Foo2())
(<class 'Foo1'>, <class 'Foo2'>)

I can also achieve it with exec, like so:

>>> class RefObj(object):
...     def __init__(self, ParentClassName):
...         print "Created RefObj with ties to %s" % ParentClassName
... def make_foo_object(index):
...     class_template = """class Foo%(index)d(object):
...         ref_obj = RefObj("Foo%(index)d")
...         """ % dict(index = index)
...     global RefObj
...     namespace = dict(RefObj = RefObj)
...     exec class_template in namespace
...     return namespace["Foo%d" % index]
... 
>>> Foo1 = make_foo_object(1)
Created RefObj with ties to Foo1
>>> Foo2 = make_foo_object(2)
Created RefObj with ties to Foo2
>>> type(Foo1()), type(Foo2())
(<class 'Foo1'>, <class 'Foo2'>)

The use of exec doesn't sit well with me (as I expect it doesn't with a lot of people who read this question) but exec is exactly how python's collections.namedtuple() class is implemented (see this line). Also very relevant is the defense of this use of exec here, by the creator of the class (Raymond Hettinger). In this defense, it is stated that "It is a key feature for named tuples that they are exactly equivalent to a hand-written class", which one might take to imply that the use of type() is not as good as using exec...

Is there a difference? Why use exec vs type()?

I expect the answer may be that both ways are the same and it is simply that the namedtuple implementation has a lot of namedtuple variables peppered through it, and doing this with dynamically generate closures for all methods made the code get unwieldy, but I want to know if there is something more to this.

Regarding my discomfort with exec, I do recognize that if there is no way whatsoever for untrusted parties to inject nefarious code into it, it should be fine... it is just ensuring that that makes me nervous.

like image 583
Russ Avatar asked Oct 06 '11 15:10

Russ


3 Answers

I would recommend type over exec here.

In fact, the class statement is just syntactic sugar for a call to type: The class body is executed within its own namespace, which is then passed on to the metaclass, which defaults to type if no custom metaclass is specified.

This approach is less errorprone since there is no need to parse code at runtime, and might even be a bit faster.

like image 103
Ferdinand Beyer Avatar answered Oct 22 '22 01:10

Ferdinand Beyer


Why not just create a class in a function?

def foo_factory(index):
    name = 'Foo%d' % index

    class Foo(object):
        ref_obj = RefObj(name)

    Foo.__name__ = name
    return Foo
like image 40
yak Avatar answered Oct 22 '22 00:10

yak


There is no disadvantage to using type() over exec. I think Raymond's defense is a bit defensive. You have to choose the technique that you find most readable and understandable. Both ways create confusing code.

You should try really hard to avoid code that creates classes in the first place, that would be best.

like image 2
Ned Batchelder Avatar answered Oct 22 '22 01:10

Ned Batchelder