J'ai joué avec Python récemment, et une chose que je trouve un peu étrange est l'utilisation extensive de "méthodes magiques", par exemple pour rendre sa longueur disponible, un objet implémente un , def __len__(self)
, puis elle est appelée lorsque vous écrivez len(obj)
.
Je me demandais simplement pourquoi les objets ne définissent pas simplement une méthode len(self)
et la font appeler directement en tant que membre de l'objet, par exemple obj.len()
? Je suis sûr qu'il doit y avoir de bonnes raisons pour Python le faire comme il le fait, mais en tant que débutant, je n'ai pas encore déterminé ce qu'ils sont.
AFAIK, len
est spécial à cet égard et a des racines historiques.
Voici une citation de la FAQ :
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 ont été 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éthodes (par exemple, des tuples). Il est également pratique d'avoir une fonction qui peut être facilement appliquée à une collection d'objets amorphe lorsque vous utilisez les fonctionnalités 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 chicaner sur des cas individuels, mais cela fait partie de Python, et il est trop tard pour apporter de tels changements fondamentaux maintenant. Les fonctions doivent rester pour éviter une rupture massive de code.
Les autres "méthodes magiques" (en fait appelées méthode spéciale dans le Python folklore) ont beaucoup de sens, et des fonctionnalités similaires existent dans d'autres langues. Ils sont principalement utilisés pour le code qui est appelé implicitement lorsqu'une syntaxe spéciale est utilisée.
Par exemple:
etc...
Du Zen de Python:
Face à l'ambiguïté, refusez la tentation de deviner.
Il devrait y avoir une - et de préférence une seule - manière évidente de le faire.
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.
Toutes les opérations courantes pour de nombreux types d'objets sont placées dans des méthodes magiques, comme __nonzero__
, __len__
ou __repr__
. Cependant, ils sont principalement facultatifs.
La surcharge de l'opérateur se fait également avec des méthodes magiques (par exemple __le__
), il est donc logique de les utiliser également pour d'autres opérations courantes.
Python utilise les "méthodes magiques" Word , car ces méthodes effectuent vraiment de la magie pour votre programme. L'un des plus grands avantages de l'utilisation des méthodes magiques de Python est qu'elles fournissent un moyen simple de faire en sorte que les objets se comportent comme des types intégrés. Cela signifie que vous pouvez éviter des méthodes moches, contre-intuitives et non standard pour exécuter des opérateurs de base.
Prenons l'exemple suivant:
dict1 = {1 : "ABC"}
dict2 = {2 : "EFG"}
dict1 + dict2
Traceback (most recent call last):
File "python", line 1, in <module>
TypeError: unsupported operand type(s) for +: 'dict' and 'dict'
Cela donne une erreur, car le type de dictionnaire ne prend pas en charge l'ajout. Maintenant, étendons la classe du dictionnaire et ajoutons "__ add __" méthode magique:
class AddableDict(dict):
def __add__(self, otherObj):
self.update(otherObj)
return AddableDict(self)
dict1 = AddableDict({1 : "ABC"})
dict2 = AddableDict({2 : "EFG"})
print (dict1 + dict2)
Maintenant, il donne la sortie suivante.
{1: 'ABC', 2: 'EFG'}
Ainsi, en ajoutant cette méthode, la magie s'est soudainement produite et l'erreur que vous obteniez plus tôt a disparu.
J'espère que cela vous clarifie les choses. Pour plus d'informations, reportez-vous à:
n guide des méthodes magiques de Python (Rafe Kettler, 2012)
Certaines de ces fonctions font plus qu'une seule méthode serait capable d'implémenter (sans méthodes abstraites sur une superclasse). Par exemple, bool()
agit un peu comme ceci:
def bool(obj):
if hasattr(obj, '__nonzero__'):
return bool(obj.__nonzero__())
Elif hasattr(obj, '__len__'):
if obj.__len__():
return True
else:
return False
return True
Vous pouvez également être sûr à 100% que bool()
renverra toujours True ou False; si vous vous appuyiez sur une méthode, vous ne pouviez pas être entièrement sûr de ce que vous obtiendriez.
Certaines autres fonctions qui ont des implémentations relativement compliquées (plus compliquées que les méthodes magiques sous-jacentes sont susceptibles d'être) sont iter()
et cmp()
, et toutes les méthodes d'attribut (getattr
, setattr
et delattr
). Des choses comme int
accèdent également aux méthodes magiques lors de la coercition (vous pouvez implémenter __int__
), Mais faites le double devoir en tant que types. len(obj)
est en fait le seul cas où je ne pense pas que ce soit jamais différent de obj.__len__()
.
Ce ne sont pas vraiment des "noms magiques". C'est juste l'interface qu'un objet doit implémenter pour fournir un service donné. En ce sens, elles ne sont pas plus magiques que n'importe quelle définition d'interface prédéfinie que vous devez réimplémenter.
Bien que la raison soit principalement historique, il existe certaines particularités dans len
de Python qui rendent l'utilisation d'une fonction au lieu d'une méthode appropriée.
Certaines opérations dans Python sont implémentées en tant que méthodes, par exemple list.index
Et dict.append
, Tandis que d'autres sont implémentées en tant que callables et méthodes magiques, par exemple str
et iter
et reversed
. Les deux groupes diffèrent suffisamment pour que l'approche différente soit justifiée:
str
, int
et amis sont des types. Il est plus logique d'appeler le constructeur.iter
peut appeler __getitem__
Si __iter__
N'est pas disponible et prend en charge des arguments supplémentaires qui ne tiennent pas dans un appel de méthode. Pour la même raison, it.next()
a été remplacé par next(it)
dans les versions récentes de Python - cela a plus de sens.__iter__
Et __next__
- cela s'appelle la boucle for
. Par souci de cohérence, une fonction est meilleure. Et cela le rend meilleur pour certaines optimisations.repr
agit comme str
. Avoir str(x)
contre x.repr()
serait déroutant.isinstance
.getattr(x, 'a')
est une autre façon de faire x.a
Et getattr
partage bon nombre des qualités susmentionnées.J'appelle personnellement le premier groupe comme une méthode et le second comme un opérateur. Ce n'est pas une très bonne distinction, mais j'espère que cela aide d'une manière ou d'une autre.
Cela dit, len
ne correspond pas exactement au deuxième groupe. C'est plus proche des opérations dans la première, à la seule différence que c'est beaucoup plus courant que presque n'importe lequel d'entre eux. Mais la seule chose qu'il fait est d'appeler __len__
, Et il est très proche de L.index
. Cependant, il existe quelques différences. Par exemple, __len__
Pourrait être appelé pour l'implémentation d'autres fonctionnalités, telles que bool
, si la méthode était appelée len
, vous pourriez casser bool(x)
avec méthode personnalisée len
qui fait une chose complètement différente.
En bref, vous avez un ensemble de fonctionnalités très communes que les classes peuvent implémenter et accessibles via un opérateur, via une fonction spéciale (qui fait généralement plus que l'implémentation, comme le ferait un opérateur), pendant la construction de l'objet, et toutes partagent certains traits communs. Tout le reste est une méthode. Et len
est en quelque sorte une exception à cette règle.
Il n'y a pas grand-chose à ajouter aux deux articles ci-dessus, mais toutes les fonctions "magiques" ne sont pas vraiment magiques. Ils font partie du module __ builtins__ qui est implicitement/automatiquement importé au démarrage de l'interpréteur. C'est à dire.:
from __builtins__ import *
se produit à chaque fois avant le début de votre programme.
J'ai toujours pensé que ce serait plus correct si Python ne faisait cela que pour le shell interactif, et nécessitait des scripts pour importer les différentes parties à partir des commandes dont ils avaient besoin. shells vs interactive. Quoi qu'il en soit, consultez toutes les fonctions et voyez ce que c'est sans elles:
dir (__builtins__)
...
del __builtins__