Re-executing the code object from a class 'declaration'

Alex Martelli aleaxit at yahoo.com
Wed Oct 6 08:06:23 EDT 2004


Carlos Ribeiro <carribeiro at gmail.com> wrote:
   ...
> As part of a pet project of mine, I needed to find a way to re-execute
> a class statement. The reason behind it was simple: the class
> statement in question had some external dependencies that could
> potentially change during the lifetime of the program. I needed to be
> able to execute the class statement block again to produce new
> classes (in opposition to new instances, which would not solve my
> problem)[1].

Interesting problem.


> I solved the problem using inspect to find the current code object
> *inside* the class statement, and stored it in an attribute of the
> class itself. Then, a method of the class can be used to re-create it.
> A metaclass-enabled version is shown below:
> 
> class Meta(type):
>    def __new__(klass, name, bases, dct):
>        dct['my_code'] = inspect.currentframe(1).f_code
>        return type(name, bases, dct)
> 
> class MyClass:
>    __metaclass__ = Meta
>    creation_time = time.ctime()
>    print "I'm inside my class definition!"
>    def re_execute(klass):
>        exec klass.my_code
>        return MyClass
>    re_execute = classmethod(re_execute)

Urk.  Ahem, I mean, interesting solution.  I _think_ it poses certain,
ahem, problems, such as infinite recursion if a call to re_execute is in
the same code object as that where the 'class MyClass' is, for example
-- because basically you ARE re-executing the whole kaboodle.  You're
not just re-executing the 'class' statement, but everything that comes
before or after it in the same containing codeobject.  Your tests in an
interactive interpreter are tricky that way, because in that special
environment a code object is a tiny little thing.  But stick this into a
module and place a dis.dis(dct['my_code']) in the metaclass's __new__
and you'll see that basically MyClass.re_execute isn't very different
from a reload of the module.  Isn't that a _bit_ too much in general?

The code object for the body of the class can also be found (with some
work): it will be among the co_consts of what you've stored as
dct['my_code'], it will be an instance of types.CodeType, and its
co_name will be the same as the 'name' parameter to Meta.__new__.  If
you have more than one code object with those characteristics, e.g. two
classes with the same name at the same module's toplevel, you do have
some trouble.  But otherwise, you can build a function, with
new.function(thatclassbodycodeobject, theglobalsfrominspect), which,
when called (w/o arguments), reexecutes the classbody and returns the
same kind of dictionary that Meta.__new__ is receiving as its 'dct'
argument.  Wouldn't this be preferable to re-executing the whole module
body, perhaps?

> Implementation notes:
> 
> 1) The exec statement actually binds the name to the same original
> name, but in the current local scope. That's why it does a 'return
> MyClass' in the re_execute() method. The MyClass symbol refers to the
> local symbol with this name that was just re-created by the exec
> method.

This also fails if you have multiple uses of name MyClass at toplevel of
the same module -- it actually fails more often than my suggestions
above, e.g if your module is:

class MyClass:
   ... etc ...
FakeName = MyClass
MyClass = 23

then your FakeName.re_execute() will return 23, while the approach I
suggest will be able to recover in this case (the codeobject in the
co_consts of the module's code object will still be the only one in that
container that's named 'MyClass').


> 2) If you call the exec from the same scope where the class was
> originally created, then it is automatically bound to the same name:

Same issue as above -- you're getting closer and closer to a module's
reload (save in the anomalous case where you type the whole class
statement at an interactive prompt;-).  Note that it IS quite possible
that what you want to do IS in fact to reload the module, period, but,
if so, then the reload statement might be handier.

> 3) I thought that the same technique could be made to work without a
> metaclass, but I couldn't make it work. I haven't invested much time
> to understand why -- it just works as implemented above. (It seems
> that it does not bind correctly to the original name as it does if you
> use the metaclass)

It appears to me, but I haven't tested this, that putting the inspect
introspection in the classbody itself should be a sounder technique, as
it should be able to find itself and stash itself away more reliably
than just based on name (which is the best the metaclass can do).  The
name binding issue is separate, however.

> [1] Think about it as a templating mechanism, similar to the C++ one,
> but for Python; this mechanism generates new classes that can be
> instantiated as many times as needed. It's not the same thing as to
> customize a instance during its initialization.

I agree, there's one metalevel of difference between instantiating a
class to make an instance, and instantiating a metaclass to make a
class.


Alex



More information about the Python-list mailing list