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