Объявления атрибутов 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')

Проблема, с которой некоторые сталкиваются с атрибутами класса, заключается в том, что они не выполняют ожидаемого - как будто это константа. Они не постоянные. Атрибуты класса изменяют данные для всех существующих и будущих экземпляров класса. Иногда полезно, но потенциально сбивает с толку.

На самом деле не имеет значения, является ли атрибут класса изменчивым или нет. Как видите - вы можете изменить атрибут в любое время.

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