Class 静态方法与 class 方法的变量作用域

Class variable scope for static vs class methods

我在 python class 变量上发现了一个奇怪的行为(至少对我来说很奇怪)。

class Base(object):
    _var = 0

    @classmethod
    def inc_class(cls):
        cls._var += 1

    @staticmethod
    def inc_static():
        Base._var += 1

class A(Base):
    pass

class B(Base):
    pass

a = A()
b = B()

a.inc_class()
b.inc_class()
a.inc_static()
b.inc_static()

print(a._var)
print(b._var)
print(Base._var)

输出为1 1 2

这让我感到惊讶(我期待 4 4 4),我想知道为什么?

当用 @classmethod 修饰时,clsinc_class(cls) 的第一个参数是 class。 <class '__main__.A'><class '__main__.B'> 分别代表 AB。所以 cls._var 指的是 A_varB 也是如此。在用 @staticmethod 修饰的 inc_static 中没有参数,您明确指的是 <class '__main__.Base'>,一个不同的 _var.

请注意 BaseA__dict__ 中的 '_var': 0 属性。 @classmethod 正在做您期望它做的事情,将成员绑定到 classes,在本例中为 AB.

>>> Base.__dict__
mappingproxy({'__module__': '__main__', '_var': 0, 'inc_class': <classmethod 
object at 0x7f23037a8b38>, 'inc_static': <staticmethod object at 
0x7f23037a8c18>, '__dict__': <attribute '__dict__' of 'Base' objects>, 
'__weakref__': <attribute '__weakref__' of 'Base' objects>, '__doc__': None})

>>> A.__dict__
mappingproxy({'__module__': '__main__', '__doc__': None})`

调用后Base.inc_static():

>>> Base.__dict__
mappingproxy({'__module__': '__main__', '_var': 1, 'inc_class': 
<classmethod object at 0x7f23037a8b38>, 'inc_static': <staticmethod 
object at 0x7f23037a8c18>, '__dict__': <attribute '__dict__' of 'Base' 
objects>, '__weakref__': <attribute '__weakref__' of 'Base' objects>, 
'__doc__': None})

>>> A.__dict__
mappingproxy({'__module__': '__main__', '__doc__': None})

调用后A.inc_class():

>>> Base.__dict__
mappingproxy({'__module__': '__main__', '_var': 1, 'inc_class': 
<classmethod object at 0x7f23037a8b38>, 'inc_static': <staticmethod 
object at 0x7f23037a8c18>, '__dict__': <attribute '__dict__' of 'Base' 
objects>, '__weakref__': <attribute '__weakref__' of 'Base' objects>, 
'__doc__': None})

>>> A.__dict__
mappingproxy({'__module__': '__main__', '__doc__': None, '_var': 1})

有趣的是 A_var 是如何初始化的。请注意,在定义 cls._var 之前执行 cls._var += 1。如 here, cls._var += 1 is equivalent to cls._var = cls._var; cls._var += 1. Because of the way python does lookup 所述,第一次读取 cls._var 将在 A 中失败并继续在 Base 中找到它。在赋值时,_var 被添加到 A__dict__ 中,值为 Base._var,然后一切正常。

>>> class Base(object):
...     _var = 10
...     @classmethod
...     def inc_class(cls):
...         cls._var += 1
... 
>>> class A(Base):
...     pass
... 
>>> A.__dict__
mappingproxy({'__module__': '__main__', '__doc__': None})
>>> A.inc_class()
>>> A.__dict__
mappingproxy({'__module__': '__main__', '__doc__': None, '_var': 11})

尽管这两个 class 继承自 Base class,但它们是完全不同的对象。通过 ab 的实例化,您有两个对象属于两个单独的 classes。当你打电话

a.inc_class()
b.inc_class()

您将 class A 的 _var 属性递增一次,然后对 class B 执行相同的操作。尽管它们共享相同的名称,但它们是不同的对象。如果您有 class A 的第二个实例,比如 a2,并且您将再次调用该函数,那么这两个调用将操作同一个变量。这解释了如何获得前两个输出。

第三个输出是指 Base class 对象。同样,即使名称相同,它也是不同的对象。您将第三个对象递增两次,因此您得到 2 作为答案。