Jak uniknąć udostępniania danych klasowych między instancjami?


145

Chcę tego zachowania:

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]

Oczywiście to, co naprawdę się dzieje, kiedy drukuję, to:

print(x.list) # prints [1, 2, 3, 4]
print(y.list) # prints [1, 2, 3, 4]

Najwyraźniej dzielą się danymi w klasie a. Jak mogę uzyskać oddzielne wystąpienia, aby osiągnąć pożądane zachowanie?


14
Nie używaj listjako nazwy atrybutu. listjest funkcją wbudowaną do tworzenia nowej listy. Nazwy klas należy pisać dużymi literami.
Marc Tudurí

Odpowiedzi:


147

Chcesz to:

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

Zadeklarowanie zmiennych wewnątrz deklaracji klasy powoduje, że stają się one członkami „klasy”, a nie elementami instancji. Zadeklarowanie ich w __init__metodzie zapewnia, że ​​nowe wystąpienie elementów członkowskich zostanie utworzone wraz z każdym nowym wystąpieniem obiektu, co jest zachowaniem, którego szukasz.


5
Dodatkowe wyjaśnienie: jeśli miałbyś ponownie przypisać właściwość listy w jednej z instancji, nie wpłynie to na pozostałe. Więc jeśli zrobiłeś coś podobnego x.list = [], możesz to zmienić i nie wpływać na inne. Problem, z którym się spotykasz, polega na tym, że x.listi y.listsą to ta sama lista, więc kiedy wywołujesz dołączenie do jednej, wpływa to na drugą.
Matt Moriarity

Ale dlaczego dzieje się to tylko w przypadku listy? Kiedy zadeklarowałem liczbę całkowitą lub ciąg poza init , nie było to współdzielone między obiektami? Czy każdy może udostępnić link do dokumentu z tą koncepcją?
Amal Ts

1
@AmalTs Wygląda na to, że nie rozumiesz, jak działa przypisanie w Pythonie. Zobacz ten film lub ten post SO . Zachowanie, które widzisz, jest spowodowane faktem, że modyfikujesz listy, ale ponownie wiążesz odniesienia do int i ciągów znaków.
Андрей Беньковский

4
@AmalTs Uwaga: używanie atrybutów klas jako „leniwych” wartości domyślnych dla atrybutów instancji jest uważane za złą praktykę. Nawet jeśli atrybuty są niezmiennego typu, lepiej jest przypisać je wewnątrz __init__.
Андрей Беньковский

21

Przyjęta odpowiedź działa, ale trochę więcej wyjaśnień nie boli.

Atrybuty klas nie stają się atrybutami instancji podczas tworzenia instancji. Stają się atrybutami instancji, gdy zostanie im przypisana wartość.

W oryginalnym kodzie żadna wartość nie jest przypisywana do listatrybutu po instancji; więc pozostaje atrybutem klasy. Definiowanie listy wewnątrz __init__działa, ponieważ __init__jest wywoływana po instancji. Alternatywnie, ten kod dałby również pożądane dane wyjściowe:

>>> 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]

Jednak zagmatwany scenariusz w pytaniu nigdy nie przydarzy się niezmiennym obiektom, takim jak liczby i łańcuchy, ponieważ ich wartości nie można zmienić bez przypisania. Na przykład kod podobny do oryginału z atrybutem typu string działa bez problemu:

>>> class a:
    string = ''


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

Podsumowując: atrybuty klas stają się atrybutami instancji wtedy i tylko wtedy, gdy wartość jest im przypisana po instancji, będąc w __init__metodzie lub nie . Jest to dobra rzecz, ponieważ w ten sposób możesz mieć atrybuty statyczne, jeśli nigdy nie przypiszesz wartości do atrybutu po utworzeniu instancji.


11

Zadeklarowano „listę” jako „właściwość na poziomie klasy”, a nie jako „właściwość na poziomie instancji”. Aby mieć zakres właściwości na poziomie instancji, należy je zainicjować poprzez odwołanie się do parametru „self” w __init__metodzie (lub w innym miejscu, w zależności od sytuacji).

Nie musisz ściśle inicjować właściwości instancji w __init__metodzie, ale ułatwia to zrozumienie.


11

Mimo że przyjęta odpowiedź jest trafna, chciałbym nieco dodać opis.

Zróbmy małe ćwiczenie

przede wszystkim zdefiniuj klasę w następujący sposób:

class A:
    temp = 'Skyharbor'

    def __init__(self, x):
        self.x = x

    def change(self, y):
        self.temp = y

Więc co tu mamy?

  • Mamy bardzo prostą klasę, która ma atrybut tempbędący łańcuchem znaków
  • __init__Metoda, która ustawiaself.x
  • Zestawy metod zmiany self.temp

Jak dotąd całkiem proste, tak? Teraz zacznijmy bawić się tą klasą. Zainicjujmy najpierw tę klasę:

a = A('Tesseract')

Teraz wykonaj następujące czynności:

>>> print(a.temp)
Skyharbor
>>> print(A.temp)
Skyharbor

Cóż, a.tempdziałało zgodnie z oczekiwaniami, ale jak do diabła A.tempdziałało? Cóż, zadziałało, ponieważ temp jest atrybutem klasy. Wszystko w Pythonie jest obiektem. Tutaj A jest również przedmiotem klasy type. Zatem atrybut temp jest atrybutem utrzymywanym przez Aklasę i jeśli zmienisz wartość temp za pośrednictwem A(a nie przez instancję a), zmieniona wartość zostanie odzwierciedlona we wszystkich instancjach Aklasy. Zróbmy to dalej:

>>> A.temp = 'Monuments'
>>> print(A.temp)
Monuments
>>> print(a.temp)
Monuments

Ciekawe, prawda? I pamiętać, że id(a.temp)i id(A.temp)są wciąż takie same .

Każdy obiekt Pythona automatycznie otrzymuje __dict__atrybut, który zawiera listę atrybutów. Zbadajmy, co zawiera ten słownik dla naszych przykładowych obiektów:

>>> print(A.__dict__)
{
    'change': <function change at 0x7f5e26fee6e0>,
    '__module__': '__main__',
    '__init__': <function __init__ at 0x7f5e26fee668>,
    'temp': 'Monuments',
    '__doc__': None
}
>>> print(a.__dict__)
{x: 'Tesseract'}

Zauważ, że tempatrybut jest wymieniony wśród Aatrybutów klasy, podczas gdy xjest wymieniony dla instancji.

Więc jak to się dzieje, że otrzymujemy określoną wartość, a.tempjeśli nie ma jej nawet na liście dla instancji a. Na tym polega magia __getattribute__()metody. W Pythonie kropkowana składnia automatycznie wywołuje tę metodę, więc kiedy piszemy a.temp, Python wykonuje a.__getattribute__('temp'). Ta metoda wykonuje akcję wyszukiwania atrybutu, tj. Znajduje wartość atrybutu, patrząc w różnych miejscach.

Standardowa implementacja __getattribute__()przeszukuje najpierw wewnętrzny słownik ( dict ) obiektu, a następnie typ samego obiektu. W tym przypadku a.__getattribute__('temp')wykonuje najpierw, a.__dict__['temp']a potema.__class__.__dict__['temp']

Okej, teraz użyjmy naszej changemetody:

>>> a.change('Intervals')
>>> print(a.temp)
Intervals
>>> print(A.temp)
Monuments

Teraz, gdy już użyliśmy self, print(a.temp)daje nam inną wartość z print(A.temp).

Teraz, jeśli porównamy id(a.temp)i id(A.temp), będą one różne.


3

Tak, musisz zadeklarować w „konstruktorze”, jeśli chcesz, aby lista stała się właściwością obiektu, a nie właściwością klasy.


3

Tak więc prawie każda odpowiedź wydaje się pomijać konkretny punkt. Zmienne klas nigdy nie stają się zmiennymi instancji, jak pokazano w poniższym kodzie. Używając metaklasy do przechwycenia przypisania zmiennej na poziomie klasy, możemy zobaczyć, że po ponownym przypisaniu a.myattr magiczna metoda przypisania pola w klasie nie jest wywoływana. Dzieje się tak, ponieważ przypisanie tworzy nową zmienną instancji . To zachowanie nie ma absolutnie nic wspólnego ze zmienną klasy, jak pokazano w drugiej klasie, która nie ma zmiennych klasowych, a mimo to umożliwia przypisywanie pól.

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

W SKRÓCIE Zmienne klasowe nie mają NIC wspólnego ze zmiennymi instancji.

Wyraźniej po prostu znajdują się w zakresie wyszukiwania instancji. Zmienne klas są w rzeczywistości zmiennymi instancji w samym obiekcie klasy. Możesz także mieć zmienne metaklasy, jeśli chcesz, ponieważ same metaklasy również są obiektami. Wszystko jest obiektem, niezależnie od tego, czy jest używane do tworzenia innych obiektów, czy nie, więc nie daj się skrępować semantyką użycia słowa class w innych językach. W Pythonie klasa jest po prostu obiektem używanym do określenia, jak tworzyć inne obiekty i jakie będą ich zachowania. Metaklasy to klasy, które tworzą klasy, aby lepiej zilustrować ten punkt.


0

Aby chronić zmienną współdzieloną przez inną instancję, musisz tworzyć nową zmienną instancji za każdym razem, gdy tworzysz instancję. Kiedy deklarujesz zmienną wewnątrz klasy, jest to zmienna klasy i jest wspólna dla wszystkich instancji. Jeśli chcesz, aby było to na przykład mądre, musisz użyć metody init , aby ponownie zainicjować zmienną w odniesieniu do instancji

Z obiektów i klas Pythona autorstwa Programiz.com :

__init__()funkcjonować. Ta funkcja specjalna jest wywoływana za każdym razem, gdy tworzony jest nowy obiekt tej klasy.

Ten typ funkcji jest również nazywany konstruktorami w programowaniu obiektowym (OOP). Zwykle używamy go do inicjalizacji wszystkich zmiennych.

Na przykład:

class example:
    list=[] #This is class variable shared by all instance
    def __init__(self):
        self.list = [] #This is instance variable referred to specific instance
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.