【问题标题】:Class variable scope for static vs class methods静态与类方法的类变量范围
【发布时间】:2019-06-08 23:06:53
【问题描述】:

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

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),我想知道为什么?

【问题讨论】:

  • 如果您不告诉我们让您感到惊讶的事情,我们将很难为您提供帮助。
  • 对不起,我没有在建议的重复线程上找到任何问题的答案
  • 重复的要点是会给出相同的答案。问一个重复的问题并不是获得已经问过的问题的答案的最佳方式;赏金是。
  • 我的问题是关于类变量,而不是类和静态方法

标签: python static-methods class-method class-variables


【解决方案1】:

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

注意BaseA__dict__ 中的'_var': 0 属性。 @classmethod 正在做你期望它做的事情,将成员绑定到类,在本例中为 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 等价于cls._var = cls._var; cls._var += 1。由于way python does lookupcls._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})

【讨论】:

  • 让我抓狂的是:如果我写 "@classmethod def inc_class(cls): cls._var_boom += 1" 我得到一个错误,所以 python 没有为它创建一个新的 '_var_boom' 变量那个类实例,但对于 '_var' 它是!
  • @cabbi 我正在调查它,但如果您尝试使用未定义的同名成员,那么继承的类似乎会使用基类的成员。请注意,+= 需要一个现有变量,因此它确实应该给出错误,除了 _var 它从 Base 中提取它。
  • This 解释它。 cls._var += 1 等价于 cls._var = cls._var; cls._var += 1cls._var 的读取将找到Base._var 并将其值分配给新定义的A._var,然后+= 工作正常。我现在找不到有关 Python 如何进行名称查找的文档,但它可能会在范围内上升,直到(如果)它找到它,这就是为什么在定义 A._var 之前选择 Base._var
  • 用你上面的评论你完全解释了我的“神奇”python 正在处理这些变量
  • 您最后的评论绝对应该是您回答的一部分
【解决方案2】:

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

a.inc_class()
b.inc_class()

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

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

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2012-03-08
    • 1970-01-01
    • 2015-05-26
    • 2014-03-23
    • 1970-01-01
    • 1970-01-01
    • 2012-09-30
    相关资源
    最近更新 更多