Le Zen of Python déclare qu'il ne devrait y avoir qu'une seule façon de faire les choses, mais je rencontre souvent le problème de décider quand utiliser une fonction par rapport à quand utiliser une méthode.
Prenons un exemple trivial - un objet ChessBoard. Disons que nous avons besoin d'un moyen d'obtenir tous les mouvements légaux de King disponibles sur le plateau. Écrivons-nous ChessBoard.get_king_moves () ou get_king_moves (chess_board)?
Voici quelques questions connexes que j'ai examinées:
- Pourquoi python utilise-t-il des «méthodes magiques»?
- Y a-t-il une raison pour laquelle les chaînes Python n'ont pas de méthode de longueur de chaîne?
Les réponses que j'ai obtenues n'étaient en grande partie pas concluantes:
Pourquoi Python utilise-t-il des méthodes pour certaines fonctionnalités (par exemple list.index ()) mais des fonctions pour d'autres (par exemple len (list))?
La raison principale est l'histoire. Les fonctions étaient utilisées pour les opérations qui étaient génériques pour un groupe de types et qui étaient destinées à fonctionner même pour des objets qui n'avaient pas du tout de méthode (par exemple des tuples). Il est également pratique d'avoir une fonction qui peut facilement être appliquée à une collection amorphe d'objets lorsque vous utilisez les fonctionnalités fonctionnelles de Python (map (), apply () et al).
En fait, implémenter len (), max (), min () en tant que fonction intégrée est en fait moins de code que de les implémenter en tant que méthodes pour chaque type. On peut ergoter sur des cas individuels, mais cela fait partie de Python, et il est trop tard pour apporter des changements aussi fondamentaux maintenant. Les fonctions doivent rester pour éviter une rupture massive du code.
Bien qu'intéressant, ce qui précède ne dit pas vraiment quelle stratégie adopter.
C'est l'une des raisons - avec des méthodes personnalisées, les développeurs seraient libres de choisir un nom de méthode différent, comme getLength (), length (), getlength () ou autre. Python applique une dénomination stricte afin que la fonction commune len () puisse être utilisée.
Un peu plus intéressant. Je pense que les fonctions sont en un sens la version pythonique des interfaces.
Enfin, de Guido lui - même :
Parler des capacités / interfaces m'a fait penser à certains de nos noms de méthodes spéciales "voyous". Dans le Language Reference, il est dit: "Une classe peut implémenter certaines opérations qui sont appelées par une syntaxe spéciale (comme des opérations arithmétiques ou des indices et des tranches) en définissant des méthodes avec des noms spéciaux." Mais il y a toutes ces méthodes avec des noms spéciaux comme
__len__
ou__unicode__
qui semblent être fournis pour le bénéfice des fonctions intégrées, plutôt que pour le support de la syntaxe. Vraisemblablement dans un Python basé sur une interface, ces méthodes se transformeraient en méthodes nommées régulièrement sur un ABC, ce qui__len__
deviendraitclass container: ... def len(self): raise NotImplemented
Cependant, en y réfléchissant un peu plus, je ne vois pas pourquoi toutes les opérations syntaxiques
<
object.lessthan
comparable.lessthan
n'appelleraient pas simplement la méthode normalement nommée appropriée sur un ABC spécifique. " ", par exemple, invoquerait vraisemblablement " " (ou peut-être " "). Un autre avantage serait donc la possibilité de sevrer Python de cette bizarrerie de nom mutilé, ce qui me semble une amélioration HCI .Hm. Je ne suis pas sûr d'être d'accord (figurez-le :-).
Il y a deux morceaux de «raisonnement Python» que j'aimerais d'abord expliquer.
Tout d'abord, j'ai choisi len (x) plutôt que x.len () pour des raisons HCI (
def __len__()
venu beaucoup plus tard). Il y a en fait deux raisons étroitement liées, les deux HCI:(a) Pour certaines opérations, la notation de préfixe lit mieux que postfix - les opérations de préfixe (et d'infixe!) ont une longue tradition en mathématiques qui aime les notations où les visuels aident le mathématicien à réfléchir à un problème. Comparez la facilité avec laquelle nous réécrivons une formule comme
x*(a+b)
dansx*a + x*b
à la maladresse de faire la même chose en utilisant une notation OO brute.(b) Quand je lis un code qui dit que
len(x)
je sais qu'il demande la longueur de quelque chose. Cela me dit deux choses: le résultat est un entier et l'argument est une sorte de conteneur. Au contraire, quand je lisx.len()
, je dois déjà savoir qu'ilx
s'agit d'une sorte de conteneur implémentant une interface ou héritant d'une classe qui a un standardlen()
. Témoin de la confusion , nous avons de temps en temps quand une classe qui ne sont pas en œuvre une cartographie a uneget()
oukeys()
méthode, ou quelque chose qui est pas un fichier a unewrite()
méthode.En disant la même chose d'une autre manière, je vois «len» comme une opération intégrée . Je détesterais perdre ça. Je ne peux pas dire avec certitude si vous vouliez dire cela ou non, mais 'def len (self): ...' semble certainement que vous vouliez le rétrograder à une méthode ordinaire. Je suis fortement -1 sur ce point.
La deuxième partie de la logique Python que j'ai promis d'expliquer est la raison pour laquelle j'ai choisi des méthodes spéciales pour regarder
__special__
et pas simplementspecial
. J'anticipais beaucoup d'opérations que les classes pourraient vouloir remplacer, certaines standards (par exemple__add__
ou__getitem__
), d'autres pas si standard (par exemple pickle__reduce__
pendant longtemps n'avait aucun support en code C). Je ne voulais pas que ces opérations spéciales utilisent des noms de méthodes ordinaires, car alors les classes préexistantes, ou les classes écrites par des utilisateurs sans mémoire encyclopédique pour toutes les méthodes spéciales, seraient susceptibles de définir accidentellement des opérations qu'elles ne voulaient pas implémenter , avec des conséquences potentiellement désastreuses. Ivan Krstić a expliqué cela de manière plus concise dans son message, qui est arrivé après avoir écrit tout cela.- --Guido van Rossum (page d'accueil: http://www.python.org/~guido/ )
Ma compréhension de ceci est que dans certains cas, la notation de préfixe a juste plus de sens (c'est-à-dire, Duck.quack a plus de sens que charlatan (Duck) d'un point de vue linguistique.) Et encore une fois, les fonctions permettent des "interfaces".
Dans un tel cas, je suppose que je devrais implémenter get_king_moves basé uniquement sur le premier point de Guido. Mais cela laisse encore beaucoup de questions ouvertes concernant, par exemple, l'implémentation d'une classe de pile et de file d'attente avec des méthodes push et pop similaires - devraient-elles être des fonctions ou des méthodes? (ici je devinerais des fonctions, car je veux vraiment signaler une interface push-pop)
TLDR: Quelqu'un peut-il expliquer quelle devrait être la stratégie pour décider quand utiliser les fonctions par rapport aux méthodes?
X.frob
ouX.__frob__
et que vous soyez autonomefrob
.