Qual è lo scopo dei metodi di classe?


250

Sto insegnando a me stesso Python e la mia lezione più recente è stata che Python non è Java , quindi ho appena trascorso un po 'di tempo a trasformare tutti i metodi della mia classe in funzioni.

Ora mi rendo conto che non ho bisogno di usare i metodi Class per quello che avrei fatto con i staticmetodi in Java, ma ora non sono sicuro di quando li userei. Tutti i consigli che posso trovare sui metodi di Python Class sono sulla falsariga dei neofiti come me dovrebbero tenersi alla larga da loro, e la documentazione standard è al massimo opaca quando li discute.

Qualcuno ha un buon esempio di utilizzo di un metodo Class in Python o almeno qualcuno può dirmi quando i metodi Class possono essere usati in modo ragionevole?

Risposte:


178

I metodi di classe servono quando devi avere metodi che non sono specifici di una particolare istanza, ma che coinvolgono comunque la classe in qualche modo. La cosa più interessante su di loro è che possono essere sovrascritti da sottoclassi, cosa semplicemente impossibile nei metodi statici di Java o nelle funzioni a livello di modulo di Python.

Se hai una classe MyClasse una funzione a livello di modulo che opera su MyClass (factory, stub di iniezione delle dipendenze, ecc.), Rendila a classmethod. Quindi sarà disponibile per le sottoclassi.


Vedo. Ma che ne dici se voglio solo classificare un metodo, che non ha bisogno di MyClass, ma ha comunque senso se raggruppo in un'altra MyClass? Mi viene in mente di spostarlo nel modulo di supporto però ...
swdev,

Ho una domanda relativa a quella originale, potresti rispondere nello stesso tempo? Quale modo di chiamare i metodi di classe di un oggetto è "migliore" o "più idiomatico": obj.cls_mthd(...)oppure type(obj).cls_mthd(...)?
Alexey,

63

I metodi di fabbrica (costruttori alternativi) sono in effetti un classico esempio di metodi di classe.

Fondamentalmente, i metodi di classe sono adatti ogni volta che si desidera avere un metodo che si adatta naturalmente allo spazio dei nomi della classe, ma non è associato a una particolare istanza della classe.

Ad esempio, nell'eccellente modulo unipath :

Directory corrente

  • Path.cwd()
    • Restituisce la directory corrente attuale; ad es Path("/tmp/my_temp_dir"). Questo è un metodo di classe.
  • .chdir()
    • Rendi self la directory corrente.

Poiché la directory corrente è estesa al processo, il cwdmetodo non ha un'istanza particolare alla quale dovrebbe essere associato. Tuttavia, la modifica della cwddirectory di una determinata Pathistanza dovrebbe effettivamente essere un metodo di istanza.

Hmmm ... come Path.cwd()effettivamente restituisce Pathun'istanza, immagino che potrebbe essere considerato un metodo di fabbrica ...


1
Sì ... i metodi di fabbrica sono stati la prima cosa che mi è venuta in mente. Inoltre, ci sono cose che implementerebbero un modello Singleton, come: getAndOptionallyCreateSomeSingleInstanceOfSomeResource ()
Jemenake

3
In Python, questi sono metodi statici, non metodi di classe.
Jaykul,

46

Pensaci in questo modo: i metodi normali sono utili per nascondere i dettagli dell'invio: puoi digitare myobj.foo()senza preoccuparti se il foo()metodo è implementato dalla myobjclasse dell'oggetto o da una delle sue classi principali. I metodi di classe sono esattamente analoghi a questo, ma con l'oggetto class invece: ti permettono di chiamare MyClass.foo()senza doversi preoccupare se foo()è stato implementato appositamente MyClassperché aveva bisogno di una propria versione specializzata, o se sta lasciando che la sua classe genitrice gestisse la chiamata.

I metodi di classe sono essenziali quando si eseguono impostazioni o calcoli che precedono la creazione di un'istanza effettiva, perché fino a quando l'istanza non esiste ovviamente non è possibile utilizzare l'istanza come punto di invio per le chiamate del metodo. Un buon esempio può essere visualizzato nel codice sorgente SQLAlchemy; dai un'occhiata al dbapi()metodo di classe al seguente link:

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

Si può vedere che il dbapi()metodo, che un back-end di database utilizza per importare la libreria di database specifica del fornitore di cui ha bisogno su richiesta, è un metodo di classe perché deve essere eseguito prima che vengano create le istanze di una particolare connessione al database, ma che non può essere una funzione semplice o statica, perché vogliono che sia in grado di chiamare altri, supportando metodi che potrebbero allo stesso modo essere scritti in modo più specifico nelle sottoclassi che nella loro classe genitore. E se invii a una funzione o una classe statica, allora "dimentichi" e perdi la conoscenza di quale classe sta eseguendo l'inizializzazione.


Collegamento interrotto, si prega di regolare
piertoni il

28

Recentemente volevo una classe di registrazione molto leggera che producesse quantità variabili di output a seconda del livello di registrazione che poteva essere impostato a livello di programmazione. Ma non volevo creare un'istanza della classe ogni volta che volevo emettere un messaggio di debug o un errore o un avviso. Ma volevo anche incapsulare il funzionamento di questa funzione di registrazione e renderla riutilizzabile senza la dichiarazione di alcun globale.

Quindi ho usato le variabili di classe e il @classmethoddecoratore per raggiungere questo obiettivo.

Con la mia semplice classe di registrazione, ho potuto fare quanto segue:

Logger._level = Logger.DEBUG

Quindi, nel mio codice, se volevo sputare un sacco di informazioni di debug, dovevo semplicemente codificare

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

Gli errori potrebbero essere eliminati

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

Nell'ambiente "produzione", posso specificare

Logger._level = Logger.ERROR

e ora verrà emesso solo il messaggio di errore. Il messaggio di debug non verrà stampato.

Ecco la mia lezione:

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

E un po 'di codice che lo verifica solo un po':

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
Questo non mi sembra che un buon esempio di metodo di classe sia adatto perché avrebbe potuto essere fatto in modo meno complesso semplicemente facendo tutte le funzioni a livello di modulo dei metodi di classe e eliminando del tutto la classe.
martineau,

10
Oh, ancora più importante, Python fornisce una classe di registrazione molto semplice da usare. Così peggio che creare una soluzione non ottimale, ho reinventato la ruota. Doppio schiaffo. Ma fornisce almeno un esempio funzionante. Non sono sicuro di essere d'accordo con il liberarmi della classe, però, a livello concettuale. A volte si desidera incapsulare il codice per un facile riutilizzo e i metodi di registrazione sono un ottimo obiettivo per il riutilizzo.
Marvo,

3
Perché non dovresti usare un metodo statico?
bdforbes,


11

Quando un utente accede al mio sito Web, un oggetto User () viene istanziato dal nome utente e dalla password.

Se ho bisogno di un oggetto utente senza che l'utente sia lì per accedere (ad esempio, un utente amministratore potrebbe voler eliminare un altro account utente, quindi devo istanziare quell'utente e chiamare il suo metodo di eliminazione):

Ho metodi di classe per afferrare l'oggetto utente.

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

Penso che la risposta più chiara sia quella di AmanKow . Si riduce a come vuoi organizzare il tuo codice. Puoi scrivere tutto come funzioni a livello di modulo che sono racchiuse nello spazio dei nomi del modulo, ad es

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()

Il codice procedurale sopra non è ben organizzato, come puoi vedere dopo solo 3 moduli diventa confuso, cosa fa ogni metodo? Puoi usare nomi descrittivi lunghi per funzioni (come in Java) ma il tuo codice diventa ingestibile molto rapidamente.

Il modo orientato agli oggetti è di suddividere il codice in blocchi gestibili, ad esempio classi e oggetti e funzioni possono essere associati a istanze di oggetti o a classi.

Con le funzioni di classe ottieni un altro livello di divisione nel tuo codice rispetto alle funzioni a livello di modulo. Quindi è possibile raggruppare funzioni correlate all'interno di una classe per renderle più specifiche per un'attività assegnata a quella classe. Ad esempio è possibile creare una classe di utilità file:

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")

In questo modo è più flessibile e più gestibile, si raggruppano le funzioni ed è più ovvio a ciò che ogni funzione fa. Inoltre si evitano conflitti di nomi, ad esempio la copia della funzione potrebbe esistere in un altro modulo importato (ad esempio copia di rete) che si utilizza nel codice, quindi quando si utilizza il nome completo FileUtil.copy () si rimuove il problema ed entrambe le funzioni di copia può essere usato fianco a fianco.


1
Per usare f1 (), f2 () e così via, come hai fatto tu, non dovresti usare dall'importazione del modulo * e dall'importazione dell'uso *?
Jblasco,

@Jblasco Ho corretto le istruzioni di importazione per rimuovere la confusione, sì, se importi il ​​modulo devi aggiungere il prefisso alle funzioni con il nome del modulo. cioè import module -> module.f1 () etc
firephil

Non sono d'accordo con questa risposta; Python non è Java. La questione del codice ingestibile appare solo apparentemente a causa della scelta deliberata di nomi poveri nell'esempio. Se invece replicassi i FileUtilmetodi di classe come funzioni di un file_utilmodulo, sarebbe paragonabile e non abuserebbe di OOP quando non esiste alcun oggetto (in realtà potresti argomentare che è preferibile, perché non from file_util import FileUtilfinisci con o con altre verbosità). Allo stesso modo si possono evitare conflitti di nomi nel codice procedurale facendo import file_utilinvece di from file_util import ....
Forgotten

7

Onestamente? Non ho mai trovato un uso per metodo statico o metodo di classe. Devo ancora vedere un'operazione che non può essere eseguita utilizzando una funzione globale o un metodo di istanza.

Sarebbe diverso se Python usasse membri privati ​​e protetti più come fa Java. In Java, ho bisogno di un metodo statico per poter accedere ai membri privati ​​di un'istanza per fare cose. In Python, ciò è raramente necessario.

Di solito, vedo le persone che usano metodi statici e metodi di classe quando tutto ciò di cui hanno veramente bisogno è usare meglio gli spazi dei nomi a livello di modulo di Python.


1
Privato: _variable_name e protetto: __variable_name
Bradley Kreider

1
Un uso indispensabile è di unittest setUpClass e tearDownClass. Stai utilizzando unittests, giusto? :)
dbn

7

Ti consente di scrivere metodi di classe generici che puoi utilizzare con qualsiasi classe compatibile.

Per esempio:

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

class C:
    name = "tester"

C.get_name = get_name

#call it:
C.get_name()

Se non lo usi @classmethodpuoi farlo con la parola chiave self ma ha bisogno di un'istanza di Class:

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

Lavoravo con PHP e recentemente mi chiedevo, cosa sta succedendo con questo metodo di classe? Il manuale di Python è molto tecnico e molto breve, quindi non aiuta a comprendere quella funzione. Cercavo su Google e cercavo su Google e ho trovato la risposta -> http://code.anjanesh.net/2007/12/python-classmethods.html .

Se sei pigro fare clic su di esso. La mia spiegazione è più breve e più in basso. :)

in PHP (forse non tutti conoscono PHP, ma questa lingua è così semplice che tutti dovrebbero capire di cosa sto parlando) abbiamo variabili statiche come questa:


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();

L'output sarà in entrambi i casi 20.

Tuttavia in Python possiamo aggiungere @classmethod decorator e quindi è possibile avere rispettivamente l'output 10 e 20. Esempio:


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()

Intelligente, no?


Un potenziale problema con il tuo esempio di Python è che se B.setInnerVar(20)fosse stato lasciato fuori, sarebbe stato stampato 10due volte (invece di dare un errore sulla seconda chiamata echoInnerBar () che non inner_varera stato definito.
martineau

5

I metodi di classe forniscono uno "zucchero semantico" (non so se questo termine è ampiamente usato) - o "convenienza semantica".

Esempio: hai un insieme di classi che rappresentano oggetti. Potresti voler avere il metodo class all()o find()scrivere User.all()o User.find(firstname='Guido'). Ciò potrebbe essere fatto usando le funzioni a livello di modulo ovviamente


L'esempio presuppone, ovviamente, che la classe stia tenendo traccia di tutte le sue istanze e possa accedervi dopo che sono state create, cosa che non è stata fatta automaticamente.
martineau,

1
Lo "zucchero semantico" sembra una bella corrispondenza con lo "zucchero sintattico".
XTL

3

Quello che mi ha appena colpito, proveniente da Ruby, è che un cosiddetto metodo di classe e un cosiddetto metodo di istanza sono solo una funzione con significato semantico applicata al suo primo parametro, che viene silenziosamente passato quando la funzione viene chiamata come metodo di un oggetto (cioè obj.meth()).

Normalmente quell'oggetto deve essere un'istanza ma il @classmethod decoratore del metodo cambia le regole per passare una classe. Puoi chiamare un metodo di classe su un'istanza (è solo una funzione) - il primo argomento sarà la sua classe.

Poiché è solo una funzione , può essere dichiarata una sola volta in un determinato ambito (ad es. classDefinizione). Se segue quindi, come sorpresa per un Rubyist, non puoi avere un metodo di classe e un metodo di istanza con lo stesso nome .

Considera questo:

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

Puoi chiamare fooun'istanza

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

Ma non in una classe:

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)

Ora aggiungi @classmethod:

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

Chiamare un'istanza ora passa la sua classe:

Foo().foo()
__main__.Foo

come fare una lezione:

Foo.foo()
__main__.Foo

È solo la convenzione a dettare che usiamo selfper quel primo argomento su un metodo di istanza e clssu un metodo di classe. Non ho usato né qui per illustrare che è solo un argomento. In Ruby, selfè una parola chiave.

Contrasto con 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>

Il metodo della classe Python è solo una funzione decorata e puoi usare le stesse tecniche per creare i tuoi decoratori . Un metodo decorato avvolge il metodo reale (nel caso in @classmethodcui passi l'argomento di classe aggiuntivo). Il metodo sottostante è ancora lì, nascosto ma ancora accessibile .


nota a piè di pagina: l'ho scritto dopo che uno scontro di nomi tra una classe e un metodo di istanza ha suscitato la mia curiosità. Sono ben lungi dall'essere un esperto di Python e vorrei commenti se qualcosa di tutto ciò è sbagliato.


"che viene passato silenziosamente quando la funzione viene chiamata come metodo di un oggetto" - credo che questo non sia esatto. AFICT, nulla è "passato silenziosamente" in Python. IMO, Python è molto più sensibile di Ruby in questo senso (tranne che per super()). AFICT, la "magia" si verifica quando un attributo viene impostato o letto. La chiamata obj.meth(arg)in Python (a differenza di Ruby) significa semplicemente (obj.meth)(arg). Nulla viene silenziosamente passato da nessuna parte, obj.methè solo un oggetto richiamabile che accetta un argomento in meno rispetto alla funzione da cui è stato creato.
Alexey,

obj.meth, a sua volta, significa semplicemente getattr(obj, "meth").
Alexey,

Ha senso avere una risposta su misura per le persone che provengono da altre lingue comuni. Tuttavia, una cosa che manca a questa conversazione, che la lega insieme, è la nozione di descrittore Python . Le funzioni sono descrittori , ed è così che il primo argomento "automagicamente" viene passato a un'istanza quando una funzione è un attributo alla classe. È anche il modo in cui vengono implementati i metodi di classe e una implementazione di Python pura è fornita nell'HOWTO I collegato. Il fatto che sia anche un decoratore è una specie di accessorio
juanpa.arrivillaga,

2

Questo è un argomento interessante. La mia opinione è che il metodo di classe python funziona come un singleton piuttosto che una fabbrica (che restituisce un prodotto un'istanza di una classe). Il motivo per cui è un singleton è che esiste un oggetto comune che viene prodotto (il dizionario) ma solo una volta per la classe ma condiviso da tutte le istanze.

Per illustrare questo qui è un esempio. Si noti che tutte le istanze hanno un riferimento al singolo dizionario. Questo non è un modello di Fabbrica come lo capisco. Questo è probabilmente molto unico per Python.

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

Mi sono posto la stessa domanda alcune volte. E anche se i ragazzi qui hanno cercato di spiegarlo, IMHO la risposta migliore (e più semplice) che ho trovato è la descrizione del metodo Class nella Documentazione di Python.

C'è anche un riferimento al metodo statico. E nel caso in cui qualcuno conosca già i metodi di istanza (che presumo), questa risposta potrebbe essere l'ultimo pezzo per mettere tutto insieme ...

Ulteriori approfondimenti su questo argomento sono disponibili anche nella documentazione: la gerarchia dei tipi standard (scorrere fino alla sezione Metodi dell'istanza )


1

@classmethodpuò essere utile per istanziare facilmente oggetti di quella classe da risorse esterne. Considera quanto segue:

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

Quindi in un altro file:

from some_package import SomeClass

inst = SomeClass.from_settings()

L'accesso a inst.x darà lo stesso valore delle impostazioni ['x'].


0

Una classe definisce ovviamente una serie di istanze. E i metodi di una classe funzionano sulle singole istanze. I metodi (e le variabili) di classe rappresentano un luogo in cui appendere altre informazioni correlate all'insieme di istanze.

Ad esempio, se la tua classe definisce un insieme di studenti, potresti desiderare variabili di classe o metodi che definiscono cose come l'insieme di voti di cui gli studenti possono essere membri.

Puoi anche usare metodi di classe per definire strumenti per lavorare sull'intero set. Ad esempio Student.all_of_em () potrebbe restituire tutti gli studenti conosciuti. Ovviamente se il tuo set di istanze ha più struttura di un semplice set puoi fornire metodi di classe per conoscere quella struttura. Students.all_of_em (grade = 'junior')

Tecniche come questa tendono a portare alla memorizzazione dei membri dell'insieme di istanze in strutture di dati radicate nelle variabili di classe. È quindi necessario fare attenzione per evitare di frustrare la raccolta dei rifiuti.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.