Объявления атрибутов Python и класса
Мне сказали, что объявление динамических атрибутов в области видимости классов - это не "путь Python", но я не понимаю, почему. Может ли кто-нибудь объяснить мне это или указать на какую-то документацию о том, почему это плохо? Честно говоря, я думал, что это хорошая практика, если вообще что-то для самодокументирования кода.
Пример:
class ClassA(object):
user_data = {}
def set_user(self):
self.user_data['username'] = 'fred'
Единственная причина, по которой я не вижу этого, заключается в том, что атрибуты являются статическими (и поэтому могут вводить в заблуждение).
4 ответа
С кодом, как показано, user_data
не является динамическим атрибутом (он не создается на экземпляре класса "динамически"). Я считаю, что это атрибут класса, который во многом похож на "статические" атрибуты в некоторых других языках. Это означает, что это атрибут, объявленный в классе во время чтения и инициализации класса. Это имеет побочный эффект / преимущество всех экземпляров, имеющих возможность доступа к одному и тому же объекту через self.whatever
,
Другими словами:
class Foo(object):
whatever = {}
def __init__(self):
print self.whatever is Foo.whatever
всегда будет печатать True
, Конечно, вы можете изменить это поведение, добавив другой whatever
приписать экземпляр:
class Bar(object):
whatever = {}
def __init__(self):
self.whatever = {}
print self.whatever is Bar.whatever
С Foo
, если я добавлю элементы: foo_instance.whatever['foo'] = 'bar'
, затем foo_instance2
также увидим это изменение, тогда как с Bar
,
В комментарии вы говорите: "Ну, каждый экземпляр будет иметь разные изменяемые user_data". Нет, не будет. Каждый экземпляр ClassA будет использовать один и тот же словарь user_data:
>>> class ClassA(object):
... user_data = {}
... def set_user(self, name):
... self.user_data['name'] = name
...
>>> a1 = ClassA()
>>> a1.set_user('fred')
>>> a1.user_data
{'name': 'fred'}
>>>
>>> a2 = ClassA()
>>> a2.user_data
{'name': 'fred'}
>>> a2.set_user('barney')
>>> a2.user_data
{'name': 'barney'}
>>>
>>> a1.user_data
{'name': 'barney'}
>>>
>>> a1.user_data is a2.user_data
True
Это не вопрос того, является ли что-то Pythonic или нет. Это вопрос написания кода, который ведет себя так, как вы хотите.
Вот пример использования членов класса и экземпляра:
class Test(object):
instances = {}
def __init__(self, name):
self.instance = self
self.instances[name] = self
# rest of class
a = Test('alfred')
b = Test('banana')
# use a to get a
print a.instance
>>>
<__main__.Test object at 0x0000000002BEBC50>
# get all instances of the Test class
print Test.instances
>>>
{'banana': <__main__.Test object at 0x0000000002BEBDA0>, 'alfred': <__main__.Test object at 0x0000000002BEBC50>}
Поскольку это всего лишь примеры, они не отражают ничего конкретного, только то, что можно сделать.
Объявление члена класса полезно в некоторых ситуациях, например, выше, если вы хотите иметь возможность документировать и контролировать все типы определенного класса. Но вы всегда должны понимать, что вы делаете, и не думать, что instance
является локальным для каждого экземпляра.
Что касается вопроса "наилучшей практики" и "наиболее питонического пути". Это просто мнения. Некоторые люди будут осуждать некоторые вещи, а не другие. Вы должны писать так, чтобы вам было удобно и это работало для вас, при условии, что вы соответствуете правилам и стандартам, установленным любой рабочей силой, частью которой вы оказались.
Чтобы было ясно, атрибуты класса, вероятно, отличаются от ожидаемых.
Пример:
class Example(object):
class_data={}
def __init__(self,val):
self.val=val
def __repr__(self):
return str(self.val)
>>> Example.class_data['one']=1 # assign value to the empty dict in class Example
>>> e1,e2=Example(1), Example(2) # INSTANCES-self.val different for each instance
>>> print e1,e2,e1.class_data,e2.class_data
1 2 {'one': 1} {'one': 1}
^^^^^ ^^^^^ # SAME class_data in two different instances
Обратите внимание, что каждый экземпляр Example имеет одну и ту же изменяемую копию class_data. Он не защищен как статическая переменная класса в Java или C++.
Действительно, вы можете в любое время изменить, добавить или удалить эти атрибуты класса:
>>> Example.class_data='old dict is gone gone gone...'
>>> print e1,e2,e1.class_data,e2.class_data
1 2 old dict is gone gone gone... old dict is gone gone gone...
Вы можете увидеть такой код:
class Example(object):
class_constants=('fee','fie','foe')
# ...
С ложным утешением, что он действует как постоянный (на другом языке) из-за этого:
>>> e1=Example(1)
>>> e1.class_constants[4]='fum'
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: 'tuple' object does not support item assignment
Но это ложное утешение, потому что кусок кода всегда может сделать это:
>>> Example.class_constants=('another','tuple','of','constants')
Проблема, с которой некоторые сталкиваются с атрибутами класса, заключается в том, что они не выполняют ожидаемого - как будто это константа. Они не постоянные. Атрибуты класса изменяют данные для всех существующих и будущих экземпляров класса. Иногда полезно, но потенциально сбивает с толку.
На самом деле не имеет значения, является ли атрибут класса изменчивым или нет. Как видите - вы можете изменить атрибут в любое время.