views:

312

answers:

3

EDIT: Note that this is a REALLY BAD idea to do in production code. This was just an interesting thing for me. Don't do this at home!

Is it possible to modify __metaclass__ variable for whole program (interpreter) in Python?

This simple example is working:

class ChattyType(type):
    def __init__(cls, name, bases, dct):
        print "Class init", name
        super(ChattyType, cls).__init__(name, bases, dct)

__metaclass__= ChattyType


class Data:
    pass

data = Data() # prints "Class init Data"
print data

but I would love to be able change of __metaclass__ to work even in submodules. So for example (file m1.py):

 class A:
       pass

 a=A()
 print a

file main.py:

class ChattyType(type):
    def __init__(cls, name, bases, dct):
        print "Class init", name
        super(ChattyType, cls).__init__(name, bases, dct)

__metaclass__= ChattyType

import m1 # and now print "Class init A"

class Data:
    pass

data = Data() # print "Class init Data"
print data

I understand that global __metaclass__ is no longer working in Python 3.X, but that is not my concern (my code if proof of concept). So is there any way to accomplish this in Python-2.x?

+1  A: 

Nope. (This is a feature!)

Mike Graham
+5  A: 

The "global __metaclass__" feature of Python 2 is designed to work per-module, only (just think what havoc it would wreak, otherwise, by forcing your own metaclass on all library and third-party modules that you imported from that point onwards -- shudder!). If it's very important to you to "secretly" alter the behavior of all modules you're importing from a certain point onwards, for whatever cloak-and-dagger reason, you could play very very dirty tricks with an import hook (at worst by first copying the sources to a temporary location while altering them...) but the effort would be proportionate to the enormity of the deed, which seems appropriate;-)

Alex Martelli
Ok, That's what I was kind of expecting. I was looking into Python + aspect orientation and this came up as an interesting way to accomplish some things.
Stan
+4  A: 

Okay; IMO this is gross, hairy, dark magic. You shouldn't use it, perhaps ever, but especially not in production code. It is kind of interesting just for curiosity's sake, however.

You can write a custom importer using the mechanisms described in PEP 302, and further discussed in Doug Hellmann's PyMOTW: Modules and Imports. That gives you the tools to accomplish the task you contemplated.

I implemented such an importer, just because I was curious. Essentially, for the modules you specify by means of the class variable __chatty_for__, it will insert a custom type as a __metaclass__ variable in the imported module's __dict__, before the code is evaluated. If the code in question defines its own __metaclass__, that will replace the one pre-inserted by the importer. It would be inadvisable to apply this importer to any modules before carefully considering what it would do to them.

I haven't written many importers, so I may have done one or more silly things while writing this one. If anyone notices flaws / corner cases I missed in the implementation, please leave a comment.

source file 1:

# foo.py
class Foo: pass

source file 2:

# bar.py
class Bar: pass

source file 3:

# baaz.py
class Baaz: pass

and the main event:

# chattyimport.py
import imp
import sys
import types

class ChattyType(type):
    def __init__(cls, name, bases, dct):
        print "Class init", name
        super(ChattyType, cls).__init__(name, bases, dct)

class ChattyImporter(object):

    __chatty_for__ = []

    def __init__(self, path_entry):
        pass

    def find_module(self, fullname, path=None):
        if fullname not in self.__chatty_for__:
            return None
        try:
            if path is None:
                self.find_results = imp.find_module(fullname)
            else:
                self.find_results = imp.find_module(fullname, path)
        except ImportError:
            return None
        (f,fn,(suf,mode,typ)) = self.find_results
        if typ == imp.PY_SOURCE:
            return self
        return None

    def load_module(self, fullname):
        #print '%s loading module %s' % (type(self).__name__, fullname)
        (f,fn,(suf,mode,typ)) = self.find_results
        data = f.read()
        if fullname in sys.modules:
            module = sys.modules[fullname]
        else:
            sys.modules[fullname] = module = types.ModuleType(fullname)

        module.__metaclass__ = ChattyType
        module.__file__ = fn
        module.__name__ = fullname
        codeobj = compile(data, fn, 'exec')
        exec codeobj in module.__dict__
        return module

class ChattyImportSomeModules(ChattyImporter):
    __chatty_for__ = 'foo bar'.split()

sys.meta_path.append(ChattyImportSomeModules(''))

import foo # prints 'Class init Foo'
import bar # prints 'Class init Bar'
import baaz
Matt Anderson
Indeed black magic, but interesting nevertheless :-)
Stan