【问题标题】:Why metaclass should inherit from type?为什么元类应该从类型继承?
【发布时间】:2019-02-18 23:54:26
【问题描述】:

我们可以将函数用作元类,我的理解是它们不是从类型派生的,如下所示:

def test_meta(name, bases, atts):
    print("testmeta called for " + name)
    return type(name,bases,atts);

class Computer:
    __metaclass__ = test_meta
    def __init__(self,brand,model,price):
        self.brand = brand
        self.model = model
        self.price = price

#
#
ob = Computer('p1','core2duo',21985.25)

但是,当我们编写元类时,它应该从类型继承,我无法理解这背后的原因:

class MyMeta:
    def __new__(meta, name, bases, dct):
        print ('-----------------------------------')
        print ("Allocating memory for class", name)
        print (meta)
        print (bases)
        print (dct)
        return type.__new__(meta, name, bases, dct)

    def __init__(cls, name, bases, dct):
        print ('-----------------------------------')
        print ("Initializing class", name)
        print (cls)
        print (bases)
        print (dct)
        type.__init__(cls,name, bases, dct)

    def __call__(mcs, *args, **kwargs):
        print ('-----------------------------------')
        print ("calling class")
        print mcs

class Computer:
    __metaclass__ = MyMeta
    def __init__(self,brand,model,price):
        self.brand = brand
        self.model = model
        self.price = price

#
#
ob = Computer('p1','core2duo',21985.25)

例如在上面的代码中,当我们显式调用类型函数时,我无法理解为什么 MyMeta 类应该从 type 继承,即 新建、初始化调用。 此外,只有在创建 i=instance (ob) 时才会出现错误“descriptor 'init' requires a 'type' object but received a 'instance'”。

【问题讨论】:

  • print(type(ob)) -> NoneType。你刚刚破坏了打字机中的某些东西。

标签: python metaclass


【解决方案1】:

在普通的 Python 代码中,真正创建内存中类对象的唯一调用是type.__new__,具有二进制结构和所需的所有字段。使用像元类一样可调用的函数只需要实例化type 本身,或者创建根本不是类的东西(见下文)。

可以使用本机 C 代码甚至在纯 Python 中创建另一个“基本元类”,调用本机操作系统内存分配函数并填写相同的 structure defined for a "type object",但是,这只会完全完成type.__new__ 已经完成了相同的任务,因此它只是一个复杂且容易出错的轮子重新发明,无法进一步改进,因为生成的二进制布局必须与该结构中定义的相同(即方法的指针实现“魔术功能”,如__init____geitem__ 等,必须与该结构中的偏移量相同)

(额外的字段,甚至是这种结构中数据的精细值都可以通过代码继承类型来实现——因此无论如何都可以通过普通的元类来实现)

正如您所说,任何可调用对象——即使是一个简单的函数,都可以在类主体上表示为“元类”(在 Python 2 和 Python 3 中)——但是,无论这个可调用对象做什么,在某一点上它将不得不调用type.__new__(函数通过调用type间接地做到这一点)。但是,一旦构建了该类,它本身就是一个 Python 对象,它是一个类的一个实例——该类的类型(它的 __class__ 属性中的内容)是它有效的元类。如果元类在代码中指向 __metaclass__ 属性或作为 Python 3 中的元类 kwarg,是 type 的子类,那也将是元类本身。否则,如果它是一个普通函数,只是充当类工厂,在其主体中调用type,则有效的元类只是类型。

换句话说:

In [1]: def function_meta(name, bases, ns):
   ...:     return type(name, bases, ns)
   ...: 

In [2]: class test(metaclass=function_meta):
   ...:     pass
   ...: 

In [3]: type(test)
Out[3]: type

In [4]: class ClassMeta(type):
   ...:     pass
   ...: 
   ...: 

In [5]: class test2(metaclass=ClassMeta):
   ...:     pass
   ...:

In [6]: type(test2)
Out[6]: __main__.ClassMeta

那么,为什么不能使用不继承自type 的类?

问题不是指定的元类不是从类型继承的——任何可调用的都可以使用,如上所示。 尝试执行此操作时出现的错误是由于调用 type.__new__ 时使用类型的非子类作为第一个参数:

In [11]: class InheritFromOther(object):
    ...:     def __new__(mcls, name, bases, ns):
    ...:         return type.__new__(mcls, name, bases, ns)
    ...:         # the line above is the one that errors - as
    ...:         # mcls here is not a subclass of type.
    ...:         # type(name, bases, ns) would work.

In [12]: class test4(metaclass=InheritFromOther):
    ...:     pass
    ...: 
    ...: 
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-12-bf5b1fa4bb7f> in <module>()
----> 1 class test4(metaclass=InheritFromOther):
      2     pass

<ipython-input-11-62d1fe46490b> in __new__(mcls, name, bases, ns)
      1 class InheritFromOther(object):
      2     def __new__(mcls, name, bases, ns):
----> 3         return type.__new__(mcls, name, bases, ns)
      4 

TypeError: type.__new__(InheritFromOther): InheritFromOther is not a subtype of type

现在,如果我们调用type.__new__ 并使用类型的有效子类作为第一个参数:

In [13]: class InheritFromOtherTake2(object):
    ...:     def __new__(mcls, name, bases, ns):
    ...:         return type.__new__(type, name, bases, ns)
    ...:     

In [14]: class test5(metaclass=InheritFromOtherTake2):
    ...:     pass
    ...: 

In [15]: type(test5)
Out[15]: type

为了完整性,如上所述,用作元类的可调用对象确实有可能返回类型实例(或其子类)以外的东西。在这种情况下,class 语句体产生的对象根本就不是一个类,而是任何可调用返回的对象:

In [7]: def dict_maker_meta(name, bases, ns):
   ...:     return ns
   ...: 

In [8]: class test3(metaclass=dict_maker_meta):
   ...:     a = 1
   ...:     b = 2
   ...:     c = 'test'
   ...:     

In [9]: type(test3)
Out[9]: dict

In [10]: test3
Out[10]: 
{'__module__': '__main__',
 '__qualname__': 'test3',
 'a': 1,
 'b': 2,
 'c': 'test'}

【讨论】:

  • 感谢这么详细的解释
猜你喜欢
  • 2011-01-10
  • 1970-01-01
  • 2016-09-02
  • 2013-09-26
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-09-24
  • 2011-06-11
相关资源
最近更新 更多