Как избежать того, чтобы данные класса делились между экземплярами?

Я хочу это поведение:

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

Вы объявили "список" как "свойство уровня класса", а не "свойство уровня экземпляра". Чтобы иметь свойства, ограниченные уровнем экземпляра, вам необходимо инициализировать их путем ссылки с параметром "self" в методе __init__ (или в другом месте в зависимости от ситуации).

Вам не нужно строго инициализировать свойства экземпляра в методе __init__, но это упрощает понимание.

Ответ 3

Принятый ответ работает, но немного больше объяснений не повредит.

Атрибуты класса не становятся атрибутами экземпляра при создании экземпляра. Они становятся атрибутами экземпляра, когда им присваивается значение.

В исходном коде никакое значение не присваивается атрибуту 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]

Однако запутанный сценарий в вопросе никогда не случится с неизменяемыми объектами, такими как числа и строки, потому что их значение не может быть изменено без назначения. Например, код, аналогичный оригиналу со строковым атрибутом, работает без проблем:

>>> class a:
    string = ''


>>> x = a()
>>> y = a()
>>> x.string += 'x'
>>> y.string += 'y'
>>> x.string
'x'
>>> y.string
'y'

Итак, чтобы суммировать: атрибуты класса становятся атрибутами экземпляра тогда и только тогда, когда им присваивается значение после экземпляра, находясь в методе __init__ или не. Это хорошо, потому что таким образом вы можете иметь статические атрибуты, если вы никогда не присваиваете значение атрибуту после создания экземпляра.

Ответ 4

Несмотря на то, что принятый anwer присутствует, я хотел бы добавить немного описания.

Сделайте небольшое упражнение

сначала определите класс следующим образом:

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 выполняет. 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), они будут отличаться

Ответ 5

Да, вы должны объявить в "конструкторе", если хотите, чтобы этот список стал свойством объекта, а не свойством класса.

Ответ 6

Таким образом, почти каждый ответ здесь, похоже, пропустит конкретный момент. Переменные класса никогда становятся переменными экземпляра, как показано в приведенном ниже коде. Используя метакласс, чтобы перехватить назначение переменных на уровне класса, мы можем видеть, что при переназначении a.myattr метод магизации полей для класса не вызывается. Это связано с тем, что назначение создает новую переменную экземпляра. Это поведение имеет абсолютно ничего для переменной класса, как показано вторым классом, который не имеет переменных класса и все же допускает назначение поля.

class mymeta(type):
    def __init__(cls, name, bases, d):
        pass

    def __setattr__(cls, attr, value):
        print("setting " + attr)
        super(mymeta, cls).__setattr__(attr, value)

class myclass(object):
    __metaclass__ = mymeta
    myattr = []

a = myclass()
a.myattr = []           #NOTHING IS PRINTED
myclass.myattr = [5]    #change is printed here
b = myclass()
print(b.myattr)         #pass through lookup on the base class

class expando(object):
    pass

a = expando()
a.random = 5            #no class variable required
print(a.random)         #but it still works

IN SHORT Переменные класса не имеют НИЧЕГО для переменных экземпляра.

Более четко Они просто попадают в область поиска по экземплярам. Переменные класса на самом деле являются переменными экземпляра на самом объекте класса. Вы также можете иметь переменные метакласса, если хотите, а также потому, что сами метаклассы тоже являются объектами. Все это объект, независимо от того, используется ли он для создания других объектов или нет, поэтому не следует привязывать семантику использования других слов в классе слов. В python класс - это просто объект, который используется для определения того, как создавать другие объекты и каковы их поведение. Метаклассы - это классы, которые создают классы, чтобы еще раз проиллюстрировать этот момент.

Ответ 7

Чтобы защитить вашу переменную, совместно используемую другим экземпляром, вам нужно создать новую переменную экземпляра каждый раз, когда вы создаете экземпляр. Когда вы объявляете переменную внутри класса, она является переменной класса и совместно используется всем экземпляром. Если вы хотите сделать это, например, необходимо использовать метод init, чтобы повторно инициализировать переменную, ссылаясь на экземпляр

Из объектов и класса Python по Programiz.com:

__init__(). Эта специальная функция вызывается всякий раз, когда создается новый объект этого класса.

Этот тип функции также называется конструкторами в объектно-ориентированном программировании (ООП). Обычно мы используем его для инициализации всех переменных.

Например:

class example:
    list=[] #This is class variable shared by all instance
    def __init__(self):
        self.list = [] #This is instance variable referred to specific instance

Ответ 8

Я думаю, что предоставленные ответы вводят в заблуждение. Свойство, определенное внутри класса, становится свойством экземпляра при создании экземпляра объекта независимо от того, как вы его определяете. Таким образом создаются копии a.list, а x.list и y.list - разные копии. Причина, по которой они кажутся одинаковыми, заключается в том, что они оба являются псевдонимами в том же списке. Но это следствие того, как работают списки, а не того, как работают классы. Если бы вы делали то же самое с числами вместо списков (или просто используя + = вместо append, что создало бы новый список), вы увидите, что изменение x.attr не влияет на изменение y.attr.

Определение self.list внутри __init__ работает, потому что функция вызывается дважды, один раз для каждого экземпляра объекта, и поэтому создается два разных списка.