Differenze del metodo di classe in Python: rilegato, non associato e statico


242

Qual è la differenza tra i seguenti metodi di classe?

Uno è statico e l'altro no?

class Test(object):
  def method_one(self):
    print "Called method_one"

  def method_two():
    print "Called method_two"

a_test = Test()
a_test.method_one()
a_test.method_two()

18
Nessuna differenza diversa dalla definizione method_two () non è valida e la sua chiamata ha esito negativo.
anatoly techtonik,

14
@techtonik: non c'è niente di sbagliato nella definizione di method_two! Viene chiamato in una specifica errata / non valida, cioè con un argomento in più.
0xc0de,

1
I tuoi sono entrambi metodi di istanza , non metodi di classe. Si crea un metodo di classe applicando @classmethodalla definizione. Il primo parametro dovrebbe essere chiamato clsinvece di selfe riceverà l'oggetto di classe piuttosto che un'istanza della tua classe: Test.method_three()e a_test.method_three()sono equivalenti.
Lutz Prechelt,

Perché vorresti creare una definizione di funzione senza l' selfargomento? Esiste un caso d'uso forte per questo?
alpha_989,

Risposte:


412

In Python esiste una distinzione tra metodi associati e non associati .

Fondamentalmente, una chiamata a una funzione membro (come method_one), una funzione associata

a_test.method_one()

è tradotto in

Test.method_one(a_test)

cioè una chiamata a un metodo non associato. Per questo motivo, una chiamata alla tua versione di method_twofallirà con aTypeError

>>> a_test = Test() 
>>> a_test.method_two()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: method_two() takes no arguments (1 given) 

Puoi cambiare il comportamento di un metodo usando un decoratore

class Test(object):
    def method_one(self):
        print "Called method_one"

    @staticmethod
    def method_two():
        print "Called method two"

Il decoratore dice alla metaclasse predefinita incorporata type(la classe di una classe, vedi questa domanda ) di non creare metodi associati per method_two.

Ora è possibile richiamare direttamente il metodo statico su un'istanza o sulla classe:

>>> a_test = Test()
>>> a_test.method_one()
Called method_one
>>> a_test.method_two()
Called method_two
>>> Test.method_two()
Called method_two

17
Sostengo questa risposta, è superiore alla mia. Ben fatto Torsten :)
Freespace il

24
in Python 3 i metodi non associati sono deprecati. invece c'è solo una funzione.
boldnik,

@boldnik, perché dici che i metodi non associati sono deprecati? metodi statici sono ancora presenti nella documentazione: docs.python.org/3/library/functions.html#staticmethod
alpha_989

195

I metodi in Python sono una cosa molto, molto semplice, una volta comprese le basi del sistema descrittivo. Immagina la seguente classe:

class C(object):
    def foo(self):
        pass

Ora diamo un'occhiata a quella classe nella shell:

>>> C.foo
<unbound method C.foo>
>>> C.__dict__['foo']
<function foo at 0x17d05b0>

Come puoi vedere se accedi fooall'attributo sulla classe ottieni un metodo non associato, tuttavia all'interno dell'archiviazione della classe (il dict) c'è una funzione. Perché? La ragione di ciò è che la classe della tua classe implementa una __getattribute__che risolve i descrittori. Sembra complesso, ma non lo è. C.fooè approssimativamente equivalente a questo codice in quel caso speciale:

>>> C.__dict__['foo'].__get__(None, C)
<unbound method C.foo>

Questo perché le funzioni hanno un __get__metodo che le rende descrittori. Se hai un'istanza di una classe è quasi la stessa, solo questa Noneè l'istanza della classe:

>>> c = C()
>>> C.__dict__['foo'].__get__(c, C)
<bound method C.foo of <__main__.C object at 0x17bd4d0>>

Ora perché lo fa Python? Perché l'oggetto metodo associa il primo parametro di una funzione all'istanza della classe. Ecco da dove viene il sé. Ora a volte non vuoi che la tua classe faccia diventare una funzione un metodo, ecco dove staticmethodentra in gioco:

 class C(object):
  @staticmethod
  def foo():
   pass

Il staticmethoddecoratore avvolge la tua classe e implementa un manichino __get__che restituisce la funzione wrapped come funzione e non come metodo:

>>> C.__dict__['foo'].__get__(None, C)
<function foo at 0x17d0c30>

Spero che questo lo spieghi.


12
Il staticmethoddecoratore avvolge la tua classe (...) Questa frase è un po 'fuorviante in quanto la classe che viene racchiusa è la classe del metodo fooe non la classe in cui fooè definita.
Piotr Dobrogost,

12

Quando si chiama un membro della classe, Python utilizza automaticamente un riferimento all'oggetto come primo parametro. La variabile in selfrealtà non significa nulla, è solo una convenzione di codifica. Potresti chiamarlo gargaloose volessi. Detto questo, la chiamata a method_twosolleva a TypeError, perché Python tenta automaticamente di passare un parametro (il riferimento al suo oggetto padre) a un metodo che è stato definito come privo di parametri.

Per farlo effettivamente funzionare, è possibile aggiungere questo alla definizione della classe:

method_two = staticmethod(method_two)

oppure potresti usare la @staticmethod funzione decoratore .


4
Intendi "la sintassi del decoratore di funzioni @staticmethod".
martedì

11
>>> class Class(object):
...     def __init__(self):
...         self.i = 0
...     def instance_method(self):
...         self.i += 1
...         print self.i
...     c = 0
...     @classmethod
...     def class_method(cls):
...         cls.c += 1
...         print cls.c
...     @staticmethod
...     def static_method(s):
...         s += 1
...         print s
... 
>>> a = Class()
>>> a.class_method()
1
>>> Class.class_method()    # The class shares this value across instances
2
>>> a.instance_method()
1
>>> Class.instance_method() # The class cannot use an instance method
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: unbound method instance_method() must be called with Class instance as first argument (got nothing instead)
>>> Class.instance_method(a)
2
>>> b = 0
>>> a.static_method(b)
1
>>> a.static_method(a.c) # Static method does not have direct access to 
>>>                      # class or instance properties.
3
>>> Class.c        # a.c above was passed by value and not by reference.
2
>>> a.c
2
>>> a.c = 5        # The connection between the instance
>>> Class.c        # and its class is weak as seen here.
2
>>> Class.class_method()
3
>>> a.c
5

2
Class.instance_method() # The class cannot use an instance methodpuò usare. Basta passare l'istanza manualmente:Class.instance_method(a)
warvariuc

@warwaruk È lì, guarda la linea sotto la TyeErrorlinea.
kzh

si l'ho visto dopo. tuttavia, imo, non è corretto dire "La classe non può usare un metodo di istanza", perché l'hai appena fatto una riga di seguito.
Warvariuc,

@kzh, grazie per la tua spiegazione. Quando hai chiamato a.class_method(), sembra che sia a.cstato aggiornato 1, quindi la chiamata a ha Class.class_method()aggiornato la Class.cvariabile 2. Tuttavia, quando hai assegnato a.c=5, perché non ti sei Class.caggiornato 5?
alpha_989,

@ alpha_989 python cerca innanzitutto un attributo direttamente sulla propria istanza di ovjects e, se non è presente, lo cerca nella sua classe per impostazione predefinita. Se hai altre domande a riguardo, non esitare ad aprire una domanda e collegarla qui e saremo lieti di aiutarti ulteriormente.
kzh

4

method_two non funzionerà perché stai definendo una funzione membro ma non le stai dicendo di cosa sia membro la funzione. Se esegui l'ultima riga otterrai:

>>> a_test.method_two()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: method_two() takes no arguments (1 given)

Se si definiscono le funzioni membro per una classe, il primo argomento deve essere sempre "self".


3

Spiegazione accurata di Armin Ronacher sopra, espandendo le sue risposte in modo che i principianti come me lo capiscano bene:

La differenza nei metodi definiti in una classe, sia statica che di istanza (esiste ancora un altro tipo - metodo di classe - non discusso qui in modo da saltarlo), risiede nel fatto che siano in qualche modo legati all'istanza della classe o meno. Ad esempio, dire se il metodo riceve un riferimento all'istanza della classe durante il runtime

class C:
    a = [] 
    def foo(self):
        pass

C # this is the class object
C.a # is a list object (class property object)
C.foo # is a function object (class property object)
c = C() 
c # this is the class instance

La __dict__proprietà del dizionario dell'oggetto classe contiene il riferimento a tutte le proprietà e i metodi di un oggetto classe e quindi

>>> C.__dict__['foo']
<function foo at 0x17d05b0>

il metodo foo è accessibile come sopra. Un punto importante da notare qui è che tutto in Python è un oggetto e quindi i riferimenti nel dizionario sopra puntano essi stessi ad altri oggetti. Permettetemi di chiamarli Class Property Objects - o come CPO nell'ambito della mia risposta per brevità.

Se un CPO è un descrittore, l'interprete python chiama il __get__()metodo del CPO per accedere al valore che contiene.

Per determinare se un CPO è un descrittore, l'interpretazione di Python verifica se implementa il protocollo descrittore. Implementare il protocollo descrittore significa implementare 3 metodi

def __get__(self, instance, owner)
def __set__(self, instance, value)
def __delete__(self, instance)

per es

>>> C.__dict__['foo'].__get__(c, C)

dove

  • self è il CPO (potrebbe essere un'istanza di list, str, function etc) ed è fornito dal runtime
  • instance è l'istanza della classe in cui è definito questo CPO (l'oggetto 'c' sopra) e deve essere esplicitamente fornito da noi
  • ownerè la classe in cui è definito questo CPO (l'oggetto di classe 'C' sopra) e deve essere fornito da noi. Tuttavia questo è perché lo stiamo chiamando sul CPO. quando lo chiamiamo sull'istanza, non è necessario fornirlo poiché il runtime può fornire l'istanza o la sua classe (polimorfismo)
  • value è il valore previsto per il CPO e deve essere fornito da noi

Non tutti i CPO sono descrittori. Per esempio

>>> C.__dict__['foo'].__get__(None, C)
<function C.foo at 0x10a72f510> 
>>> C.__dict__['a'].__get__(None, C)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: 'list' object has no attribute '__get__'

Questo perché la classe list non implementa il protocollo descrittore.

Quindi l'argomento self in c.foo(self)è richiesto perché la sua firma del metodo è in realtà questa C.__dict__['foo'].__get__(c, C)(come spiegato sopra, C non è necessaria in quanto può essere scoperto o polimorfizzato) ed è anche per questo che si ottiene un TypeError se non si passa l'argomento dell'istanza richiesta.

Se noti che il metodo è ancora referenziato tramite la classe Oggetto C e l'associazione con l'istanza della classe viene ottenuta passando un contesto sotto forma dell'oggetto di istanza in questa funzione.

Questo è davvero fantastico poiché se hai scelto di non mantenere alcun contesto o nessun legame con l'istanza, tutto ciò che era necessario era scrivere una classe per avvolgere il descrittore CPO e sovrascrivere il suo __get__()metodo per non richiedere alcun contesto. Questa nuova classe è ciò che chiamiamo decoratore e viene applicata tramite la parola chiave@staticmethod

class C(object):
  @staticmethod
  def foo():
   pass

L'assenza di contesto nel nuovo CPO foointegrato non genera un errore e può essere verificata come segue:

>>> C.__dict__['foo'].__get__(None, C)
<function foo at 0x17d0c30>

Il caso d'uso di un metodo statico è più di uno spazio dei nomi e della manutenibilità del codice (togliendolo da una classe e rendendolo disponibile in tutto il modulo, ecc.).

Forse è meglio scrivere metodi statici piuttosto che metodi di istanza ogni volta che è possibile, a meno che ovviamente non sia necessario contestualizzare i metodi (come accedere alle variabili di istanza, variabili di classe ecc.). Uno dei motivi è quello di facilitare la raccolta dei rifiuti non mantenendo riferimenti indesiderati agli oggetti.


1

questo è un errore.

prima di tutto, la prima riga dovrebbe essere così (attenzione alle maiuscole)

class Test(object):

Ogni volta che chiami un metodo di una classe, si ottiene come primo argomento (da cui il nome self) e method_two dà questo errore

>>> a.method_two()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: method_two() takes no arguments (1 given)

1

Il secondo non funzionerà perché quando lo chiami in quel modo Python tenta internamente di chiamarlo con l'istanza a_test come primo argomento, ma il tuo method_two non accetta alcun argomento, quindi non funzionerà, otterrai un runtime errore. Se si desidera l'equivalente di un metodo statico, è possibile utilizzare un metodo di classe. C'è molto meno bisogno di metodi di classe in Python rispetto ai metodi statici in linguaggi come Java o C #. Molto spesso la soluzione migliore è utilizzare un metodo nel modulo, al di fuori di una definizione di classe, quelli funzionano in modo più efficiente rispetto ai metodi di classe.


Se definisco una funzione Class Test(object): @staticmethod def method_two(): print(“called method_two”) Un caso d'uso, stavo pensando è quando vuoi che la funzione faccia parte di una classe, ma non vuoi che l'utente acceda direttamente alla funzione. Quindi method_twopuò essere chiamato da altre funzioni all'interno Testdell'istanza, ma non può essere chiamato usando a_test.method_two(). Se uso def method_two(), funzionerà per questo caso d'uso? O c'è un modo migliore per modificare la definizione della funzione, quindi funziona come previsto per il caso d'uso sopra?
alpha_989,

1

La chiamata a method_two genererà un'eccezione per la mancata accettazione del parametro automatico da parte del runtime Python.

Se vuoi creare un metodo statico in una classe Python, decoralo con il staticmethod decorator.

Class Test(Object):
  @staticmethod
  def method_two():
    print "Called method_two"

Test.method_two()


0

La definizione di method_twonon è valida. Quando chiami method_two, otterraiTypeError: method_two() takes 0 positional arguments but 1 was given dall'interprete.

Un metodo di istanza è una funzione limitata quando lo chiami come a_test.method_two(). Accetta automaticamente self, che indica un'istanza di Test, come primo parametro. Tramite il selfparametro, un metodo di istanza può accedere liberamente agli attributi e modificarli sullo stesso oggetto.


0

Metodi non associati

I metodi non associati sono metodi che non sono ancora associati a nessuna particolare istanza di classe.

Metodi associati

I metodi associati sono quelli associati a un'istanza specifica di una classe.

Come documentato qui , il sé può riferirsi a cose diverse a seconda della funzione legata, non legata o statica.

Dai un'occhiata al seguente esempio:

class MyClass:    
    def some_method(self):
        return self  # For the sake of the example

>>> MyClass().some_method()
<__main__.MyClass object at 0x10e8e43a0># This can also be written as:>>> obj = MyClass()

>>> obj.some_method()
<__main__.MyClass object at 0x10ea12bb0>

# Bound method call:
>>> obj.some_method(10)
TypeError: some_method() takes 1 positional argument but 2 were given

# WHY IT DIDN'T WORK?
# obj.some_method(10) bound call translated as
# MyClass.some_method(obj, 10) unbound method and it takes 2 
# arguments now instead of 1 

# ----- USING THE UNBOUND METHOD ------
>>> MyClass.some_method(10)
10

Dal momento che non abbiamo usato l'istanza di classe - obj nell'ultima chiamata, possiamo dire che sembra un metodo statico.

In tal caso, qual è la differenza tra una MyClass.some_method(10)chiamata e una chiamata a una funzione statica decorata con a@staticmethod decoratore?

Usando il decoratore, chiariamo esplicitamente che il metodo verrà usato senza prima creare un'istanza. Normalmente non ci si aspetterebbe che i metodi dei membri della classe vengano utilizzati senza l'istanza e accedervi può causare possibili errori a seconda della struttura del metodo.

Inoltre, aggiungendo il @staticmethoddecoratore, stiamo rendendo possibile essere raggiunti anche attraverso un oggetto.

class MyClass:    
    def some_method(self):
        return self    

    @staticmethod
    def some_static_method(number):
        return number

>>> MyClass.some_static_method(10)   # without an instance
10
>>> MyClass().some_static_method(10)   # Calling through an instance
10

Non puoi fare l'esempio sopra con i metodi di istanza. Potresti sopravvivere al primo (come abbiamo fatto prima) ma il secondo verrà tradotto in una chiamata non associata MyClass.some_method(obj, 10)che genererà un TypeErrordato che il metodo di istanza accetta un argomento e tu hai involontariamente provato a passarne due.

Quindi, potresti dire "se posso chiamare metodi statici attraverso un'istanza e una classe MyClass.some_static_methode MyClass().some_static_methoddovrei essere gli stessi metodi". Sì!


0

Metodo associato = metodo di istanza

Metodo non associato = metodo statico.


Per favore aggiungi qualche ulteriore spiegazione alla tua risposta in modo che altri possano imparare da essa. Ad esempio, dai un'occhiata alle altre risposte a questa domanda
Nico Haase,
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.