Chiamare il metodo statico di classe all'interno del corpo della classe?


159

Quando provo a usare un metodo statico all'interno del corpo della classe e definisco il metodo statico usando la staticmethodfunzione integrata come decoratore, in questo modo:

class Klass(object):

    @staticmethod  # use as decorator
    def _stat_func():
        return 42

    _ANS = _stat_func()  # call the staticmethod

    def method(self):
        ret = Klass._stat_func() + Klass._ANS
        return ret

Ottengo il seguente errore:

Traceback (most recent call last):<br>
  File "call_staticmethod.py", line 1, in <module>
    class Klass(object): 
  File "call_staticmethod.py", line 7, in Klass
    _ANS = _stat_func() 
  TypeError: 'staticmethod' object is not callable

Capisco perché ciò sta accadendo (associazione del descrittore) e posso aggirare il problema convertendolo manualmente _stat_func()in un metodo statico dopo il suo ultimo utilizzo, in questo modo:

class Klass(object):

    def _stat_func():
        return 42

    _ANS = _stat_func()  # use the non-staticmethod version

    _stat_func = staticmethod(_stat_func)  # convert function to a static method

    def method(self):
        ret = Klass._stat_func() + Klass._ANS
        return ret

Quindi la mia domanda è:

Ci sono modi migliori, come quelli più puliti o più "Pythonic", per farlo?


4
Se stai chiedendo di Pythonicity, il consiglio standard è di non usarlo staticmethodaffatto. Di solito sono più utili come funzioni a livello di modulo, nel qual caso il tuo problema non è un problema. classmethod, d'altra parte ...
Benjamin Hodgson

1
@poorsod: Sì, sono a conoscenza di questa alternativa. Tuttavia, nel codice effettivo in cui ho riscontrato questo problema, rendere la funzione un metodo statico piuttosto che metterla a livello di modulo ha più senso di quanto non faccia nel semplice esempio utilizzato nella mia domanda.
martineau,

Risposte:


180

staticmethodgli oggetti hanno apparentemente un __func__attributo che memorizza la funzione raw originale (ha senso che dovevano farlo). Quindi questo funzionerà:

class Klass(object):

    @staticmethod  # use as decorator
    def stat_func():
        return 42

    _ANS = stat_func.__func__()  # call the staticmethod

    def method(self):
        ret = Klass.stat_func()
        return ret

Per inciso, anche se sospettavo che un oggetto staticmethod avesse una sorta di attributo che memorizzava la funzione originale, non avevo idea dei dettagli. Nello spirito di insegnare a qualcuno a pescare piuttosto che dargli un pesce, questo è quello che ho fatto per indagare e scoprirlo (un C&P dalla mia sessione di Python):

>>> class Foo(object):
...     @staticmethod
...     def foo():
...         return 3
...     global z
...     z = foo

>>> z
<staticmethod object at 0x0000000002E40558>
>>> Foo.foo
<function foo at 0x0000000002E3CBA8>
>>> dir(z)
['__class__', '__delattr__', '__doc__', '__format__', '__func__', '__get__', '__getattribute__', '__hash__', '__init__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__']
>>> z.__func__
<function foo at 0x0000000002E3CBA8>

Tipi simili di scavi in ​​una sessione interattiva ( dirè molto utile) possono spesso risolvere questi tipi di domande molto rapidamente.


Buon aggiornamento ... Stavo per chiederti come lo sapevi dal momento che non lo vedo nella documentazione, il che mi rende un po 'nervoso nell'usarlo perché potrebbe essere un "dettaglio di implementazione".
martineau,

Dopo ulteriori letture vedo che __func__è solo un altro nome per im_funced è stato aggiunto a Py 2.6 per la compatibilità con Python 3.
martineau,

2
Vedo, quindi tecnicamente non è documentato in questo contesto.
martineau,

1
@AkshayHazari L' __func__attributo di un metodo statico ti fornisce un riferimento alla funzione originale, esattamente come se non avessi mai usato il staticmethoddecoratore. Quindi se la tua funzione richiede argomenti, dovrai passarli quando chiami __func__. Il messaggio di errore suona abbastanza come se non gli avessi dato alcun argomento. Se l' stat_funcesempio di questo post prendesse due argomenti, _ANS = stat_func.__func__(arg1, arg2)
Ben

1
@AkshayHazari Non sono sicuro di aver capito. Possono essere variabili, devono solo essere variabili nell'ambito: o definite in precedenza nello stesso ambito in cui la classe è definita (spesso globale) o definita in precedenza nell'ambito della classe ( stat_funcstessa è tale variabile). Volevi dire che non puoi usare gli attributi di istanza della classe che stai definendo? È vero, ma non sorprende; non abbiamo un'istanza della classe, poiché stiamo ancora definendo la classe! Ma comunque, li intendevo solo come standin per qualsiasi argomento tu volessi passare; potresti usare letterali lì.
Ben

24

Questo è il modo in cui preferisco:

class Klass(object):

    @staticmethod
    def stat_func():
        return 42

    _ANS = stat_func.__func__()

    def method(self):
        return self.__class__.stat_func() + self.__class__._ANS

Preferisco questa soluzione a Klass.stat_funccausa del principio DRY . Mi ricorda il motivo per cui c'è un nuovosuper() in Python 3 :)

Ma sono d'accordo con gli altri, di solito la scelta migliore è definire una funzione a livello di modulo.

Ad esempio con la @staticmethodfunzione, la ricorsione potrebbe non sembrare molto buona (dovresti rompere il principio DRY chiamando Klass.stat_funcall'interno Klass.stat_func). Questo perché non hai riferimenti al selfmetodo statico interno. Con la funzione a livello di modulo, tutto sembrerà OK.


Anche se concordo sul fatto che l'uso self.__class__.stat_func()di metodi regolari ha dei vantaggi (DRY e tutto il resto) rispetto all'utilizzo Klass.stat_func(), questo non era proprio l'argomento della mia domanda - in effetti ho evitato di usare il primo per non appannare il problema dell'incoerenza.
martineau,

1
Non è davvero un problema di DRY di per sé. self.__class__è meglio perché se una sottoclasse ha la precedenza stat_func, Subclass.methodchiamerà quella della sottoclasse stat_func. Ma per essere completamente onesti, in quella situazione è molto meglio usare solo un metodo reale anziché statico.
asmeurer,

@asmeurer: Non posso usare un metodo reale perché non sono state ancora create istanze della classe — non possono essere poiché la classe stessa non è stata ancora completamente definita.
martineau,

Volevo un modo per chiamare il metodo statico per la mia classe (che è ereditato; quindi il genitore ha effettivamente la funzione) e questo sembra di gran lunga migliore (e funzionerà ancora se lo sovrascrivo in seguito).
whitey04

11

Che dire dell'iniezione dell'attributo class dopo la definizione della classe?

class Klass(object):

    @staticmethod  # use as decorator
    def stat_func():
        return 42

    def method(self):
        ret = Klass.stat_func()
        return ret

Klass._ANS = Klass.stat_func()  # inject the class attribute with static method value

1
Questo è simile ai miei primi tentativi di lavoro, ma preferirei qualcosa che era all'interno della classe ... parzialmente perché l'attributo in questione ha un carattere di sottolineatura iniziale ed è privato.
martineau,

11

Ciò è dovuto al fatto che il metodo statico è un descrittore e richiede un recupero di attributo a livello di classe per esercitare il protocollo descrittore e ottenere il vero callable.

Dal codice sorgente:

Può essere chiamato sulla classe (ad es. C.f()) O su un'istanza (ad es. C().f()); l'istanza viene ignorata ad eccezione della sua classe.

Ma non direttamente dall'interno della classe mentre viene definita.

Ma come ha detto un commentatore, questo non è affatto un design "Pythonic". Usa invece una funzione a livello di modulo.


Come ho detto nella mia domanda, capisco perché il codice originale non ha funzionato. Puoi spiegare (o fornire un collegamento a qualcosa che lo fa) perché è considerato non pitonico?
martineau,

Il metodo statico richiede che l'oggetto classe stesso funzioni correttamente. Ma se lo chiami dall'interno della classe a livello di classe, la classe non è veramente completamente definita a quel punto. Quindi non puoi ancora fare riferimento. Va bene chiamare il metodo statico dall'esterno della classe, dopo che è stato definito. Ma " Pythonic " non è in realtà ben definito, molto simile all'estetica. Posso dirti che la mia prima impressione di quel codice non è stata favorevole.
Keith,

Impressione di quale versione (o entrambe)? E perché, in particolare?
martineau,

Posso solo immaginare quale sia il tuo obiettivo finale, ma mi sembra che tu voglia un attributo dinamico a livello di classe. Sembra un lavoro per i metaclassi. Ma ancora una volta potrebbe esserci un modo più semplice o un altro modo di guardare al design che elimina e semplifica senza sacrificare la funzionalità.
Keith

3
No, quello che voglio fare è in realtà piuttosto semplice - che è quello di scomporre un po 'di codice comune e riutilizzarlo, sia per creare un attributo di classe privato al momento della creazione della classe, sia in seguito all'interno di uno o più metodi di classe. Questo codice comune non serve a niente al di fuori della classe, quindi naturalmente voglio che ne faccia parte. Usare una metaclasse (o un decoratore di classe) avrebbe funzionato, ma sembra eccessivo per qualcosa che dovrebbe essere facile da fare, IMHO.
martineau,

8

E questa soluzione? Non si basa sulla conoscenza dell'implementazione del @staticmethoddecoratore. Classe interna StaticMethod viene utilizzato come contenitore di funzioni di inizializzazione statica.

class Klass(object):

    class StaticMethod:
        @staticmethod  # use as decorator
        def _stat_func():
            return 42

    _ANS = StaticMethod._stat_func()  # call the staticmethod

    def method(self):
        ret = self.StaticMethod._stat_func() + Klass._ANS
        return ret

2
+1 per la creatività, ma non sono più preoccupato per l'uso __func__perché ora è ufficialmente documentato (vedere la sezione Modifiche di altre lingue di Novità in Python 2.7 e il suo riferimento al numero 5982 ). La tua soluzione è ancora più portatile, dal momento che probabilmente funzionerebbe anche nelle versioni Python precedenti alla 2.6 (quando è __func__stata introdotta per la prima volta come sinonimo di im_func).
martineau,

Questa è l'unica soluzione che funziona con Python 2.6.
benselme,

@benselme: non riesco a verificare il tuo reclamo perché non ho installato Python 2.6, ma dubito seriamente che sia l'unico ...
martineau,
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.