【问题标题】:Is modifying a metaclass from an external module possible?是否可以从外部模块修改元类?
【发布时间】:2015-05-13 11:35:01
【问题描述】:

让我们有一个名为fields.py 的外部模块,它带有一个元类FieldModelMetaclass,它改变了类FieldModel

# module fields.py
class FieldModelMetaclass(type):
    def __new__(cls, name, bases, attrs):
        # code to alter class creation

class FieldModel(object):
    __metaclass__ = FieldModelMetaclass
    print 'run at parse time BEFORE metaclass applies'

# module consumer.py
import fields

def adjust_metaclass_new(cls, name, bases, attrs):
  # do an extra work on class `cls' and attributes `attrs'

# Is somehow possible from here alter `FieldModelMetaclass.__new__`
# by injection of adjust_metaclass_new to modify FieldModel
# before it gets instantiated ?
mymodel = fields.FieldModel()

据我所知,类的元类在编译运行时生效,所以在我导入定义的模块时,类已经受到影响。

如何在外部模块创建类时截取元类的影响?

【问题讨论】:

  • 元类在运行时生效,而不是在编译时生效。但是,由于它是在在同一个模块中使用,因此在加载类语句时,您无法在元类已经被使用之前拦截它。
  • @MartijnPieters 哦,是的,我现在看到了 :) 当然 :)
  • @MartijnPieters 所以我要注入Class.__new__ 方法,以便在每个类实例化时调用它吗?
  • @MartijnPieters 谢谢。与直接修改元类定义相反,是否存在一些性能损失?
  • @DavidUnric:这取决于您在此处实际尝试做什么。每次创建实例时运行额外的 Python 代码显然比每次创建类时运行代码的“成本”要高。

标签: python metaprogramming


【解决方案1】:

在这种情况下,您不能轻易拦截 MetaClass 的使用。 FieldModelMetaclass.__new__ 方法在 class FieldModel 主体完成后立即调用,在导入模块时(所以在运行时,而不是编译时)。

理论上,您可以在创建元类之后但在执行 class 语句之前使用 sys.setrace() hook 注入额外的 Python 代码,但这种方式很疯狂。

另一个理论上的选择是解析模块源代码,重写它(使用ast module),但同样,这种方式更疯狂。

如果您只想拦截新实例的创建,您可以将__call__ 方法添加到元类或将__new__ 方法添加到使用元类创建的每个类:

import fields

def metaclass__call__(cls, *args, **kw):
    instance = super(FieldModelMetaclass, cls).__call__(*args, **kw)
    # do something to the instance
    return instance

FieldModelMetaclass.__call__ = metaclass__call__

如果您的目标是更改 FieldModelMetaclass 生成类对象的方式,那么您最好的选择是在导入后再次更改类对象。您可以随时向类添加更多属性,或使用您自己的元类将类替换为不同的类。

【讨论】:

  • 哦,谢谢你的回答。我无法想象在生产代码中使用settrace,因为它会显着降低运行时速度,因为每个表达式都会调用tracefunc。使用 AST 解析源代码看起来更加疯狂 ;) 使用 .__call__ 方法的好主意,与 __new__ 的用法相似,但将事物分开。
  • @DavidUnric: 并且有一个额外的优势,您可以在元类上设置它,并让从它派生的 所有 类使用它。
  • 附带说明,这里 Ruby 占了上风,因为重新打开和修改 any 类(不像 Python 的内置/扩展)是无限且简单的。
  • @DavidUnric:不过,它在 Python 中也是无限且简单的。您要求的是在工厂吐出产品之前对其进行更改,这在此处很难拦截。但是我们可以很容易地改变最终产品。 :-)
  • 有些答案简直就是最好的艺术!
猜你喜欢
  • 1970-01-01
  • 2012-01-19
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-01-09
  • 2014-05-11
  • 1970-01-01
  • 2011-04-21
相关资源
最近更新 更多