python修改整个程序的__metaclass__

时间:2009-12-01 21:55:06

标签: python metaprogramming metaclass python-2.x

编辑:请注意,在生产代码中这是一个非常糟糕的想法。这对我来说只是一个有趣的事情。不要在家里这样做!

是否可以在Python中修改整个程序(解释器)的__metaclass__变量?

这个简单的例子正在起作用:

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

但我希望能够改变__metaclass__甚至在子模块中工作。例如(文件m1.py):

 class A:
       pass

 a=A()
 print a

文件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

我理解全局__metaclass__不再适用于Python 3.X,但这不是我关心的问题(我的代码是否有概念验证)。那么有没有办法在Python-2.x中实现这一点?

3 个答案:

答案 0 :(得分:7)

Python 2的“全局__metaclass__”功能仅用于按模块工作(只是想想它会造成什么样的破坏,否则,通过在所有库和第三方模块上强制使用自己的元类来实现你从那一点开始进口 - 不寒而栗!)。如果你“偷偷”改变你从某一点开始导入的所有模块的行为非常重要,不管是什么样的斗篷和匕首,你都可以用导入钩子玩非常肮脏的技巧(最糟糕的是首先将来源复制到临时位置,同时改变它们......)但是努力将与契约的严重程度成比例,这似乎是合适的; - )

答案 1 :(得分:4)

好; IMO这是一个毛茸茸的黑暗魔法。你不应该使用它,也许永远不会,但特别是在生产代码中。然而,仅仅出于好奇心,它有点有趣。

您可以使用PEP 302中描述的机制编写自定义导入程序,并在Doug Hellmann的PyMOTW: Modules and Imports中进一步讨论。这为您提供了完成预期任务的工具。

我实施了这样的进口商,只是因为我很好奇。基本上,对于您通过类变量__chatty_for__指定的模块,它会在导入模块的__metaclass__之前在中插入自定义类型作为__dict__变量>评估代码。如果有问题的代码定义了自己的__metaclass__,那将取代导入者预先插入的代码。在仔细考虑它会对他们做什么之前,将这个进口商应用于任何模块是不明智的。

我没有写过很多进口商,所以在撰写本文时我可能做过一件或多件傻事。如果有人注意到我在实施过程中遗漏的缺陷/角落,请发表评论。

源文件1:

# foo.py
class Foo: pass

源文件2:

# bar.py
class Bar: pass

源文件3:

# baaz.py
class Baaz: pass

和主要事件:

# 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

答案 2 :(得分:1)

不。 (这是一个功能!)