【问题标题】:Python - metaclass changes variable scope from instance to classPython - 元类将变量范围从实例更改为类
【发布时间】:2019-12-09 06:01:50
【问题描述】:

运行以下代码:

class NonMeta:
    def __new__(cls):
        x = super().__new__(cls)
        x.attr = 100
        return x


class Meta(type):
    def __new__(mcs, name, bases, dct):
        x = super().__new__(mcs, name, bases, dct)
        x.attr = 100
        return x


class WithMeta(metaclass=Meta):
    pass


print(WithMeta.attr)
print(NonMeta.attr)

结果

/usr/bin/python3.7 /home/lookash/PycharmProjects/PythonLearning/classes.py
100
Traceback (most recent call last):
  File "/home/lookash/PycharmProjects/PythonLearning/classes.py", line 20, in <module>
    print(NonMeta.attr)
AttributeError: type object 'NonMeta' has no attribute 'attr'

为什么 WithMeta 类中的 attr 是类变量,而 NonMeta 类中的 attr 是实例变量?

【问题讨论】:

    标签: python metaclass


    【解决方案1】:

    NonMeta.__new__内部,xNonMeta的一个实例,所以对x.attr的赋值就是为实例x创建一个实例属性。

    Meta.__new__内部,xMeta的一个实例,即一个新的类。在这种情况下,x 是类 WithMeta,因此对 x.attr 的赋值会创建一个类属性。

    class 语句在某种意义上是调用元类型的语法糖。也就是说,

    class WithMeta(metaclass=Meta):
        pass
    

    等价于

    WithMeta = Meta('WithMeta', (object,), {})
    # == Meta.__new__(Meta, 'WithMeta', (object,), {})
    

    【讨论】:

      【解决方案2】:

      元类的实例是声明它们将使用它的类(及其子类)。所以当你在Meta.__new__ 中做x.attr = 100 时,x 是一个类(元类的一个实例,在这种情况下是WithMeta)。

      这与在NonMeta.__new__ 中运行同一行时不同,其中xNonMeta 的一个实例,并且类本身不会被分配attr 属性。

      因此,最好将__new__ 方法中的代码更改为使用与x 不同且更好的名称。在NonMeta 中,一个好的名称可能是self,因为这是方法中实例的传统名称。在Meta 中,您可以使用cls,因为实例将是一个类。

      【讨论】:

        【解决方案3】:

        没有元类的等效代码与您正在执行的操作使用元类很简单:

        class NonMeta:
            x = 100
        

        类中定义的__new__ 方法仅在创建该类的实例时调用——它不会运行该类。在这段代码中:

        class NonMeta:
            def __new__(cls):
                x = super().__new__(cls)
                x.attr = 100
                return x
        

        super().__new__(cls) 返回并存储在x 中的只是实例本身 - 以名称self 传递给常规实例方法(包括__init__)的同一个对象。

        另一方面,元类在创建类时运行其__new__(和__init__)方法。在一个普通的模块导入中,这发生在 class 语句及其主体被执行时。在主体的末尾,Python 运行时拥有创建新类所需的所有信息,并调用元类的__new__。无论它返回什么成为类。并且由于它的返回值设置了x 属性,因此它已经在类中可见。

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2015-11-15
          • 2011-08-09
          • 1970-01-01
          • 2013-11-09
          • 2014-04-18
          • 2011-08-24
          相关资源
          最近更新 更多