web-dev-qa-db-fra.com

Fonction module vs staticmethod vs classmethod vs sans décorateurs: quel idiome est plus pythonique?

Je suis un développeur Java qui a joué avec Python. Je suis récemment tombé sur cet article qui mentionne les erreurs les plus courantes des programmeurs Java lorsqu'ils récupèrent Python. Le premier a attiré mon attention:

Une méthode statique en Java ne se traduit pas en méthode de classe Python. Bien sûr, il en résulte plus ou moins le même effet, mais l'objectif d'une méthode de classe est de faire quelque chose qui n'est généralement pas possible en Java (comme hériter d'un constructeur autre que celui par défaut). La traduction idiomatique d'une méthode statique Java est généralement une fonction au niveau du module, et non une méthode de classe ou une méthode statique. (Et les champs finaux statiques doivent être convertis en constantes au niveau du module.)

Ce n’est pas vraiment un problème de performances, mais un programmeur Python qui doit travailler avec un code Java comme celui-ci sera plutôt irrité par la saisie de Foo.Foo.someMethod alors que cela ne devrait être que Foo.someFunction. Mais notez que l'appel d'une méthode de classe implique une allocation de mémoire supplémentaire, contrairement à l'appel d'une méthode ou d'une fonction statique.

Oh, et toutes ces chaînes d'attributs Foo.Bar.Baz ne sont pas gratuites, non plus. En Java, le compilateur recherche ces noms en pointillés. Par conséquent, au moment de l'exécution, peu importe le nombre de noms que vous possédez. En Python, les recherches ont lieu au moment de l'exécution, ainsi chaque point compte. (N'oubliez pas qu'en Python, "Flat est meilleur que imbriqué", bien qu'il soit davantage lié à "La lisibilité compte" et "Simple est meilleur que complexe", plutôt que d'être à propos de la performance.)

J'ai trouvé cela un peu étrange car la documentation de staticmethod dit:

Les méthodes statiques en Python sont similaires à celles trouvées en Java ou en C++. Voir aussi classmethod () pour une variante utile pour créer des constructeurs de classe alternatifs.

Encore plus curieux, c'est que ce code:

class A:
    def foo(x):
        print(x)
A.foo(5)

Echoue comme prévu dans Python 2.7.3 mais fonctionne correctement sous 3.2.3 (bien que vous ne puissiez pas appeler la méthode sur une instance de A, uniquement sur la classe.)

Il y a donc trois façons d'implémenter des méthodes statiques (quatre si vous comptez en utilisant classmethod), chacune avec des différences subtiles, l'une d'entre elles apparemment non documentée. Cela semble contredire le mantra de Python Il devrait y avoir une - et de préférence une seule - manière évidente de le faire. Quel langage est le plus pythonique? Quels sont les avantages et les inconvénients de chacun?

Voici ce que j'ai compris jusqu'à présent:

Fonction du module:

  • Evite le problème Foo.Foo.f ()
  • Pollue l'espace de nom du module plus que les alternatives
  • Pas d'héritage

méthode statique:

  • Conserve les fonctions liées à la classe à l'intérieur de la classe et hors de l'espace de nom du module.
  • Permet d'appeler la fonction sur des instances de la classe.
  • Les sous-classes peuvent remplacer la méthode.

méthode de classe:

  • Identique à staticmethod, mais transmet également la classe en tant que premier argument.

Méthode régulière (Python 3 uniquement):

  • Identique à staticmethod, mais ne peut pas appeler la méthode sur des instances de la classe.

Est-ce que je réfléchis à cela? Est-ce que ce n'est pas un problème? S'il vous plaît aider!

58
Doval

La façon la plus simple d’y réfléchir est de déterminer le type d’objet dont la méthode a besoin pour faire son travail. Si votre méthode a besoin d'accéder à une instance, faites-en une méthode régulière. S'il a besoin d'accéder à la classe, faites-en une méthode de classe. S'il n'a pas besoin d'accéder à la classe ou à l'instance, faites-en une fonction. Il est rarement nécessaire de transformer quelque chose en méthode statique, mais si vous voulez qu'une fonction soit "groupée" avec une classe (par exemple, elle peut être remplacée) même si elle n'a pas besoin d'accéder à la classe, je suppose vous pouvez en faire une méthode statique.

J'ajouterais que mettre des fonctions au niveau du module ne "pollue" pas l'espace de noms. Si les fonctions sont censées être utilisées, elles ne polluent pas l'espace de noms, elles l'utilisent exactement comme il se doit. Les fonctions sont des objets légitimes dans un module, tout comme les classes ou toute autre chose. Il n'y a aucune raison de cacher une fonction dans une classe si elle n'a aucune raison d'être là.

78
BrenBarn

Excellente réponse de BrenBarn , mais je changerais 'S'il n'a pas besoin d'accéder à la classe ou à l'instance, faites-en une fonction' en:

'S'il n'a pas besoin d'accéder à la classe ou à l'instance ... mais est lié thématiquement à la classe (exemple typique: fonctions d'assistance et fonctions de conversion utilisées par d'autres méthodes de classe ou par d'autres constructeurs) , puis utilise staticmethod

sinon en faire un fonction du module

26
smci

Ce n'est pas vraiment une réponse, mais plutôt un long commentaire:

Encore plus curieux, c'est que ce code:

        class A:
            def foo(x):
                print(x)
        A.foo(5)

Echoue comme prévu dans Python 2.7.3 mais fonctionne correctement sous 3.2.3 (bien que Vous ne puissiez pas appeler la méthode sur une instance de A, mais uniquement sur la classe.)

Je vais essayer d'expliquer ce qui se passe ici.

C'est à proprement parler un abus du protocole de la méthode d'instance "normale".

Ce que vous définissez ici est une méthode, mais avec le premier (et seul) paramètre non nommé self, mais x. Bien sûr, vous pouvez appeler la méthode dans une instance de A, mais vous devrez l'appeler comme suit:

A().foo()

ou

a = A()
a.foo()

donc l'instance est donnée à la fonction comme premier argument.

La possibilité d’appeler des méthodes régulières via la classe a toujours été présente et fonctionne par

a = A()
A.foo(a)

Ici, lorsque vous appelez la méthode de la classe plutôt que sur l'instance, son premier paramètre n'est pas automatiquement attribué, mais vous devrez le fournir.

Tant qu'il s'agit d'une instance de A, tout va bien. Donner quelque chose d'autre est un abus du protocole par l'OMI, et donc la différence entre Py2 et Py3:

Dans Py2, A.foo est transformé en une méthode non liée et requiert donc que son premier argument soit une instance de la classe dans laquelle elle "vit". Son appel avec autre chose échouera.

Dans Py3, cette vérification a été supprimée et A.foo est simplement l'objet de fonction d'origine. Donc, vous pouvez l'appeler avec tout comme premier argument, mais je ne le ferais pas. Le premier paramètre d'une méthode doit toujours s'appeler self et avoir la sémantique de self.

14
glglgl

La meilleure réponse dépend de la manière dont la fonction sera utilisée. Dans mon cas, j’écris des packages d’application qui seront utilisés dans les cahiers Jupyter. Mon objectif principal est de rendre les choses faciles pour l'utilisateur. 

Le principal avantage des définitions de fonction est que l'utilisateur peut importer son fichier de définition à l'aide du mot clé "as". Cela permet à l’utilisateur d’appeler les fonctions de la même manière qu’il appellerait une fonction numpy ou matplotlib. 

L'un des inconvénients de Python est que les noms ne peuvent pas être protégés contre d'autres assignations. Cependant, si "import numpy as np" apparaît en haut du cahier, c'est un indice fort que "np" ne doit pas être utilisé comme nom de variable commune. Vous pouvez évidemment accomplir la même chose avec les noms de classe, mais la familiarité des utilisateurs compte beaucoup. 

Dans les packages, cependant, je préfère utiliser des méthodes statiques. Mon architecture logicielle est orientée objet et j'écris avec Eclipse, que j'utilise pour plusieurs langues cibles. Il est pratique d'ouvrir le fichier source et de voir la définition de classe au premier niveau, les définitions de méthodes en retrait d'un niveau, etc. Le public visé par le code à ce niveau est principalement constitué d’autres analystes et développeurs. Il est donc préférable d’éviter les idiomes spécifiques au langage.

Je n'ai pas beaucoup de confiance dans la gestion de l'espace de noms Python, en particulier lorsque vous utilisez des modèles de conception dans lesquels un objet passe une référence à lui-même afin que l'objet appelé puisse appeler une méthode définie sur l'appelant. Alors j'essaie de ne pas le forcer trop loin. J'utilise beaucoup de noms entièrement qualifiés et de variables d'instance explicites (avec self ) où, dans d'autres langages, je pouvais compter sur l'interpréteur ou le compilateur pour gérer plus étroitement l'étendue. C'est plus facile à faire avec des classes et des méthodes statiques, c'est pourquoi je pense qu'elles sont le meilleur choix pour les paquets complexes où l'abstraction et la dissimulation d'informations sont les plus utiles. 

0
user5920660