В чем разница между атрибутом/свойством экземпляра и атрибутом/свойством класса? Когда я использую один и когда я использую другой в Python?
То есть, если у нас есть:
class Foo:
a = 5
def print_a(self):
print(self.a)
Д:
class Foo:
def __init__(self):
self.a = 5
def print_a(self):
print(self.a)
Какие различия существуют в отношении атрибута a
?
Фундаментальное отличие состоит в том, что атрибуты класса являются общими для всех экземпляров этого класса, в то время как атрибуты экземпляра специфичны для каждого объекта, созданного с помощью этого класса. Таким образом, переменные экземпляра предназначены для данных, уникальных для каждого объекта, а переменные класса предназначены для атрибутов, которые должны быть общими для всех экземпляров этого класса.
Атрибуты экземпляра создаются с использованием синтаксиса
instance.attr
orself.attr
(self
это действительно соглашение, это просто идентификатор, который относится к самому экземпляру). Как уже было сказано, они локальны для этого экземпляра и поэтому доступны только из одного из них.Как правило, они создаются внутри метода. Атрибуты экземпляра могут быть созданы в любом методе экземпляра, но обычно рекомендуется делать это в инициализаторе (
__init__
) или конструкторе (__new__
) класса или, самое большее, в методе, который вызывается непосредственно из одного из них. Если этого не сделать (даже просто инициализировать их нулевым значением), код теряет читабельность и, что более важно, атрибут не существует до тех пор, пока не будет вызван метод, который его создает, что создает неопределенность в отношении того, имеет экземпляр или нет определенный атрибут в определенное время.Что касается их полезности, мы можем в общем сказать, что они позволяют определить определенное «состояние» для этого конкретного объекта и что оно отличает его от других объектов этого класса. Например, атрибут "номерной знак" в классе "автомобиль".
Они хранятся в словаре (экземплярном или объектном словаре), к которому вы можете получить доступ через
NombreInstancia.__dict__
.Атрибуты класса определяются вне каких-либо методов класса, обычно чуть ниже строки документации. На них можно ссылаться непосредственно из класса, а также на них можно ссылаться из любого экземпляра класса. Обычно используется для создания перечисляемой константы в качестве управляющей переменной в синглтоне, для установки значений по умолчанию для переменных экземпляра, для определения констант и т. д.
Они хранятся в другом словаре (словаре классов) и не зависят от того, который используется для атрибутов экземпляра, который вы можете запрашивать через
NombreClase.__dict__
или из экземпляра с помощьюNombreInstancia.__class__.__dict__
. Свойства и дескрипторы в целом также находятся на уровне класса, а не на уровне экземпляра.Важно знать порядок, в котором разрешается доступ к атрибуту, Python следует следующему порядку при поиске атрибута из объекта:
__dict__
,__slots__
,__weakref__
, и т.д.).__set__
) в словаре классов и возможных родительских классах.__get__
, но не выполняются__set__
) в словаре класса и родительских элементах.__getattr__
.если мы игнорируем дескрипторы данных (и, следовательно, свойства с помощью setter ), Python сначала ищет атрибут в атрибутах экземпляра, а если не находит, ищет в атрибутах класса. Это означает, что если вы создаете атрибут экземпляра с тем же именем, что и у уже существующего атрибута класса, атрибут класса будет «скрытым», если к пути
self.atributo
или попытаются получить доступinstancia.atributo
.В случае уступки порядок следующий:
__setattr__
Вышеприведенное в основном отвечает на вопрос, следующее несколько длиннее (в основном для примеров) и пытается объяснить распространенную ошибку, вызванную разницей между присвоением атрибуту класса из его класса или из экземпляра и непосредственно связанную с объясненным порядком разрешения выше.
Если кто-то не хочет читать дальше ツ, мораль такова:
Как обсуждалось выше, мы можем получить доступ к атрибутам класса двумя способами:
Когда вместо простого доступа к значению мы хотим выполнить присваивание, все меняется:
Если мы назначаем через класс поведение, как и ожидалось, значение изменяется для всех экземпляров класса:
Вы можете видеть, как изменение значения с
n
помощьюFoo.n
или через метод класса (где равенствоcls
относится к ) изменение отражается во всех экземплярах класса.Foo
__class__
Если мы присвоим новое значение переменной класса, используя экземпляр для ссылки на нее, мы получим сюрприз:
Попытка присвоить новый объект атрибуту класса
instancia.atributo
с помощью метода экземпляра via или из негоself.atributo
(пока мы не имеем дело с дескриптором данных, который, как мы видели, имеет приоритет и будет вести себя так, как мы ожидаем) не изменяет атрибут класса, он создает атрибут экземпляра с тем же именем, и ему присваивается новое значение .Давайте вспомним, что атрибуты (опять же кроме дескрипторов данных) сначала ищутся,
__dict__
а затем__class__.__dict__
, если они не найдены, это приводит к тому, что атрибут класса скрывается за атрибутом экземпляра, мы можем видеть его через содержимое__dict__
и__class__.__dict__
:Наконец, помните, что в Python есть изменяемые объекты, такие как (
list
,dict
,set
) и неизменяемые (int
,float
,bool
,tuple
,frozenset
,str
). Неизменяемые объекты не могут быть изменены после создания, когда мы объединяем строку, создается новый объект, чего не происходит со списком:Python обрабатывает назначение по ссылке, переменная или атрибут — это не что иное, как идентификатор, имя, которое ссылается на объект в памяти. Атрибуты класса являются общими для всех экземпляров, потому что они ссылаются на один и тот же объект в памяти, поэтому, когда мы изменяем изменяемые атрибуты класса с помощью
instancia.atributo
илиself.atributo
изменение, если это отражается во всех экземплярах:Этого не происходит с атрибутом экземпляра, поскольку он специфичен для каждого объекта:
Если атрибут класса ссылается на неизменяемый объект, хотя мы и пытаемся «модифицировать» его в одном экземпляре, это не означает, что мы делаем это в остальных случаях, поскольку «модификация» неизменяемого объекта всегда подразумевает присваивание между и, как было показано ранее. , присваивание экземпляра вызывает создание нового атрибута экземпляра, а не модификацию атрибута класса (если это не дескриптор данных):
В отличие от того, что произошло со списком, конкатенация строк (или операции с целыми числами, числами с плавающей запятой и т. д.) подразумевает создание нового объекта и попытку присвоить его атрибуту, что подразумевает создание экземпляра атрибута, как мы видели ранее и не изменение атрибута класса.
Хотя дескрипторы тесно связаны со всем этим и неоднократно упоминаются, они не вводятся, потому что объяснение протокола дескриптора означало бы слишком большое расширение ответа, когда он уже, возможно, слишком длинный, мы можем увидеть в самой документации общее руководство по этому поводу. : Дескриптор HowTo Guide
Переменная класса наследуется всеми производными от нее экземплярами. Если вы посмотрите на этот пример:
pancho
иfido
два экземпляраPerro
, которые унаследовали значение переменнойnumero_patas
. Теперь меняем значение переменной в экземпляреfido
Теперь мы добавляем новую переменную экземпляра в фидо:
В Python переменные класса и экземпляра хранятся в разных словарях. Если мы посмотрим в словарь каждого созданного экземпляра:
Мы видим, что
pancho
у него нет переменных экземпляра. Однако, когда мы попросили его , онnumero_patas
вернулся к нам4
. Это связано с тем, что существует второй словарь, словарь класса, к которому обращаются, когда переменная экземпляра не найдена в его словаре. Если мы получим к нему доступ:Вы увидите, что они идентичны. Если вы создаете один экземпляр класса, вы должны использовать переменные экземпляра, так как сначала доступ к словарю переменных экземпляра будет немного быстрее.
Разница между атрибутом класса и атрибутом экземпляра заключается в способе доступа к ним , поэтому для атрибутов экземпляра доступ возможен только через объект, для атрибутов класса доступ к ним осуществляется непосредственно из класса и/или также с помощью объект.