85 votes

Fonction de module vs méthode statique vs méthode de classe vs pas de décorateur : Quel idiome est le plus pythonique ?

Je suis un développeur Java qui a joué avec Python par intermittence. Je suis récemment tombé sur cet article qui mentionne les erreurs courantes commises par les programmeurs Java lorsqu'ils adoptent Python. La première a attiré mon attention :

Une méthode statique en Java ne se traduit pas par une méthode de classe en Python. Bien sûr, il en résulte plus ou moins le même effet, mais le but d'une classmethod est en fait de faire quelque chose qui n'est généralement même pas possible en Java (comme hériter d'un constructeur qui n'est pas 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 classmethod ou staticmethod. (Et les champs statiques finaux devraient se traduire par des constantes au niveau du module).

Ce n'est pas vraiment un problème de performance, mais un programmeur Python qui doit travailler avec du code Java-idiom comme celui-ci sera plutôt irrité par le fait de taper Foo.Foo.someMethod alors qu'il devrait simplement s'agir de Foo.someFunction. Mais notez que l'appel à une classmethod implique une allocation de mémoire supplémentaire que l'appel à une staticmethod ou à une function n'implique pas.

Oh, et toutes ces chaînes d'attributs Foo.Bar.Baz ne sont pas gratuites non plus. En Java, ces noms en pointillés sont recherchés par le compilateur, de sorte qu'au moment de l'exécution, leur nombre n'a pas vraiment d'importance. En Python, les recherches se font au moment de l'exécution, donc chaque point compte. (Rappelez-vous qu'en Python, "Flat is better than nested", bien que cela soit plus lié à "Readability counts" et "Simple is better than complex" qu'à la performance).

J'ai trouvé cela un peu étrange car la documentation de méthode statique dice:

Les méthodes statiques en Python sont similaires à celles que l'on trouve en Java ou en C++. Voir également classmethod() pour une variante utile pour créer des constructeurs de classe alternatifs.

Ce qui est encore plus surprenant, c'est que ce code :

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

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

Il y a donc trois façons d'implémenter les méthodes statiques (quatre si l'on compte l'utilisation de classmethod), chacune avec des différences subtiles, l'une d'entre elles n'étant apparemment pas documentée. Cela semble en contradiction avec le mantra de Python, à savoir Il devrait y avoir une - et de préférence une seule - façon évidente de le faire. Quel est l'idiome 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 noms du module plus que les autres solutions.
  • Pas d'héritage

méthode statique :

  • Maintient les fonctions liées à la classe à l'intérieur de la classe et en dehors de l'espace de noms du module.
  • Permet d'appeler la fonction sur des instances de la classe.
  • Les sous-classes peuvent surcharger la méthode.

méthode de classe :

  • Identique à staticmethod, mais transmet également la classe comme premier argument.

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

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

Est-ce que je réfléchis trop ? S'agit-il d'un problème sans importance ? Merci de m'aider !

114voto

BrenBarn Points 63718

La façon la plus simple de l'envisager est de penser au type d'objet dont la méthode a besoin pour effectuer son travail. Si votre méthode doit accéder à une instance, faites-en une méthode normale. Si elle a besoin d'accéder à la classe, il s'agit d'une méthode de classe. Si elle n'a pas besoin d'accéder à la classe ou à l'instance, il s'agit d'une fonction. Il est rarement nécessaire de faire de quelque chose une méthode statique, mais si vous souhaitez qu'une fonction soit "groupée" avec une classe (par exemple, pour qu'elle puisse être surchargée) même si elle n'a pas besoin d'accéder à la classe, je suppose que vous pouvez en faire une méthode statique.

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

39voto

smci Points 2818

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

S'il n'a pas besoin d'accéder à la classe ou à l'instance... mais es thématiquement liés à la classe (exemple typique : fonctions d'aide et de conversion utilisées par d'autres méthodes de la classe ou utilisées par des constructeurs alternatifs), puis utilisez méthode statique

sinon, il s'agit d'un fonction du module

16voto

glglgl Points 35668

Il ne s'agit pas vraiment d'une réponse, mais plutôt d'un long commentaire :

Ce qui est encore plus surprenant, c'est que ce code :

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

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

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

Il s'agit, à proprement parler, d'un abus du protocole "normal" de la méthode d'instance.

Ce que vous définissez ici est une méthode, mais dont le premier (et seul) paramètre n'est pas nommé self mais x . Bien entendu, vous pouvez appeler la méthode dans une instance de A mais vous devrez l'appeler ainsi :

A().foo()

ou

a = A()
a.foo()

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

La possibilité d'appeler des méthodes normales via la classe a toujours existé et fonctionne par

a = A()
A.foo(a)

Ici, comme vous appelez la méthode de la classe plutôt que celle de l'instance, son premier paramètre n'est pas donné automatiquement, mais vous devez le fournir.

Tant qu'il s'agit d'une instance de A Tout va bien. Lui donner autre chose est un abus du protocole, et c'est ce qui fait la différence entre Py2 et Py3 :

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

Dans Py3, cette vérification a été abandonnée et A.foo n'est que l'objet de la fonction d'origine. Vous pouvez donc l'appeler avec tout comme premier argument, mais je ne le ferais pas. Le premier paramètre d'une méthode doit toujours être nommé self et ont la sémantique de self .

0voto

user5920660 Points 101

La meilleure réponse dépend de l'utilisation qui sera faite de la fonction. Dans mon cas, j'écris des paquets d'applications qui seront utilisés dans les carnets Jupyter. Mon principal objectif est de faciliter la tâche de l'utilisateur.

Le principal avantage des définitions de fonctions est que l'utilisateur peut importer leur 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 dans numpy ou matplotlib.

L'un des inconvénients de Python est que les noms ne peuvent pas être protégés contre une affectation ultérieure. Cependant, si "import numpy as np" apparaît en haut du carnet, c'est un indice fort que "np" ne doit pas être utilisé comme nom de variable commun. On peut évidemment faire la même chose avec des noms de classe, mais la familiarité de l'utilisateur compte beaucoup.

À l'intérieur des paquets, 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 la classe au niveau supérieur, les définitions des méthodes indentées d'un niveau, etc. Les destinataires du code à ce niveau sont principalement d'autres analystes et développeurs, il est donc préférable d'éviter les idiomes spécifiques à la langue.

Je n'ai pas une grande confiance dans la gestion de l'espace de noms de Python, en particulier lors de l'utilisation de modèles de conception où (disons) 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. J'essaie donc de ne pas aller trop loin. J'utilise beaucoup de noms pleinement qualifiés et de variables d'instance explicites (avec soi ) alors que dans d'autres langues, je pouvais compter sur l'interprète ou le compilateur pour gérer plus étroitement le champ d'application. Il est plus facile de le faire avec les classes et les 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.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X