В чем разница между атрибутами класса и экземпляра?
-
03-07-2019 - |
Вопрос
Существует ли какое-либо значимое различие между:
class A(object):
foo = 5 # some default value
против.
class B(object):
def __init__(self, foo=5):
self.foo = foo
Если вы создаете много экземпляров, есть ли какая-либо разница в производительности или требованиях к пространству для двух стилей?Когда вы читаете код, считаете ли вы, что значение этих двух стилей существенно отличается?
Решение
Помимо соображений производительности, существует значительный семантический разница.В случае атрибута класса имеется ссылка только на один объект.В параметре instance-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
[]
Другие советы
Разница в том, что атрибут класса является общим для всех экземпляров.Атрибут экземпляра уникален для этого экземпляра.
Если исходить из C ++, атрибуты класса больше похожи на статические переменные-члены.
Вот очень хороший Публикация, и резюмируйте это, как показано ниже.
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'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
Причем в визуальной форме
Назначение атрибута класса
Если атрибут класса задан путем доступа к классу, он переопределит значение для все экземпляры
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
Поскольку люди в комментариях здесь и в двух других вопросах, помеченных как дублирующие, похоже, одинаково смущены этим, я думаю, стоит добавить дополнительный ответ поверх Дом Алекса Ковентри.
Тот факт, что Alex присваивает значение изменяемого типа, например list, не имеет никакого отношения к тому, являются ли вещи общими или нет.Мы можем видеть это с помощью 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
s может в конечном итоге быть одним и тем же экземпляром number 5
.Но совершенно отдельно созданный object()
я не могу.)
Итак, почему же это происходит 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
.
** Некоторые люди пользуются этим преимуществом, используя атрибут класса в качестве "значения по умолчанию" для атрибута экземпляра, которое экземпляры могут устанавливать, а могут и не устанавливать.Это может быть полезно в некоторых случаях, но также может сбить с толку, поэтому будьте с этим осторожны.
Есть еще одна ситуация.
Атрибуты класса и экземпляра являются Дескриптор.
# -*- 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).