Zmienne instancji a zmienne klas w Pythonie

121

Mam klasy Pythona, z których potrzebuję tylko jednej instancji w czasie wykonywania, więc wystarczyłoby mieć atrybuty tylko raz na klasę, a nie na instancję. Jeśli byłoby więcej niż jedno wystąpienie (co się nie stanie), wszystkie wystąpienia powinny mieć tę samą konfigurację. Zastanawiam się, która z poniższych opcji byłaby lepsza lub bardziej „idiomatyczna” w Pythonie.

Zmienne klasowe:

class MyController(Controller):

  path = "something/"
  children = [AController, BController]

  def action(self, request):
    pass

Zmienne instancji:

class MyController(Controller):

  def __init__(self):
    self.path = "something/"
    self.children = [AController, BController]

  def action(self, request):
    pass
demon
źródło
4
Po przeczytaniu tego pytania i zobaczeniu odpowiedzi, jednym z moich pierwszych pytań było: „Jak więc uzyskać dostęp do zmiennych klas?” - to dlatego, że do tej pory używałem tylko zmiennych instancji. Odpowiadając na moje własne pytanie, robisz to poprzez samą nazwę klasy, chociaż technicznie możesz to również zrobić za pośrednictwem instancji. Oto link do przeczytania dla każdego z tym samym pytaniem: stackoverflow.com/a/3434596/4561887
Gabriel Staples

Odpowiedzi:

159

Jeśli i tak masz tylko jedną instancję, najlepiej ustawić wszystkie zmienne na instancję, po prostu dlatego, że będą dostępne (trochę) szybciej (o jeden poziom „wyszukiwania” mniej ze względu na „dziedziczenie” z klasy do instancji), i nie ma żadnych wad, aby ważyć tę małą przewagę.

Alex Martelli
źródło
7
Nigdy nie słyszałeś o wzorze Borg? Posiadanie tylko jednej instancji było niewłaściwym sposobem na jej posiadanie.
Devin Jeanpierre
435
@Devin, tak, słyszałem o wzorze Borg, ponieważ to ja go przedstawiłem (w 2001, cfr code.activestate.com/recipes/… ;-). Ale nie ma nic złego w prostych przypadkach, mając po prostu jedną instancję bez egzekwowania.
Alex Martelli
2
@ user1767754, łatwo zrobić je samemu python -mtimeit- ale właśnie to zrobiłem w pythonie3.4 Zauważyłem, że dostęp do intzmiennej klasy jest w rzeczywistości około 5 do 11 nanosekund szybciej niż ta sama zmienna instancji na mojej starej stacji roboczej - nie jestem pewien, co codepath sprawia, że ​​tak jest.
Alex Martelli,
45

Dalsze powtórzenie rady Mike'a i Alexa i dodanie własnego koloru ...

Używanie atrybutów instancji jest typowym ... bardziej idiomatycznym Pythonem. Atrybuty klas nie są używane tak często, ponieważ ich przypadki użycia są specyficzne. To samo dotyczy metod statycznych i klasowych oraz metod „normalnych”. Są to specjalne konstrukcje odnoszące się do konkretnych przypadków użycia, w przeciwnym razie jest to kod stworzony przez anormalnego programistę, który chce się pochwalić, że zna jakiś niejasny zakątek programowania w Pythonie.

Alex wspomina w swojej odpowiedzi, że dostęp będzie (trochę) szybszy dzięki o jeden mniejszy poziom wyszukiwania ... pozwólcie, że wyjaśnię dalej tym, którzy nie wiedzą jeszcze, jak to działa. Jest bardzo podobny do dostępu zmiennego, którego kolejność wyszukiwania jest następująca:

  1. miejscowi
  2. nielokalne
  3. globalne
  4. wbudowane

W przypadku dostępu do atrybutów kolejność jest następująca:

  1. instancja
  2. klasa
  3. klasy bazowe określone przez MRO (kolejność rozwiązywania metod)

Obie techniki działają w sposób „na lewą stronę”, co oznacza, że ​​najpierw sprawdzane są najbardziej lokalne obiekty, a następnie sprawdzane są warstwy zewnętrzne.

W powyższym przykładzie załóżmy, że szukasz pathatrybutu. Gdy napotka odniesienie takie jak „ self.path”, Python najpierw sprawdzi atrybuty instancji w celu dopasowania. Gdy to się nie powiedzie, sprawdza klasę, z której utworzono obiekt. Na koniec przeszuka klasy bazowe. Jak stwierdził Alex, jeśli twój atrybut zostanie znaleziony w instancji, nie musi szukać gdzie indziej, stąd oszczędność czasu.

Jeśli jednak nalegasz na atrybuty klas, potrzebujesz tego dodatkowego wyszukiwania. Lub inną alternatywą jest odwoływanie się do obiektu za pośrednictwem klasy zamiast instancji, np. MyController.pathZamiast self.path. To jest bezpośrednie wyszukiwanie, które obejdzie odroczone wyszukiwanie, ale jak Alex wspomina poniżej, jest to zmienna globalna, więc tracisz ten bit, który myślałeś, że zamierzasz zapisać (chyba że utworzysz lokalne odniesienie do [globalnej] nazwy klasy ).

Najważniejsze jest to, że przez większość czasu powinieneś używać atrybutów instancji. Będą jednak sytuacje, w których atrybut klasy będzie odpowiednim narzędziem do tego zadania. Kod używający obu w tym samym czasie będzie wymagał największej staranności, ponieważ użycie selfzapewni ci dostęp tylko do obiektu atrybutu instancji i cienia do atrybutu klasy o tej samej nazwie. W takim przypadku musisz użyć access atrybutu przez nazwę klasy, aby się do niego odwołać.

wescpy
źródło
@wescpy, ale MyControllerjest wyszukiwany w globalnych, więc całkowity koszt jest wyższy niż self.pathgdzie pathjest zmienna instancji (ponieważ selfjest lokalna dla metody == superszybkie wyszukiwanie).
Alex Martelli
ach, prawda. dobry chwyt. Myślę, że jedynym rozwiązaniem jest utworzenie lokalnego odniesienia ... w tym momencie nie jest to naprawdę tego warte.
wescpy
24

W razie wątpliwości prawdopodobnie potrzebujesz atrybutu instancji.

Atrybuty klas najlepiej rezerwować na specjalne okazje, w których mają sens. Jedynym bardzo częstym przypadkiem użycia są metody. Nie jest rzadkością , aby korzystać z atrybutów klasy dla stałych tylko do odczytu, że przypadki trzeba wiedzieć (choć jedyną korzyścią jest to, czy chcesz także dostęp z zewnątrz klasy), ale należy być ostrożnym z pewnością o przechowywanie w nich żadnego państwa , co rzadko jest tym, czego chcesz. Nawet jeśli będziesz mieć tylko jedną instancję, powinieneś napisać klasę tak, jak każdą inną, co zwykle oznacza użycie atrybutów instancji.

Mike Graham
źródło
1
Zmienne klasy są rodzajem stałych tylko do odczytu. Gdyby Python pozwolił mi zdefiniować stałe, zapisałbym to jako stałe.
deamon
1
@deamon, jest trochę bardziej prawdopodobne, że moje stałe zostaną umieszczone całkowicie poza definicjami klas i nazywane nimi wielkimi literami. Umieszczenie ich w klasie też jest w porządku. Nadanie im atrybutów instancji nic nie zaszkodzi, ale może być trochę dziwne. Nie sądzę, żeby to był problem, w którym społeczność zbytnio popiera jedną z opcji.
Mike Graham
@MikeGraham FWIW, przewodnik Google po stylu Pythona, sugeruje unikanie zmiennych globalnych na rzecz zmiennych klas. Są jednak wyjątki.
Dennis
Oto nowy link do przewodnika Google po stylu Pythona . Teraz jest po prostu napisane: avoid global variablesa ich definicja jest taka, że ​​zmienne globalne są również zmiennymi zadeklarowanymi jako atrybuty klas. Jednak własny przewodnik po stylu Pythona ( PEP-8 ) powinien być pierwszym miejscem do zadawania pytań tego rodzaju. Wtedy Twój własny umysł powinien być narzędziem z wyboru (oczywiście możesz też czerpać pomysły na przykład z Google).
colidyre
4

To samo pytanie na temat wydajności dostępu do zmiennych klas w Pythonie - kod tutaj zaadaptowany z @Edward Loper

Najszybciej uzyskuje się dostęp do zmiennych lokalnych, które są w dużej mierze powiązane ze zmiennymi modułowymi, po których następują zmienne klasy, a po nich zmienne instancji.

Istnieją 4 zakresy, z których można uzyskać dostęp do zmiennych:

  1. Zmienne instancji (self.varname)
  2. Zmienne klas (Classname.varname)
  3. Zmienne modułu (VARNAME)
  4. Zmienne lokalne (nazwa zmiennej)

Test:

import timeit

setup='''
XGLOBAL= 5
class A:
    xclass = 5
    def __init__(self):
        self.xinstance = 5
    def f1(self):
        xlocal = 5
        x = self.xinstance
    def f2(self):
        xlocal = 5
        x = A.xclass
    def f3(self):
        xlocal = 5
        x = XGLOBAL
    def f4(self):
        xlocal = 5
        x = xlocal
a = A()
'''
print('access via instance variable: %.3f' % timeit.timeit('a.f1()', setup=setup, number=300000000) )
print('access via class variable: %.3f' % timeit.timeit('a.f2()', setup=setup, number=300000000) )
print('access via module variable: %.3f' % timeit.timeit('a.f3()', setup=setup, number=300000000) )
print('access via local variable: %.3f' % timeit.timeit('a.f4()', setup=setup, number=300000000) )

Wynik:

access via instance variable: 93.456
access via class variable: 82.169
access via module variable: 72.634
access via local variable: 72.199
robertcollier4
źródło