Як уникнути спільного використання даних про класи серед примірників?


145

Чого я хочу, це така поведінка:

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. Як я можу отримати окремі екземпляри, щоб досягти бажаної мені поведінки?


14
Будь ласка, не використовуйте listяк ім’я атрибута. list- це вбудована функція для створення нового списку. Ви повинні написати класи імен з великої літери.
Марк Тудурі

Відповіді:


147

Ти хочеш це:

class a:
    def __init__(self):
        self.list = []

Оголошення змінних всередині декларації класу робить їх членами класу, а не членами екземпляра. Декларуючи їх всередині __init__методу, переконайтеся, що поруч із кожним новим екземпляром об’єкта створюється новий екземпляр членів, який є поведінкою, яку ви шукаєте.


5
Додане уточнення: якщо ви перепризначили властивість списку в одному з екземплярів, це не вплине на інші. Тож якщо ви зробили щось на кшталт x.list = [], ви можете змінити це, а не вплинути на інших. Проблема, з якою ви стикаєтеся, полягає в тому, що вони є одним x.listі y.listтим самим списком, тож коли ви телефонуєте, додавати додавання на одному, це впливає на інший.
Метт Моріарити

Але чому це відбувається лише для списку? Коли я оголосив ціле чи рядок за межами init , його не поділили між об'єктами? Чи може хтось поділитися будь-яким документом посилання на цю концепцію?
Амаль Ц

1
@AmalTs Схоже, ви не розумієте, як працює призначення в python. Дивіться це відео чи цю публікацію SO . Поведінка, яку ви бачите, викликана тим, що ви мутуєте списки, але відновлюєте посилання на вставки та рядки.
Андрій Беньковський

4
@AmalTs Примітка: вважається поганою практикою використання атрибутів класу як "ледачих" значень за замовчуванням, наприклад, атрибутів. Навіть якщо атрибути мають непорушний тип, краще призначити їх всередині __init__.
Андрій Беньковський

21

Прийнята відповідь працює, але трохи більше пояснень не зашкодить.

Атрибути класу не стають атрибутами примірника, коли створюється екземпляр. Вони стають атрибутами екземпляра, коли їм присвоюється значення.

У вихідному коді жодному значенню не присвоюється 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__методі чи ні . Це добре, тому що таким чином ви можете мати статичні атрибути, якщо ви ніколи не присвоюєте значення атрибуту після інстанції.


11

Ви оголосили "список" як "властивість рівня класу", а не "властивість рівня екземпляра". Для того, щоб властивості були розподілені на рівні екземпляра, вам потрібно ініціалізувати їх шляхом посилання на параметр "self" у __init__методі (або в іншому місці залежно від ситуації).

Вам не обов'язково ініціалізувати властивості екземпляра в __init__методі, але це полегшує розуміння.


11

Незважаючи на те, що прийнята анвер пляма, я хочу додати трохи опису.

Давайте зробимо невелику вправу

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

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 є атрибутом класу. Все в пітоні - це предмет. Тут А також є об’єктом класу 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), вони будуть іншими.


3

Так, ви повинні оголосити в "конструкторі", якщо ви хочете, щоб список став властивістю об'єкта, а не властивістю класу.


3

Тому майже кожна відповідь тут, здається, пропускає певний момент. Змінні класу ніколи не стають змінними екземплярів, як продемонстровано наведеним нижче кодом. Використовуючи метаклас для перехоплення присвоєння змінної на рівні класу, ми можемо бачити, що при перевлаштуванні 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

В КОРОТКІ Змінні класу НІЧОГО не мають стосуватися змінних екземплярів.

Більш чітко, вони просто трапляються в області пошуку для інстанцій. Змінні класу - це фактично змінні екземпляри самого об’єкта класу. Ви також можете мати змінні метакласи, якщо ви хочете, тому що самі метакласи також є об'єктами. Все - це об'єкт, незалежно від того, використовується він для створення інших об'єктів чи ні, тому не зациклюйтеся на семантиці використання класу слів інших мов. У python клас насправді є просто об’єктом, який використовується для визначення способів створення інших об'єктів та якою буде їх поведінка. Метакласи - це класи, які створюють класи, лише для подальшої ілюстрації цієї точки.


0

Щоб захистити свою змінну, якою поділяється інший екземпляр, вам потрібно створювати нову змінну примірника щоразу, коли ви створюєте екземпляр. Коли ви декларуєте змінну всередині класу, вона є змінною класу та ділиться всіма примірниками. Якщо ви хочете зробити це, наприклад, мудро потрібно використовувати метод init для повторної ініціалізації змінної як посилання на екземпляр

Від об’єктів і класів Python від Programiz.com :

__init__()функція. Ця спеціальна функція викликається кожного разу, коли новий об'єкт цього класу інстанціюється.

Цей тип функції називають також конструкторами в об'єктно-орієнтованому програмуванні (OOP). Ми зазвичай використовуємо його для ініціалізації всіх змінних.

Наприклад:

class example:
    list=[] #This is class variable shared by all instance
    def __init__(self):
        self.list = [] #This is instance variable referred to specific instance
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.