Jaki jest cel metod klasowych?


250

Uczę się Pythona, a moją ostatnią lekcją było to, że Python nie jest Javą , więc spędziłem trochę czasu przekształcając wszystkie moje metody klasy w funkcje.

Teraz zdaję sobie sprawę, że nie muszę używać metod Class do tego, co zrobiłbym z staticmetodami w Javie, ale teraz nie jestem pewien, kiedy ich użyję. Wszystkie porady, które mogę znaleźć na temat metod klasy Python, są zgodne z zasadami dla początkujących, takich jak ja, powinni unikać ich, a standardowa dokumentacja jest najbardziej nieprzejrzysta podczas ich omawiania.

Czy ktoś ma dobry przykład użycia metody klasy w Pythonie, a przynajmniej ktoś może mi powiedzieć, kiedy metody klasy można rozsądnie zastosować?

Odpowiedzi:


178

Metody klasowe są przeznaczone, gdy trzeba mieć metody, które nie są specyficzne dla żadnego konkretnego wystąpienia, ale nadal obejmują klasę w jakiś sposób. Najciekawsze w nich jest to, że można je zastąpić podklasami, co jest po prostu niemożliwe w statycznych metodach Javy lub funkcjach Pythona na poziomie modułu.

Jeśli masz klasę MyClassi funkcję na poziomie modułu, która działa na MyClass (fabryka, stub wstrzykiwania zależności itp.), Uczyń to classmethod. Wtedy będzie dostępny dla podklas.


Widzę. Ale co, jeśli chcę po prostu skategoryzować metodę, która nie potrzebuje MyClass, ale nadal ma jakiś sens, jeśli grupuję w innej MyClass? Mogę jednak pomyśleć o przeniesieniu go do modułu pomocniczego ...
swdev,

Mam pytanie związane z pierwotnym, może mógłbyś odpowiedzieć na to w tym samym czasie? Który sposób wywoływania metod klasowych obiektu jest „lepszy” lub „bardziej idiomatyczny”: obj.cls_mthd(...)lub type(obj).cls_mthd(...)?
Alexey

63

Metody fabryczne (konstruktory alternatywne) są rzeczywiście klasycznym przykładem metod klasowych.

Zasadniczo metody klasowe są odpowiednie zawsze, gdy chcesz mieć metodę, która naturalnie pasuje do przestrzeni nazw klasy, ale nie jest powiązana z konkretnym wystąpieniem klasy.

Na przykład w doskonałym module unipath :

Aktualny katalog

  • Path.cwd()
    • Zwraca aktualny bieżący katalog; np Path("/tmp/my_temp_dir"). To jest metoda klasowa.
  • .chdir()
    • Zrób sobie bieżący katalog.

Ponieważ bieżący katalog obejmuje cały proces, cwdmetoda nie ma konkretnej instancji, z którą należy ją powiązać. Jednak zmiana cwdkatalogu na daną Pathinstancję powinna rzeczywiście być metodą instancji.

Hmmm ... jak Path.cwd()zresztą zwraca Pathinstancję, myślę, że można ją uznać za metodę fabryczną ...


1
Tak ... pierwsze były metody fabryczne. Również wzdłuż tych linii są rzeczy, które mogłyby zaimplementować wzorzec Singleton, na przykład: getAndOptionallyCreateSomeSingleInstanceOfSomeResource ()
Jemenake

3
W Pythonie są to metody statyczne, a nie metody klasowe.
Jaykul

46

Pomyśl o tym w ten sposób: normalne metody są przydatne, aby ukryć szczegóły wysyłki: możesz pisać myobj.foo()bez martwienia się, czy foo()metoda jest zaimplementowana przez myobjklasę obiektu, czy jedną z jego klas nadrzędnych. Metody klasy są dokładnie analogiczne do tego, ale zamiast tego z obiektem klasy: pozwalają na wywołanie MyClass.foo()bez martwienia się o foo()to, czy jest implementowane specjalnie przez MyClassto, że potrzebował własnej specjalizowanej wersji, czy też pozwala klasie macierzystej obsłużyć wywołanie.

Metody klasowe są niezbędne, gdy wykonujesz konfigurację lub obliczenia, które poprzedzają utworzenie rzeczywistej instancji, ponieważ dopóki instancja nie istnieje, oczywiście nie możesz użyć instancji jako punktu wysyłki dla wywołań metod. Dobry przykład można zobaczyć w kodzie źródłowym SQLAlchemy; spójrz na dbapi()metodę klasy pod następującym linkiem:

https://github.com/zzzeek/sqlalchemy/blob/ab6946769742602e40fb9ed9dde5f642885d1906/lib/sqlalchemy/dialects/mssql/pymssql.py#L47

Widać, że dbapi()metoda, której backend bazy danych używa do importowania biblioteki bazy danych specyficznej dla dostawcy, której potrzebuje na żądanie, jest metodą klasową, ponieważ musi zostać uruchomiona, zanim zaczną się tworzyć instancje określonego połączenia z bazą danych - ale nie może być prostą funkcją lub funkcją statyczną, ponieważ chcą, aby mogła wywoływać inne, wspierające metody, które podobnie mogą wymagać napisania dokładniej w podklasach niż w klasie nadrzędnej. A jeśli wysyłasz do funkcji lub klasy statycznej, wówczas „zapominasz” i tracisz wiedzę o tym, która klasa inicjuje.


Zepsuty link, proszę dostosować
piertoni

28

Ostatnio chciałem mieć bardzo lekką klasę rejestrowania, która generowałaby różne ilości danych wyjściowych w zależności od poziomu rejestrowania, który można ustawić programowo. Ale nie chciałem tworzyć instancji klasy za każdym razem, gdy chciałem wyświetlać komunikat debugowania, błąd lub ostrzeżenie. Ale chciałem także podsumować funkcjonowanie tego narzędzia do rejestrowania i umożliwić jego ponowne użycie bez deklaracji globali.

Użyłem więc zmiennych klas i @classmethoddekoratora, aby to osiągnąć.

Dzięki mojej prostej klasie Logging mogę wykonać następujące czynności:

Logger._level = Logger.DEBUG

Następnie, w moim kodzie, jeśli chciałem wyrzucić kilka informacji debugujących, musiałem po prostu napisać kod

Logger.debug( "this is some annoying message I only want to see while debugging" )

Błędy można wyeliminować

Logger.error( "Wow, something really awful happened." )

W środowisku „produkcyjnym” mogę określić

Logger._level = Logger.ERROR

a teraz zostanie wyświetlony tylko komunikat o błędzie. Komunikat debugowania nie zostanie wydrukowany.

Oto moja klasa:

class Logger :
    ''' Handles logging of debugging and error messages. '''

    DEBUG = 5
    INFO  = 4
    WARN  = 3
    ERROR = 2
    FATAL = 1
    _level = DEBUG

    def __init__( self ) :
        Logger._level = Logger.DEBUG

    @classmethod
    def isLevel( cls, level ) :
        return cls._level >= level

    @classmethod
    def debug( cls, message ) :
        if cls.isLevel( Logger.DEBUG ) :
            print "DEBUG:  " + message

    @classmethod
    def info( cls, message ) :
        if cls.isLevel( Logger.INFO ) :
            print "INFO :  " + message

    @classmethod
    def warn( cls, message ) :
        if cls.isLevel( Logger.WARN ) :
            print "WARN :  " + message

    @classmethod
    def error( cls, message ) :
        if cls.isLevel( Logger.ERROR ) :
            print "ERROR:  " + message

    @classmethod
    def fatal( cls, message ) :
        if cls.isLevel( Logger.FATAL ) :
            print "FATAL:  " + message

I trochę kodu, który to trochę testuje:

def logAll() :
    Logger.debug( "This is a Debug message." )
    Logger.info ( "This is a Info  message." )
    Logger.warn ( "This is a Warn  message." )
    Logger.error( "This is a Error message." )
    Logger.fatal( "This is a Fatal message." )

if __name__ == '__main__' :

    print "Should see all DEBUG and higher"
    Logger._level = Logger.DEBUG
    logAll()

    print "Should see all ERROR and higher"
    Logger._level = Logger.ERROR
    logAll()

10
Ten sposób nie wydaje mi się to dobrym przykładem metody klasy są dobre, ponieważ może to mieć wszystko zostało zrobione w mniej skomplikowany sposób po prostu podejmowania wszystkich metod klasy funkcje modułu poziomie i rezygnacji z klasy w ogóle.
martineau,

10
Co ważniejsze, Python zapewnia bardzo prostą w użyciu klasę rejestrowania. Tak gorzej niż stworzenie mniej niż optymalnego rozwiązania, wymyśliłem koło na nowo. Podwójne uderzenie ręką. Ale to przynajmniej działa przykład. Nie jestem jednak pewien, czy zgadzam się na pozbycie się klasy na poziomie koncepcyjnym. Czasami chcesz zamknąć kod w celu łatwego ponownego użycia, a metody rejestrowania są doskonałym celem do ponownego użycia.
Marvo,

3
Dlaczego nie użyłbyś metody statycznej?
bdforbes,


10

Gdy użytkownik loguje się na mojej stronie, obiekt User () jest tworzony na podstawie nazwy użytkownika i hasła.

Jeśli potrzebuję obiektu użytkownika bez zalogowanego użytkownika (np. Administrator może chcieć usunąć konto innego użytkownika, więc muszę utworzyć instancję tego użytkownika i wywołać jego metodę usuwania):

Mam metody klasowe do przechwycenia obiektu użytkownika.

class User():
    #lots of code
    #...
    # more code

    @classmethod
    def get_by_username(cls, username):
        return cls.query(cls.username == username).get()

    @classmethod
    def get_by_auth_id(cls, auth_id):
        return cls.query(cls.auth_id == auth_id).get()

9

Myślę, że najbardziej jednoznaczna jest odpowiedź AmanKow . Sprowadza się to do tego, jak chcesz uporządkować swój kod. Możesz napisać wszystko jako funkcje na poziomie modułu, które są opakowane w przestrzeń nazw modułu tj

module.py (file 1)
---------
def f1() : pass
def f2() : pass
def f3() : pass


usage.py (file 2)
--------
from module import *
f1()
f2()
f3()
def f4():pass 
def f5():pass

usage1.py (file 3)
-------------------
from usage import f4,f5
f4()
f5()

Powyższy kod proceduralny nie jest dobrze zorganizowany, ponieważ po zaledwie 3 modułach robi się mylący, co robi każda metoda? Możesz używać długich opisowych nazw dla funkcji (jak w Javie), ale nadal twój kod staje się niemożliwy do zarządzania bardzo szybko.

Zorientowanym obiektowo sposobem jest rozbicie kodu na zarządzalne bloki, tzn. Klasy i obiekty oraz funkcje mogą być powiązane z instancjami obiektów lub klasami.

Dzięki funkcjom klasowym zyskujesz inny poziom podziału w kodzie w porównaniu z funkcjami na poziomie modułu. Możesz więc pogrupować powiązane funkcje w obrębie klasy, aby uczynić je bardziej specyficznymi dla zadania przypisanego do tej klasy. Na przykład możesz utworzyć klasę plików:

class FileUtil ():
  def copy(source,dest):pass
  def move(source,dest):pass
  def copyDir(source,dest):pass
  def moveDir(source,dest):pass

//usage
FileUtil.copy("1.txt","2.txt")
FileUtil.moveDir("dir1","dir2")

Ten sposób jest bardziej elastyczny i łatwiejszy w utrzymaniu, grupujesz funkcje razem i jest to bardziej oczywiste dla tego, co robi każda funkcja. Zapobiegasz również konfliktom nazw, na przykład kopia funkcji może istnieć w innym zaimportowanym module (na przykład kopii sieciowej), którego używasz w kodzie, więc używając pełnej nazwy FileUtil.copy () usuwasz problem i obie funkcje kopiowania można stosować obok siebie.


1
Aby używać f1 (), f2 () itd., Tak jak to robiłeś, nie powinieneś używać z importu modułu * i importu użytkowania *?
Jblasco

@Jblasco Poprawiłem instrukcje importu, aby usunąć zamieszanie, tak, jeśli importujesz moduł, musisz poprzedzić funkcje nazwą modułu. tj. moduł importu -> module.f1 () itp.
firephil

Nie zgadzam się z tą odpowiedzią; Python to nie Java. Kwestia niemożliwego do zarządzania kodu pojawia się najwyraźniej z powodu celowego wyboru złych nazwisk w przykładzie. Jeśli zamiast tego zreplikowałeś FileUtilmetody klas jako funkcje file_utilmodułu, byłoby to porównywalne i nie nadużywałoby OOP, gdy w rzeczywistości nie istniały żadne obiekty (w rzeczywistości można argumentować, że jest to preferowane, ponieważ nie kończy się to from file_util import FileUtilani z innymi gadatliwościami). Konflikty nazw można w podobny sposób uniknąć w kodzie proceduralnym, wykonując import file_utilzamiast from file_util import ....
ForgottenUmbrella

7

Szczerze? Nigdy nie znalazłem zastosowania metody statycznej ani metody klasowej. Nie widziałem jeszcze operacji, której nie można wykonać za pomocą funkcji globalnej lub metody instancji.

Byłoby inaczej, gdyby Python używał prywatnych i chronionych członków bardziej niż Java. W Javie potrzebuję metody statycznej, aby móc uzyskać dostęp do prywatnych członków instancji w celu wykonywania różnych czynności. W Pythonie rzadko jest to konieczne.

Zwykle widzę ludzi używających metod statycznych i metod klasy, gdy wszystko, co naprawdę muszą zrobić, to lepiej używać przestrzeni nazw na poziomie modułu w Pythonie.


1
Prywatne: _nazwa_ zmiennej i chronione: __nazwa_ zmiennej
Bradley Kreider

1
Jeden nieodzowne stosowanie jest unittest na setUpClass i tearDownClass. Używasz unittestów, prawda? :)
dbn

7

Pozwala pisać ogólne metody klas, których można używać z dowolną kompatybilną klasą.

Na przykład:

@classmethod
def get_name(cls):
    print cls.name

class C:
    name = "tester"

C.get_name = get_name

#call it:
C.get_name()

Jeśli nie używasz @classmethod, możesz to zrobić za pomocą własnego słowa kluczowego, ale wymaga ono wystąpienia klasy:

def get_name(self):
    print self.name

class C:
    name = "tester"

C.get_name = get_name

#call it:
C().get_name() #<-note the its an instance of class C

5

Pracowałem z PHP, a ostatnio zastanawiałem się, co się dzieje z tą klasą? Podręcznik Pythona jest bardzo techniczny i bardzo krótki, więc nie pomoże w zrozumieniu tej funkcji. Byłem googling i googling i znalazłem odpowiedź -> http://code.anjanesh.net/2007/12/python-classmethods.html .

Jeśli jesteś leniwy, kliknij go. Moje wyjaśnienie jest krótsze i poniżej. :)

w PHP (może nie wszyscy znacie PHP, ale ten język jest tak prosty, że wszyscy powinni zrozumieć, o czym mówię) mamy zmienne statyczne takie jak to:


class A
{

    static protected $inner_var = null;

    static public function echoInnerVar()
    {
        echo self::$inner_var."\n";
    }

    static public function setInnerVar($v)
    {
        self::$inner_var = $v;
    }

}

class B extends A
{
}

A::setInnerVar(10);
B::setInnerVar(20);

A::echoInnerVar();
B::echoInnerVar();

Wynik będzie w obu przypadkach 20.

Jednak w Pythonie możemy dodać dekorator @classmethod, a zatem możliwe jest uzyskanie odpowiednio 10 i 20. Przykład:


class A(object):
    inner_var = 0

    @classmethod
    def setInnerVar(cls, value):
        cls.inner_var = value

    @classmethod
    def echoInnerVar(cls):
        print cls.inner_var


class B(A):
    pass


A.setInnerVar(10)
B.setInnerVar(20)

A.echoInnerVar()
B.echoInnerVar()

Sprytne, prawda?


Jednym z potencjalnych problemów z twoim przykładem Pythona jest to, że jeśli B.setInnerVar(20)został pominięty, wydrukowałby 10dwa razy (zamiast dawać błąd przy drugim wywołaniu echoInnerBar (), który nie inner_varzostał zdefiniowany
martineau,

5

Metody klasowe zapewniają „cukier semantyczny” (nie wiem, czy termin ten jest powszechnie używany) - lub „wygodę semantyczną”.

Przykład: masz zestaw klas reprezentujących obiekty. Możesz chcieć mieć metodę klasy all()lub find()pisać User.all()lub User.find(firstname='Guido'). Można to oczywiście zrobić za pomocą funkcji na poziomie modułu ...


Przykład zakłada oczywiście, że klasa śledzi wszystkie swoje instancje i może uzyskać do nich dostęp po ich utworzeniu - coś, co nie jest wykonywane automatycznie.
martineau,

1
„cukier semantyczny” brzmi jak dobre dopasowanie do „cukru syntaktycznego”.
XTL

3

To, co właśnie mnie uderzyło, wywodzące się z Ruby, to fakt, że tak zwana metoda klasowa i tak zwana metoda instancji jest tylko funkcją o znaczeniu semantycznym zastosowaną do jej pierwszego parametru, który jest cicho przekazywany, gdy funkcja jest wywoływana jako metoda obiekt (tj obj.meth()).

Zwykle ten obiekt musi być instancją, ale @classmethod dekorator metod zmienia reguły przekazywania klasy. Możesz wywołać metodę klasy na instancji (to tylko funkcja) - pierwszym argumentem będzie jej klasa.

Ponieważ jest to tylko funkcja , można ją zadeklarować tylko raz w dowolnym zakresie (tj. classDefinicji). Dlatego też, jako niespodzianka dla Rubyist, że nie możesz mieć metody klasy i metody instancji o tej samej nazwie .

Rozważ to:

class Foo():
  def foo(x):
    print(x)

Możesz zadzwonić foodo instancji

Foo().foo()
<__main__.Foo instance at 0x7f4dd3e3bc20>

Ale nie na zajęciach:

Foo.foo()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: unbound method foo() must be called with Foo instance as first argument (got nothing instead)

Teraz dodaj @classmethod:

class Foo():
  @classmethod
  def foo(x):
    print(x)

Wywołanie instancji przechodzi teraz przez jej klasę:

Foo().foo()
__main__.Foo

podobnie jak wzywanie klasy:

Foo.foo()
__main__.Foo

Jest to tylko konwencja, która nakazuje, abyśmy używali selftego pierwszego argumentu w metodzie instancji i clsmetodzie klasy. Nie użyłem żadnego z nich, aby zilustrować, że to tylko argument. W Ruby selfjest słowem kluczowym.

Porównaj z Ruby:

class Foo
  def foo()
    puts "instance method #{self}"
  end
  def self.foo()
    puts "class method #{self}"
  end
end

Foo.foo()
class method Foo

Foo.new.foo()
instance method #<Foo:0x000000020fe018>

Metoda klasy Python jest tylko dekorowaną funkcją i możesz używać tych samych technik do tworzenia własnych dekoratorów . Udekorowana metoda otacza prawdziwą metodę (w przypadku, @classmethodgdy przekazuje dodatkowy argument klasy). Podstawowa metoda jest nadal dostępna, ukryta, ale nadal dostępna .


przypis: Napisałem to po konflikcie nazw między metodą klasy a instancji, która wzbudziła moją ciekawość. Jestem daleki od eksperta w Pythonie i chciałbym komentować, jeśli którekolwiek z nich jest złe.


„który jest cicho przekazywany, gdy funkcja jest wywoływana jako metoda obiektu” - uważam, że nie jest to dokładne. AFICT, nic nie jest „cicho przekazywane” w Pythonie. IMO, w tym sensie Python jest znacznie bardziej sensowny niż Ruby (z wyjątkiem super()). AFICT, „magia” ma miejsce, gdy atrybut jest ustawiony lub odczytany. Wywołanie obj.meth(arg)w Pythonie (w przeciwieństwie do Ruby) oznacza po prostu (obj.meth)(arg). Nigdzie nic nie jest po cichu przekazywane, obj.methto tylko obiekt na żądanie, który akceptuje jeden argument mniej niż funkcja, z której został utworzony.
Alexey

obj.methz kolei oznacza po prostu getattr(obj, "meth").
Alexey

Sensowna jest odpowiedź dostosowana do osób pochodzących z innych popularnych języków. Jednak jedną rzeczą, której brakuje w tej rozmowie, która ją łączy, jest pojęcie deskryptora Pythona . Funkcje są deskryptorami i w ten sposób pierwszy argument „automagicznie” przechodzi przez instancję, gdy funkcja jest atrybutem klasy. Jest to także sposób implementacji metod klas, a implementacja czystego pythona znajduje się w połączonym HOWTO I. Fakt, że jest także dekoratorem, jest swego rodzaju pomocnikiem
juanpa.arrivillaga,

2

To ciekawy temat. Uważam, że metoda klasy python działa jak singleton, a nie fabryka (która zwraca wyprodukowaną instancję klasy). Jest to singleton, ponieważ istnieje wspólny obiekt, który jest generowany (słownik), ale tylko raz dla klasy, ale wspólny dla wszystkich instancji.

Aby to zilustrować, oto przykład. Zauważ, że wszystkie instancje mają odniesienie do pojedynczego słownika. To nie jest wzór fabryczny, jak go rozumiem. Jest to prawdopodobnie bardzo unikalne dla Pythona.

class M():
 @classmethod
 def m(cls, arg):
     print "arg was",  getattr(cls, "arg" , None),
     cls.arg = arg
     print "arg is" , cls.arg

 M.m(1)   # prints arg was None arg is 1
 M.m(2)   # prints arg was 1 arg is 2
 m1 = M()
 m2 = M() 
 m1.m(3)  # prints arg was 2 arg is 3  
 m2.m(4)  # prints arg was 3 arg is 4 << this breaks the factory pattern theory.
 M.m(5)   # prints arg was 4 arg is 5

2

Kilka razy zadawałem sobie to samo pytanie. I chociaż chłopaki tutaj starali się to wytłumaczyć, IMHO najlepszą odpowiedzią (i najprostszą), jaką znalazłem, jest opis metody Class w Dokumentacji Pythona.

Istnieje również odniesienie do metody statycznej. A jeśli ktoś już zna metody instancji (które, jak zakładam), ta odpowiedź może być ostatnim elementem, który poskłada wszystko razem ...

Dalsze i głębsze opracowanie na ten temat można znaleźć również w dokumentacji: Standardowa hierarchia typów (przewiń w dół do sekcji Metody wystąpienia )


1

@classmethodmoże być przydatny do łatwego tworzenia instancji obiektów tej klasy z zasobów zewnętrznych. Rozważ następujące:

import settings

class SomeClass:
    @classmethod
    def from_settings(cls):
        return cls(settings=settings)

    def __init__(self, settings=None):
        if settings is not None:
            self.x = settings['x']
            self.y = settings['y']

Następnie w innym pliku:

from some_package import SomeClass

inst = SomeClass.from_settings()

Dostęp do inst.x da tę samą wartość co ustawienia ['x'].


0

Klasa oczywiście określa zestaw instancji. Metody klasy działają na poszczególnych instancjach. Metody klasy (i zmienne) to miejsce do zawieszenia wszystkich informacji związanych z zestawem instancji.

Na przykład, jeśli twoja klasa definiuje zestaw uczniów, możesz chcieć mieć zmienne klasowe lub metody, które definiują takie rzeczy, jak zestaw ocen, do których uczniowie mogą należeć.

Możesz także użyć metod klasowych do zdefiniowania narzędzi do pracy nad całym zestawem. Na przykład Student.all_of_em () może zwrócić wszystkich znanych studentów. Oczywiście, jeśli twój zestaw instancji ma więcej struktur niż tylko zestaw, możesz podać metody klasowe, które będą wiedzieć o tej strukturze. Students.all_of_em (grade = „juniorzy”)

Takie techniki prowadzą do przechowywania elementów zbioru instancji w strukturach danych zakorzenionych w zmiennych klasowych. Musisz wtedy uważać, aby nie frustrować zbierania śmieci.

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.