Appel de la méthode statique de la classe dans le corps de la classe?

159

Lorsque j'essaie d'utiliser une méthode statique à partir du corps de la classe et que je définis la méthode statique en utilisant la staticmethodfonction intégrée en tant que décorateur, comme ceci:

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

J'obtiens l'erreur suivante:

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

Je comprends pourquoi cela se produit (liaison de descripteur) , et je peux le contourner en convertissant manuellement _stat_func()en une méthode statique après sa dernière utilisation, comme ceci:

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

Ma question est donc:

Y a-t-il de meilleures façons, par exemple, plus propres ou plus «pythoniques», d'accomplir cela?

Martineau
la source
4
Si vous posez des questions sur la pythonicité, le conseil standard est de ne pas l'utiliser staticmethoddu tout. Ils sont généralement plus utiles en tant que fonctions au niveau du module, auquel cas votre problème n'est pas un problème. classmethod, d'autre part ...
Benjamin Hodgson
1
@poorsod: Oui, je connais cette alternative. Cependant, dans le code réel où j'ai rencontré ce problème, faire de la fonction une méthode statique plutôt que de la placer au niveau du module a plus de sens que dans l'exemple simple utilisé dans ma question.
martineau

Réponses:

180

staticmethodles objets ont apparemment un __func__attribut stockant la fonction brute d'origine (il est logique qu'ils le fassent). Donc cela fonctionnera:

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

En passant, même si je soupçonnais qu'un objet staticmethod avait une sorte d'attribut stockant la fonction d'origine, je n'avais aucune idée des détails. Dans l'esprit d'apprendre à quelqu'un à pêcher plutôt que de lui donner un poisson, voici ce que j'ai fait pour enquêter et le découvrir (un C&P de ma session 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>

Des types similaires de fouille dans une session interactive ( direst très utile) peuvent souvent résoudre ce type de questions très rapidement.

Ben
la source
Bonne mise à jour ... J'étais sur le point de vous demander comment vous le saviez puisque je ne le vois pas dans la documentation - ce qui me rend un peu nerveux à l'idée de l'utiliser car il pourrait s'agir d'un "détail d'implémentation".
martineau
Après une lecture plus approfondie, je vois que ce __func__n'est qu'un autre nom im_funcet a été ajouté à Py 2.6 pour la compatibilité ascendante Python 3.
martineau
2
Je vois, donc techniquement, il n'est pas documenté dans ce contexte.
martineau
1
@AkshayHazari L' __func__attribut d'une méthode statique vous donne une référence à la fonction d'origine, exactement comme si vous n'aviez jamais utilisé le staticmethoddécorateur. Donc, si votre fonction nécessite des arguments, vous devrez les passer lors de l'appel __func__. Le message d'erreur qui vous semble tout à fait semble que vous ne lui avez donné aucun argument. Si l'exemple stat_funcde cet article prenait deux arguments, vous utiliseriez_ANS = stat_func.__func__(arg1, arg2)
Ben
1
@AkshayHazari Je ne suis pas sûr de comprendre. Ils peuvent être des variables, ils doivent juste être des variables dans la portée: soit définies plus tôt dans la même portée où la classe est définie (souvent globale), soit définies plus tôt dans la portée de la classe ( stat_funcelle-même est une telle variable). Vouliez-vous dire que vous ne pouvez pas utiliser les attributs d'instance de la classe que vous définissez? C'est vrai, mais sans surprise; nous n'avons une instance de la classe, puisque nous définissons toujours la classe! Mais de toute façon, je les entendais simplement comme des supports pour tous les arguments que vous vouliez faire passer; vous pouvez y utiliser des littéraux.
Ben
24

C'est comme ça que je préfère:

class Klass(object):

    @staticmethod
    def stat_func():
        return 42

    _ANS = stat_func.__func__()

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

Je préfère cette solution à Klass.stat_func, en raison du principe DRY . Cela me rappelle la raison pour laquelle il y a un nouveausuper() dans Python 3 :)

Mais je suis d'accord avec les autres, généralement le meilleur choix est de définir une fonction au niveau du module.

Par exemple, avec la @staticmethodfonction, la récursivité peut ne pas sembler très bonne (vous auriez besoin de casser le principe DRY en appelant Klass.stat_funcà l' intérieur Klass.stat_func). C'est parce que vous n'avez pas de référence à selfla méthode statique interne. Avec la fonction de niveau de module, tout semblera OK.

Jan Vorcak
la source
Bien que je convienne que l'utilisation self.__class__.stat_func()de méthodes régulières présente des avantages (DRY et tout ça) par rapport à l'utilisation Klass.stat_func(), ce n'était pas vraiment le sujet de ma question - en fait, j'ai évité d'utiliser la première pour ne pas brouiller le problème de l'incohérence.
martineau
1
Ce n'est pas vraiment un problème de DRY en soi. self.__class__est mieux car si une sous-classe remplace stat_func, alors Subclass.methodappellera la sous-classe stat_func. Mais pour être tout à fait honnête, dans cette situation, il est préférable d'utiliser simplement une méthode réelle au lieu d'une méthode statique.
asmeurer
@asmeurer: Je ne peux pas utiliser une vraie méthode car aucune instance de la classe n'a encore été créée - elles ne peuvent pas l'être car la classe elle-même n'a même pas encore été complètement définie.
martineau
Je voulais un moyen d'appeler la méthode statique pour ma classe (qui est héritée; donc le parent a réellement la fonction) et cela semble de loin le meilleur (et fonctionnera toujours si je le remplace plus tard).
whitey04
11

Qu'en est-il de l'injection de l'attribut de classe après la définition de 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
Pedro Romano
la source
1
Ceci est similaire à mes premières tentatives de contournement, mais je préférerais quelque chose qui était à l'intérieur de la classe ... en partie parce que l'attribut impliqué a un trait de soulignement principal et est privé.
martineau
11

Cela est dû au fait que staticmethod est un descripteur et nécessite une extraction d'attribut au niveau de la classe pour exercer le protocole du descripteur et obtenir le vrai appelable.

À partir du code source:

Il peut être appelé soit sur la classe (par exemple C.f()) soit sur une instance (par exemple C().f()); l'instance est ignorée sauf pour sa classe.

Mais pas directement de l'intérieur de la classe pendant sa définition.

Mais comme un commentateur l'a mentionné, ce n'est pas du tout une conception "pythonique". Utilisez simplement une fonction de niveau module à la place.

Keith
la source
Comme je l'ai dit dans ma question, je comprends pourquoi le code d'origine n'a pas fonctionné. Pouvez-vous expliquer (ou fournir un lien vers quelque chose qui fait) pourquoi il est considéré comme non Pythonique?
martineau
La méthode statique nécessite que l'objet de classe lui-même fonctionne correctement. Mais si vous l'appelez depuis la classe au niveau de la classe, la classe n'est pas vraiment complètement définie à ce stade. Vous ne pouvez donc pas encore le référencer. Il est possible d'appeler la méthode statique depuis l'extérieur de la classe, une fois qu'elle est définie. Mais " Pythonic " n'est pas vraiment bien défini, tout comme l'esthétique. Je peux vous dire que ma première impression de ce code n'était pas favorable.
Keith
Impression de quelle version (ou des deux)? Et pourquoi, précisément?
martineau
Je ne peux que deviner quel est votre objectif ultime, mais il me semble que vous voulez un attribut dynamique au niveau de la classe. Cela ressemble à un travail pour les métaclasses. Mais encore une fois, il pourrait y avoir une façon plus simple, ou une autre façon de regarder la conception qui élimine et simplifie sans sacrifier la fonctionnalité.
Keith
3
Non, ce que je veux faire est en fait assez simple - qui consiste à éliminer du code commun et à le réutiliser, à la fois pour créer un attribut de classe privée au moment de la création de la classe, et plus tard dans une ou plusieurs méthodes de classe. Ce code commun n'a aucune utilité en dehors de la classe, donc je veux naturellement qu'il en fasse partie. Utiliser une métaclasse (ou un décorateur de classe) fonctionnerait, mais cela semble exagéré pour quelque chose qui devrait être facile à faire, à mon humble avis.
martineau
8

Et cette solution? Il ne repose pas sur la connaissance de la @staticmethodmise en œuvre du décorateur. La classe interne StaticMethod joue comme un conteneur de fonctions d'initialisation statiques.

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
schatten
la source
2
+1 pour la créativité, mais je ne suis plus inquiète à propos de l'utilisation __func__car elle est maintenant officiellement documentée (voir la section Autres changements de langage des nouveautés de Python 2.7 et sa référence au numéro 5982 ). Votre solution est encore plus portable, car elle fonctionnerait probablement aussi dans les versions Python antérieures à 2.6 (quand a __func__été introduite pour la première fois comme synonyme de im_func).
martineau
C'est la seule solution qui fonctionne avec Python 2.6.
benselme
@benselme: Je ne peux pas vérifier votre affirmation car je n'ai pas Python 2.6 installé, mais je doute sérieusement que ce soit le seul ...
martineau