Если вы создаете много экземпляров, есть ли разница в производительности или требованиях к пространству для двух стилей? Когда вы читаете код, считаете ли вы, что значение двух стилей существенно отличается?
Переведено автоматически
Ответ 1
Существует существенное семантическое различие (помимо соображений производительности):
когда атрибут определен в экземпляре (что мы обычно и делаем), могут быть ссылки на несколько объектов. Каждый получает совершенно отдельную версию этого атрибута.
когда атрибут определен в классе, упоминается только один базовый объект, поэтому, если операции над разными экземплярами этого класса одновременно пытаются установить / (добавить / расширить / вставить / и т.д.) Атрибут, то:
если атрибут имеет встроенный тип (например, int, float, boolean, string), операции над одним объектом перезапишут (сгладят) значение
если атрибут имеет изменяемый тип (например, list или dict), мы получим нежелательную утечку.
Например:
>>> classA: foo = [] >>> a, b = A(), A() >>> a.foo.append(5) >>> b.foo [5] >>> classA: ... def__init__(self): self.foo = [] >>> a, b = A(), A() >>> a.foo.append(5) >>> b.foo []
Ответ 2
Разница в том, что атрибут класса является общим для всех экземпляров. Атрибут экземпляра уникален для этого экземпляра.
Если исходить из C ++, атрибуты класса больше похожи на статические переменные-члены.
Ответ 3
Вот очень хороший пост, и резюмируйте его, как показано ниже.
classBar(object): ## No need for dot syntax class_var = 1
def__init__(self, i_var): self.i_var = i_var
## Need dot syntax as we've left scope of class namespace Bar.class_var ## 1 foo = Bar(2)
## Finds i_var in foo's instance namespace foo.i_var ## 2
## Doesn't find class_var in instance namespace… ## So look's in class namespace (Bar.__dict__) foo.class_var ## 1
И в визуальной форме
Назначение атрибутов класса
Если атрибут класса задан путем доступа к классу, он переопределит значение для всех экземпляров
Если переменная класса устанавливается при обращении к экземпляру, она переопределяет значение только для этого экземпляра. Это по сути переопределяет переменную класса и превращает ее в переменную экземпляра, доступную интуитивно только для этого экземпляра.
Хранение констант. Поскольку к атрибутам класса можно обращаться как к атрибутам самого класса, часто бывает приятно использовать их для хранения констант всего класса, специфичных для конкретного класса
Определение значений по умолчанию. В качестве тривиального примера мы могли бы создать ограниченный список (т. Е. Список, который может содержать только определенное количество элементов или меньше) и выбрать ограничение по умолчанию в 10 элементов
defadd(self, e): iflen(self.data) >= self.limit: raise Exception("Too many elements") self.data.append(e)
MyClass.limit ## 10
Ответ 4
Поскольку люди в комментариях здесь и в двух других вопросах, помеченных как дублирующие, похоже, одинаково сбиты с толку по этому поводу, я думаю, стоит добавить дополнительный ответ поверх Алекса Ковентри.
Тот факт, что Alex присваивает значение изменяемого типа, например list , не имеет никакого отношения к тому, являются ли объекты общими или нет. Мы можем увидеть это с помощью id функции или is оператора:
>>> classA: foo = object() >>> a, b = A(), A() >>> a.foo is b.foo True >>> classA: ... def__init__(self): self.foo = object() >>> a, b = A(), A() >>> a.foo is b.foo False
(Если вам интересно, почему я использовал object() вместо, скажем, 5, это для того, чтобы не столкнуться с двумя совершенно другими проблемами, в которые я не хочу здесь вдаваться; по двум разным причинам полностью раздельно созданные 5 s могут в конечном итоге оказаться одним и тем же экземпляром number 5. Но полностью созданные отдельно object() s не могут.)
Итак, почему это a.foo.append(5) в примере Алекса влияет b.foo, а a.foo = 5 в моем примере нет? Что ж, попробуйте a.foo = 5 в примере Алекса и обратите внимание, что это не влияет b.foo там тоже.
a.foo = 5 просто превращает a.foo в имя для 5. Это не влияет на b.foo или любое другое имя для старого значения, на которое a.foo раньше ссылались. * Немного сложно, что мы создаем атрибут экземпляра, который скрывает атрибут класса, ** но как только вы это получите, здесь не произойдет ничего сложного.
Надеюсь, теперь очевидно, почему Алекс использовал список: тот факт, что вы можете изменять список, означает, что проще показать, что две переменные называют один и тот же список, а также означает, что в реальном коде важнее знать, есть ли у вас два списка или два имени для одного и того же списка.
* Путаница для людей, знакомых с таким языком, как C ++, заключается в том, что в Python значения не хранятся в переменных. Значения существуют сами по себе, переменные - это просто имена для значений, а присваивание просто создает новое имя для значения. Если это поможет, думайте о каждой переменной Python как о shared_ptr<T> вместо T.
** Некоторые люди пользуются этим преимуществом, используя атрибут класса в качестве "значения по умолчанию" для атрибута экземпляра, которое экземпляры могут устанавливать, а могут и не устанавливать. Это может быть полезно в некоторых случаях, но также может сбивать с толку, поэтому будьте осторожны с этим.