В чем разница между атрибутами класса и экземпляра?
Есть ли какое-либо осмысленное различие между:
class A(object):
foo = 5 # some default value
против.
class B(object):
def __init__(self, foo=5):
self.foo = foo
Если вы создаете множество экземпляров, есть ли разница в производительности или пространстве для двух стилей? Когда вы читаете код, считаете ли вы, что смысл двух стилей существенно отличается?
Ответы
Ответ 1
Помимо соображений производительности, существует значительная смысловая разница. В случае атрибута класса имеется только один объект. В экземпляре-attribute-set-at-instantiation может быть указано несколько объектов. Например,
>>> class A: foo = []
>>> a, b = A(), A()
>>> a.foo.append(5)
>>> b.foo
[5]
>>> class A:
... def __init__(self): self.foo = []
>>> a, b = A(), A()
>>> a.foo.append(5)
>>> b.foo
[]
Ответ 2
Разница в том, что атрибут класса разделяется всеми экземплярами. Атрибут экземпляра уникален для этого экземпляра.
Если из С++ атрибуты класса больше похожи на статические переменные-члены.
Ответ 3
Вот очень хорошее сообщение и суммируйте его, как показано ниже.
class Bar(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 = MyClass(2)
## Finds i_var in foo instance namespace
foo.i_var
## 2
## Doesn't find class_var in instance namespace…
## So look in class namespace (Bar.__dict__)
foo.class_var
## 1
И в визуальной форме
![введите описание изображения здесь]()
Назначение атрибута класса
-
Если атрибут класса задается путем доступа к классу, он переопределяет значение для всех экземпляров
foo = Bar(2)
foo.class_var
## 1
Bar.class_var = 2
foo.class_var
## 2
-
Если переменная класса задается путем обращения к экземпляру, она переопределяет значение только для этого экземпляра. Это по существу переопределяет переменную класса и превращает ее в переменную экземпляра, доступную, интуитивно, только для этого экземпляра.
foo = Bar(2)
foo.class_var
## 1
foo.class_var = 2
foo.class_var
## 2
Bar.class_var
## 1
Когда вы используете атрибут класса?
-
Сохраняющие константы. Поскольку атрибуты класса могут быть доступны как атрибуты самого класса, его часто приятно использовать для хранения кластерных констант класса
class Circle(object):
pi = 3.14159
def __init__(self, radius):
self.radius = radius
def area(self):
return Circle.pi * self.radius * self.radius
Circle.pi
## 3.14159
c = Circle(10)
c.pi
## 3.14159
c.area()
## 314.159
-
Определение значений по умолчанию. В качестве тривиального примера мы могли бы создать ограниченный список (т.е. Список, который может содержать только определенное количество элементов или меньше), и выбрать, чтобы по умолчанию было 10 элементов
class MyClass(object):
limit = 10
def __init__(self):
self.data = []
def item(self, i):
return self.data[i]
def add(self, e):
if len(self.data) >= self.limit:
raise Exception("Too many elements")
self.data.append(e)
MyClass.limit
## 10
Ответ 4
Поскольку люди в комментариях здесь и в двух других вопросах, отмеченных как дубликаты, похоже, смущены об этом таким же образом, я думаю, что стоит добавить дополнительный ответ поверх Алекс Ковентри.
Тот факт, что Alex присваивает значение изменяемого типа, например список, не имеет никакого отношения к тому, являются ли вещи общими или нет. Мы можем видеть это с помощью функции id
или оператора is
:
>>> class A: foo = object()
>>> a, b = A(), A()
>>> a.foo is b.foo
True
>>> class A:
... def __init__(self): self.foo = object()
>>> a, b = A(), A()
>>> a.foo is b.foo
False
(Если вам интересно, почему я использовал object()
вместо, скажем, 5
, чтобы избежать использования двух целых других проблем, которые я не хочу вдаваться сюда, для двух разных причины, полностью созданные отдельно 5
могут оказаться тем же экземпляром числа 5
. Но полностью созданный отдельно object()
не может.)
Итак, почему же a.foo.append(5)
в примере Alex влияет на b.foo
, но a.foo = 5
в моем примере нет? Ну, попробуйте a.foo = 5
в примере Alex и обратите внимание, что он не влияет на b.foo
.
a.foo = 5
просто превращает a.foo
в имя для 5
. Это не влияет на b.foo
или любое другое имя для старого значения, к которому обращается a.foo
. * Немного сложно, что мы создаем атрибут экземпляра, который скрывает атрибут класса **, но как только вы получилось, что здесь ничего сложного не происходит.
Надеюсь, теперь очевидно, почему Алекс использовал список: тот факт, что вы можете изменить список, означает, что проще показать, что две переменные называют один и тот же список, а также означает, что более важно в реальном коде знать, есть ли у вас два списка или два имени для одного и того же списка.
* Путаница для людей, поступающих с языка С++, заключается в том, что в Python значения не сохраняются в переменных. Значения в реальном выражении живут сами по себе, переменные - это просто имена для значений, а присваивание просто создает новое имя для значения. Если это помогает, подумайте о каждой переменной Python как shared_ptr<T>
вместо T
.
** Некоторые люди используют это, используя атрибут класса как "значение по умолчанию" для атрибута экземпляра, который экземпляры могут или не могут быть установлены. Это может быть полезно в некоторых случаях, но оно также может быть запутанным, поэтому будьте осторожны с ним.
Ответ 5
Просто уточняя, что сказал Алекс Ковентри, другой Алекс (Мартелли) обратился к аналогичному вопросу в группу новостей comp.lang.python
лет назад. Он исследует семантическую разницу в том, что человек намеревался против того, что он получил (используя переменные экземпляра).
http://groups.google.com/group/comp.lang.python/msg/5914d297aff35fae?hl=en
Ответ 6
Есть еще одна ситуация.
Атрибуты класса и экземпляра Дескриптор.
# -*- encoding: utf-8 -*-
class RevealAccess(object):
def __init__(self, initval=None, name='var'):
self.val = initval
self.name = name
def __get__(self, obj, objtype):
return self.val
class Base(object):
attr_1 = RevealAccess(10, 'var "x"')
def __init__(self):
self.attr_2 = RevealAccess(10, 'var "x"')
def main():
b = Base()
print("Access to class attribute, return: ", Base.attr_1)
print("Access to instance attribute, return: ", b.attr_2)
if __name__ == '__main__':
main()
Выше будет выводиться:
('Access to class attribute, return: ', 10)
('Access to instance attribute, return: ', <__main__.RevealAccess object at 0x10184eb50>)
Тот же тип доступа к экземпляру через класс или экземпляр возвращает другой результат!
И я нашел в определение c.PyObject_GenericGetAttr и отличный сообщение.
Объяснить
Если атрибут найден в словаре классов, которые составляют. объекты MRO, затем проверьте, указывает ли обнаруженный атрибут на дескриптор данных (это не что иное, как класс, реализующий методы __get__
и __set__
). Если это так, разрешите поиск атрибута, вызвав метод __get__
дескриптора данных (строки 28-33).