Область видимости переменной класса для статических методов и методов класса

Я обнаружил странное поведение (по крайней мере странное для меня) с переменными класса 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а мне интересно почему?

2 ответа

Решение

Когда украшен @classmethod первый аргумент cls в inc_class(cls) это, ну, класс. <class '__main__.A'> а также <class '__main__.B'> соответственно для A а также B, Так cls._var относится к A"s _varи аналогично для B, В inc_static, украшенный @staticmethod нет аргументов, вы явно ссылаетесь на <class '__main__.Base'>, отличающийся _var,

Обратите внимание '_var': 0 приписывать Baseи A"s __dict__, @classmethod делает то, что вы ожидаете, привязывая членов к классам, в этом случае A а также B,

>>> 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"s _var инициализируется. Обратите внимание, что вы делаете cls._var += 1 до cls._var был определен. Как объяснено здесь, cls._var += 1 эквивалентно cls._var = cls._var; cls._var += 1, Из-за того, как Python выполняет поиск при первом чтении cls._var потерпит неудачу в A и продолжать находить его в Base, При назначении _var добавлен в A"s __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})

Хотя эти два класса наследуются от базового класса, они являются совершенно разными объектами. Через создание a а также bу вас есть два объекта, которые принадлежат двум отдельным классам. Когда вы звоните

a.inc_class()
b.inc_class()

Вы увеличиваете _var атрибут класса A один раз, и затем вы делаете то же самое для класса B. Даже если они имеют одно и то же имя, они являются разными объектами. Если у вас был второй экземпляр класса А, скажем, a2, и вы вызываете функцию снова, тогда оба вызова манипулируют одной и той же переменной. Это объясняет, как вы получаете ваши первые два выхода.

Третий вывод относится к объекту базового класса. Опять же, даже если это одно и то же имя, это другой объект. Вы увеличиваете третий объект в два раза, поэтому вы получаете 2 как ответ.

Другие вопросы по тегам