Как избежать совместного использования данных класса между экземплярами?
Чего я хочу, так это такого поведения:
class a:
list = []
x = a()
y = a()
x.list.append(1)
y.list.append(2)
x.list.append(3)
y.list.append(4)
print(x.list) # prints [1, 3]
print(y.list) # prints [2, 4]
Конечно, что на самом деле происходит при печати, так это:
print(x.list) # prints [1, 2, 3, 4]
print(y.list) # prints [1, 2, 3, 4]
Очевидно, что они совместно используют данные в классе a
. Как мне получить отдельные экземпляры для достижения желаемого поведения?
Переведено автоматически
Ответ 1
Вы хотите этого:
class a:
def __init__(self):
self.list = []
Объявление переменных внутри объявления класса делает их членами "класса", а не членами экземпляра. Их объявление внутри __init__
метода гарантирует, что новый экземпляр элементов создается рядом с каждым новым экземпляром объекта, что соответствует тому поведению, которое вы ищете.
Ответ 2
Принятый ответ работает, но немного дополнительных объяснений не помешает.
Атрибуты класса не становятся атрибутами экземпляра при создании экземпляра. Они становятся атрибутами экземпляра, когда им присваивается значение.
В исходном коде list
атрибуту после создания экземпляра не присваивается значение; поэтому он остается атрибутом класса. Определение списка внутри __init__
работает, потому что __init__
вызывается после создания экземпляра. В качестве альтернативы, этот код также будет выдавать желаемый результат:
>>> class a:
list = []
>>> y = a()
>>> x = a()
>>> x.list = []
>>> y.list = []
>>> x.list.append(1)
>>> y.list.append(2)
>>> x.list.append(3)
>>> y.list.append(4)
>>> print(x.list)
[1, 3]
>>> print(y.list)
[2, 4]
Однако запутанный сценарий, описанный в вопросе, никогда не произойдет с неизменяемыми объектами, такими как числа и строки, потому что их значение нельзя изменить без присвоения. Например, код, аналогичный исходному, с типом атрибута string, работает без каких-либо проблем:
>>> class a:
string = ''
>>> x = a()
>>> y = a()
>>> x.string += 'x'
>>> y.string += 'y'
>>> x.string
'x'
>>> y.string
'y'
Итак, подведем итог: атрибуты класса становятся атрибутами экземпляра тогда и только тогда, когда им присваивается значение после создания экземпляра, будь то в __init__
методе или нет. Это хорошо, потому что таким образом вы можете иметь статические атрибуты, если вы никогда не присваиваете значение атрибуту после создания экземпляра.
Ответ 3
Хотя принятый ответ точен, я хотел бы добавить краткое описание.
Давайте выполним небольшое упражнение
прежде всего определите класс следующим образом:
class A:
temp = 'Skyharbor'
def __init__(self, x):
self.x = x
def change(self, y):
self.temp = y
Итак, что мы здесь имеем?
- У нас есть очень простой класс, у которого есть атрибут,
temp
который является строкой __init__
Метод, который устанавливаетself.x
- Метод изменения устанавливает
self.temp
Пока довольно прямолинейно, да? Теперь давайте начнем играть с этим классом. Давайте сначала инициализируем этот класс:
a = A('Tesseract')
Теперь выполните следующее:
>>> print(a.temp)
Skyharbor
>>> print(A.temp)
Skyharbor
Ну, a.temp
сработало, как ожидалось, но как, черт возьми, A.temp
сработало? Ну, это сработало, потому что temp - это атрибут класса. Все в python является объектом. Здесь A также является объектом класса type
. Таким образом, атрибут temp является атрибутом, удерживаемым A
классом, и если вы измените значение temp через A
(а не через экземпляр a
), измененное значение будет отражено во всех экземплярах A
класса.
Давайте продолжим и сделаем это:
>>> A.temp = 'Monuments'
>>> print(A.temp)
Monuments
>>> print(a.temp)
Monuments
Интересно, не правда ли? И обратите внимание, что id(a.temp)
и id(A.temp)
все те же.
Любому объекту Python автоматически присваивается __dict__
атрибут, который содержит его список атрибутов. Давайте исследуем, что содержит этот словарь для наших примеров объектов:
>>> print(A.__dict__)
{
'change': <function change at 0x7f5e26fee6e0>,
'__module__': '__main__',
'__init__': <function __init__ at 0x7f5e26fee668>,
'temp': 'Monuments',
'__doc__': None
}
>>> print(a.__dict__)
{x: 'Tesseract'}
Обратите внимание, что temp
атрибут указан среди A
атрибутов класса, в то время как x
указан для экземпляра.
Итак, как получилось, что мы получаем определенное значение a.temp
, если его даже нет в списке для экземпляра a
. Что ж, в этом и заключается магия __getattribute__()
метода. В Python синтаксис с точками автоматически вызывает этот метод, поэтому, когда мы пишем a.temp
, Python выполняет a.__getattribute__('temp')
. Этот метод выполняет действие поиска атрибута, то есть находит значение атрибута путем поиска в разных местах.
Стандартная реализация __getattribute__()
выполняет поиск сначала по внутреннему словарю (dict) объекта, затем по типу самого объекта. В этом случае a.__getattribute__('temp')
выполняется сначала a.__dict__['temp']
, а затем a.__class__.__dict__['temp']
Хорошо, теперь давайте воспользуемся нашим change
методом:
>>> a.change('Intervals')
>>> print(a.temp)
Intervals
>>> print(A.temp)
Monuments
Что ж, теперь, когда мы использовали self
, print(a.temp)
дает нам значение, отличное от print(A.temp)
.
Теперь, если мы сравним id(a.temp)
и id(A.temp)
, они будут разными.
Ответ 4
Вы объявили "список" как "свойство уровня класса", а не "свойство уровня экземпляра". Чтобы ограничить область действия свойств на уровне экземпляра, вам необходимо инициализировать их путем обращения к параметру "self" в __init__
методе (или в другом месте, в зависимости от ситуации).
Вам не обязательно строго инициализировать свойства экземпляра в __init__
методе, но это упрощает понимание.