Jaka jest różnica między atrybutami klasy i instancji?

134

Czy istnieje jakieś znaczące rozróżnienie między:

class A(object):
    foo = 5   # some default value

vs.

class B(object):
    def __init__(self, foo=5):
        self.foo = foo

Jeśli tworzysz wiele instancji, czy jest jakaś różnica w wydajności lub wymaganej przestrzeni dla obu stylów? Czy czytając kod, uważasz, że znaczenie tych dwóch stylów znacząco się różni?

Dan Homerick
źródło
1
Właśnie zdałem sobie sprawę, że tutaj zadano podobne pytanie i otrzymałem odpowiedź: stackoverflow.com/questions/206734/… Czy mam usunąć to pytanie?
Dan Homerick,
2
To jest Twoje pytanie, możesz je usunąć. Skoro jest twój, po co pytać kogokolwiek o opinię?
S.Lott,

Odpowiedzi:

148

Poza względami wydajnościowymi istnieje znacząca różnica semantyczna . W przypadku atrybutu klasy istnieje tylko odniesienie do jednego obiektu. W instancji atrybutu zestaw w instancji może istnieć wiele obiektów, do których można się odwołać. Na przykład

>>> class A: foo = []
>>> a, b = A(), A()
>>> a.foo.append(5)
>>> b.foo
[5]
>>> class A:
...  def __init__(self): self.foo = []
>>> a, b = A(), A()
>>> a.foo.append(5)
>>> b.foo    
[]
Alex Coventry
źródło
4
Udostępniane są tylko zmienne typy. Lubią inti strnadal są związani z każdą instancją, a nie klasą.
Babu,
11
@Babu: Nie, inti str również udostępniane, dokładnie w ten sam sposób. Możesz to łatwo sprawdzić za pomocą islub id. Lub po prostu spójrz na każdą instancję __dict__i klasę __dict__. Zwykle nie ma większego znaczenia, czy niezmienne typy są udostępniane, czy nie.
abarnert
18
Pamiętaj jednak, że jeśli to zrobisz a.foo = 5, w obu przypadkach zobaczysz b.foopowrót []. Dzieje się tak, ponieważ w pierwszym przypadku nadpisujesz atrybut klasya.foo nowym atrybutem instancji o tej samej nazwie.
Konstantin Schubert
39

Różnica polega na tym, że atrybut w klasie jest wspólny dla wszystkich instancji. Atrybut instancji jest unikatowy dla tej instancji.

Jeśli pochodzą z C ++, atrybuty w klasie są bardziej podobne do statycznych zmiennych składowych.

Peter Shinners
źródło
1
Czy to nie tylko zmienne typy są udostępniane? Zaakceptowana odpowiedź pokazuje listę, która działa, ale jeśli jest to int, wygląda na to, że jest to to samo, co atrybut instancji: >>> class A(object): foo = 5 >>> a, b = A(), A() >>> a.foo = 10 >>> b.foo 5
Rafe
7
@Rafe: Nie, wszystkie typy są udostępniane. Powodem, dla którego jesteś zdezorientowany, jest to, że to, co a.foo.append(5)mutuje wartość, a.foodo której się odwołuje, a.foo = 5tworzy a.foonową nazwę wartości 5. Otrzymujesz więc atrybut instancji, który ukrywa atrybut klasy. Spróbuj tego samego a.foo = 5w wersji Alexa, a zobaczysz, że b.footo się nie zmieniło.
abarnert
31

Oto bardzo dobry post i podsumowanie go jak poniżej.

class Bar(object):
    ## No need for dot syntax
    class_var = 1

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

## Need dot syntax as we've left scope of class namespace
Bar.class_var
## 1
foo = MyClass(2)

## Finds i_var in foo's instance namespace
foo.i_var
## 2

## Doesn't find class_var in instance namespace…
## So look's in class namespace (Bar.__dict__)
foo.class_var
## 1

I w formie wizualnej

wprowadź opis obrazu tutaj

Przypisanie atrybutu klasy

  • Jeśli atrybut klasy zostanie ustawiony przez dostęp do klasy, nadpisze wartość dla wszystkich instancji

    foo = Bar(2)
    foo.class_var
    ## 1
    Bar.class_var = 2
    foo.class_var
    ## 2
    
  • Jeśli zmienna klasy jest ustawiana poprzez dostęp do instancji, nadpisuje wartość tylko dla tej instancji . To zasadniczo przesłania zmienną klasy i zamienia ją w zmienną instancji dostępną intuicyjnie tylko dla tej instancji .

    foo = Bar(2)
    foo.class_var
    ## 1
    foo.class_var = 2
    foo.class_var
    ## 2
    Bar.class_var
    ## 1
    

Kiedy użyłbyś atrybutu class?

  • Przechowywanie stałych . Ponieważ atrybuty klas mogą być dostępne jako atrybuty samej klasy, często dobrze jest ich używać do przechowywania stałych dla całej klasy, specyficznych dla klasy

    class Circle(object):
         pi = 3.14159
    
         def __init__(self, radius):
              self.radius = radius   
        def area(self):
             return Circle.pi * self.radius * self.radius
    
    Circle.pi
    ## 3.14159
    c = Circle(10)
    c.pi
    ## 3.14159
    c.area()
    ## 314.159
    
  • Definiowanie wartości domyślnych . Jako trywialny przykład możemy utworzyć ograniczoną listę (tj. Listę, która może zawierać tylko określoną liczbę elementów lub mniej) i wybrać domyślny limit 10 elementów

    class MyClass(object):
        limit = 10
    
        def __init__(self):
            self.data = []
        def item(self, i):
            return self.data[i]
    
        def add(self, e):
            if len(self.data) >= self.limit:
                raise Exception("Too many elements")
            self.data.append(e)
    
     MyClass.limit
     ## 10
    
zangw
źródło
20

Ponieważ ludzie w komentarzach tutaj iw dwóch innych pytaniach oznaczonych jako dupki wydają się być zdezorientowani w ten sam sposób, myślę, że warto dodać dodatkową odpowiedź oprócz odpowiedzi Alexa Coventry'ego .

Fakt, że Alex przypisuje wartość typu zmiennego, takiego jak lista, nie ma nic wspólnego z tym, czy rzeczy są udostępniane, czy nie. Widzimy to za pomocą idfunkcji lub isoperatora:

>>> class A: foo = object()
>>> a, b = A(), A()
>>> a.foo is b.foo
True
>>> class A:
...     def __init__(self): self.foo = object()
>>> a, b = A(), A()
>>> a.foo is b.foo
False

(Jeśli zastanawiasz się, dlaczego użyłem object()zamiast, powiedzmy, 5aby uniknąć napotkania dwóch innych problemów, których nie chcę tutaj omawiać; z dwóch różnych powodów, całkowicie oddzielnie utworzone 5pliki mogą ostatecznie stać się to samo wystąpienie liczby 5. Ale całkowicie oddzielnie utworzone object()s nie mogą).


Dlaczego więc a.foo.append(5)w przykładzie Alexa ma to wpływ b.foo, a a.foo = 5w moim nie? Dobrze, spróbuj a.foo = 5na przykład Alexa, i zauważ, że nie wpływa on b.footam albo .

a.foo = 5po prostu tworzy a.foonazwę dla 5. To nie ma wpływu b.foo, ani żadna inna nazwa starej wartości, a.foodo której się odnosiliśmy. * To trochę skomplikowane, że tworzymy atrybut instancji, który ukrywa atrybut klasy, ** ale kiedy już to zdobędziesz, nic skomplikowanego nie jest dzieje się tutaj.


Mamy nadzieję, że teraz jest oczywiste, dlaczego Alex użył listy: fakt, że możesz zmutować listę, oznacza, że ​​łatwiej jest pokazać, że dwie zmienne nazywają tę samą listę, a także oznacza, że ​​w prawdziwym kodzie ważniejsze jest, aby wiedzieć, czy masz dwie listy, czy dwie nazwy dla tej samej listy.


* Zamieszanie dla ludzi wywodzących się z języka takiego jak C ++ polega na tym, że w Pythonie wartości nie są przechowywane w zmiennych. Wartości same w sobie żyją na gruncie wartości, zmienne są tylko nazwami wartości, a przypisanie tworzy po prostu nową nazwę wartości. Jeśli to pomoże, pomyśl o każdej zmiennej Pythona jako o shared_ptr<T>zamiast a T.

** Niektóre osoby wykorzystują to, używając atrybutu class jako „wartości domyślnej” atrybutu instancji, który instancje mogą lub nie mogą być ustawiane. Może to być przydatne w niektórych przypadkach, ale może też być mylące, więc bądź ostrożny.

abarnert
źródło
0

Jest jeszcze jedna sytuacja.

Atrybuty klasy i instancji to Descriptor .

# -*- encoding: utf-8 -*-


class RevealAccess(object):
    def __init__(self, initval=None, name='var'):
        self.val = initval
        self.name = name

    def __get__(self, obj, objtype):
        return self.val


class Base(object):
    attr_1 = RevealAccess(10, 'var "x"')

    def __init__(self):
        self.attr_2 = RevealAccess(10, 'var "x"')


def main():
    b = Base()
    print("Access to class attribute, return: ", Base.attr_1)
    print("Access to instance attribute, return: ", b.attr_2)

if __name__ == '__main__':
    main()

Powyższe wyświetli:

('Access to class attribute, return: ', 10)
('Access to instance attribute, return: ', <__main__.RevealAccess object at 0x10184eb50>)

Ten sam typ dostępu do instancji za pośrednictwem klasy lub instancji zwraca inny wynik!

I znalazłem w definicji c.PyObject_GenericGetAttr, i świetny post .

Wyjaśnić

Jeśli atrybut znajduje się w słowniku klas, które tworzą. obiektów MRO, a następnie sprawdzić, czy istota atrybut wzrok punkty do deskryptora danych (która jest niczym więcej, że klasa wykonania zarówno __get__i __set__metody). Jeśli tak, rozwiąż wyszukiwanie atrybutu, wywołując __get__metodę deskryptora danych (wiersze 28–33).

SimonShyu
źródło