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?
la source
X.frob
ouX.__frob__
et que vous soyez autonomefrob
.Réponses:
Ma règle générale est la suivante: l'opération est-elle effectuée sur l'objet ou par l'objet?
si cela est fait par l'objet, il doit s'agir d'une opération membre. Si cela peut s'appliquer à d'autres choses aussi, ou est fait par autre chose à l'objet, alors ce devrait être une fonction (ou peut-être un membre de quelque chose d'autre).
Lors de l'introduction de la programmation, il est traditionnel (bien que la mise en œuvre soit incorrecte) de décrire des objets en termes d'objets du monde réel tels que des voitures. Vous parlez d'un canard, alors allons-y.
Dans le contexte de l'analogie «les objets sont des choses réelles», il est «correct» d'ajouter une méthode de classe pour tout ce que l'objet peut faire. Alors disons que je veux tuer un canard, dois-je ajouter un .kill () au canard? Non ... pour autant que je sache, les animaux ne se suicident pas. Par conséquent, si je veux tuer un canard, je devrais faire ceci:
S'éloignant de cette analogie, pourquoi utilisons-nous des méthodes et des classes? Parce que nous voulons contenir des données et, espérons-le, structurer notre code de manière à ce qu'il soit réutilisable et extensible à l'avenir. Ceci nous amène à la notion d'encapsulation si chère au design OO.
Le principe d'encapsulation est vraiment ce à quoi cela se résume: en tant que concepteur, vous devez tout cacher sur l'implémentation et les composants internes de la classe auxquels aucun utilisateur ou autre développeur n'a nécessairement accès. Comme nous traitons des instances de classes, cela se réduit à «quelles opérations sont cruciales sur cette instance ». Si une opération n'est pas spécifique à une instance, elle ne doit pas être une fonction membre.
TL; DR : ce que @Bryan a dit. S'il fonctionne sur une instance et a besoin d'accéder à des données internes à l'instance de classe, il doit s'agir d'une fonction membre.
la source
Utilisez une classe lorsque vous souhaitez:
1) Isolez le code d'appel des détails d'implémentation - en tirant parti de l' abstraction et de l' encapsulation .
2) Lorsque vous voulez être substituable à d'autres objets - en tirant parti du polymorphisme .
3) Lorsque vous souhaitez réutiliser du code pour des objets similaires - en tirant parti de l' héritage .
Utilisez une fonction pour les appels qui ont un sens sur de nombreux types d'objets différents - par exemple, les fonctions intégrées len et repr s'appliquent à de nombreux types d'objets.
Cela étant dit, le choix se résume parfois à une question de goût. Pensez à ce qui est le plus pratique et le plus lisible pour les appels classiques. Par exemple, quel serait le meilleur
(x.sin()**2 + y.cos()**2).sqrt()
ousqrt(sin(x)**2 + cos(y)**2)
?la source
Voici une règle empirique simple: si le code agit sur une seule instance d'un objet, utilisez une méthode. Mieux encore: utilisez une méthode à moins qu'il n'y ait une raison impérieuse de l'écrire en tant que fonction.
Dans votre exemple spécifique, vous voulez que cela ressemble à ceci:
N'y pensez pas trop. Utilisez toujours des méthodes jusqu'à ce que vous vous disiez "cela n'a pas de sens d'en faire une méthode", auquel cas vous pouvez créer une fonction.
la source
len()
on peut également affirmer que la conception a du sens, même si je pense personnellement que les fonctions ne seraient pas trop mauvaises là-bas non plus - nous aurions juste une autre convention quilen()
doit toujours renvoyer un entier (mais avec tous les problèmes supplémentaires de backcomp, je ne recommanderais pas cela non plus pour python)Je pense généralement à un objet comme à une personne.
Les attributs sont le nom de la personne, sa taille, sa pointure, etc.
Les méthodes et les fonctions sont des opérations que la personne peut effectuer.
Si l'opération peut être effectuée par n'importe quelle vieille personne, sans exiger quoi que ce soit d'unique à cette personne spécifique (et sans rien changer à cette personne spécifique), alors c'est une fonction et doit être écrite comme telle.
Si une opération agit sur la personne (par exemple manger, marcher, ...) ou nécessite quelque chose d'unique à cette personne pour s'impliquer (comme danser, écrire un livre, ...), alors cela devrait être une méthode .
Bien sûr, il n'est pas toujours trivial de traduire cela dans l'objet spécifique avec lequel vous travaillez, mais je trouve que c'est une bonne façon d'y penser.
la source
height(person)
, nonperson.height
?are_married(person1, person2)
? Cette requête est très générale et doit donc être une fonction et non une méthode.En général, j'utilise des classes pour implémenter un ensemble logique de capacités pour quelque chose , de sorte que dans le reste de mon programme, je puisse raisonner sur la chose , sans avoir à me soucier de tous les petits soucis qui composent son implémentation.
Tout ce qui fait partie de cette abstraction fondamentale de «ce que vous pouvez faire avec une chose » devrait généralement être une méthode. Cela inclut généralement tout ce qui peut modifier une chose , car l'état des données internes est généralement considéré comme privé et ne fait pas partie de l'idée logique de "ce que vous pouvez faire avec une chose ".
Quand vous arrivez à des opérations de niveau supérieur, surtout si elles impliquent plusieurs choses , je trouve qu'elles sont généralement exprimées le plus naturellement sous forme de fonctions, si elles peuvent être construites à partir de l'abstraction publique d'une chose sans avoir besoin d'un accès spécial aux éléments internes (à moins qu'ils '' re méthodes d'un autre objet). Cela a le gros avantage que lorsque je décide de réécrire complètement les éléments internes du fonctionnement de mon truc (sans changer l'interface), je n'ai qu'un petit ensemble de méthodes de base à réécrire, puis toutes les fonctions externes écrites en fonction de ces méthodes fonctionnera simplement. Je trouve qu'insister sur le fait que toutes les opérations à faire avec la classe X sont des méthodes sur la classe X conduit à des classes trop compliquées.
Cela dépend du code que j'écris. Pour certains programmes, je les modélise comme une collection d'objets dont les interactions donnent lieu au comportement du programme; ici, la fonctionnalité la plus importante est étroitement liée à un seul objet, et est donc implémentée dans les méthodes, avec une dispersion des fonctions d'utilité. Pour d'autres programmes, le truc le plus important est un ensemble de fonctions qui manipulent les données, et les classes ne sont utilisées que pour implémenter les "types de canard" naturels qui sont manipulés par les fonctions.
la source
Vous pouvez dire que, "face à l'ambiguïté, refusez la tentation de deviner".
Cependant, ce n'est même pas une supposition. Vous êtes absolument sûr que les résultats des deux approches sont les mêmes en ce sens qu'ils résolvent votre problème.
Je pense que ce n'est qu'une bonne chose d'avoir plusieurs façons d'atteindre les objectifs. Je vous dirais humblement, comme d'autres utilisateurs l'ont déjà fait, d'employer ce qui «goûte le mieux» / se sent le plus intuitif , en termes de langage.
la source