Différence entre len () et .__ len __ ()?

100

Y a-t-il une différence entre appeler len([1,2,3])ou [1,2,3].__len__()?

S'il n'y a pas de différence visible, que fait-on différemment dans les coulisses?

marque
la source

Réponses:

102

lenest une fonction pour obtenir la longueur d'une collection. Cela fonctionne en appelant la __len__méthode d' un objet .__something__Les attributs sont spéciaux et généralement plus qu'il n'y paraît, et ne doivent généralement pas être appelés directement.

Il a été décidé il y a longtemps que la longueur de quelque chose devait être une fonction et non un code de méthode, le raisonnement que cette len(a)signification serait clair pour les débutants mais a.len()ne serait pas aussi clair. Lorsque Python a commencé, il __len__n'existait même pas et lenétait une chose spéciale qui fonctionnait avec quelques types d'objets. Que la situation que cela nous laisse ait ou non un sens total, elle est là pour rester.

Mike Graham
la source
66

Il arrive souvent que le comportement "typique" d'un opérateur intégré ou d'un opérateur soit d'appeler (avec une syntaxe différente et plus agréable) des méthodes magiques appropriées (celles avec des noms comme __whatever__) sur les objets impliqués. Souvent, l’opérateur intégré ou l’opérateur a une «valeur ajoutée» (il peut emprunter des chemins différents en fonction des objets impliqués) - dans le cas de lenvs __len__, il s’agit juste d’un peu de vérification de la cohérence de l’intégré qui manque dans le méthode magique:

>>> class bah(object):
...   def __len__(self): return "an inch"
... 
>>> bah().__len__()
'an inch'
>>> len(bah())
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: 'str' object cannot be interpreted as an integer

Lorsque vous voyez un appel à la fonction lenintégrée, vous êtes sûr que, si le programme continue après cela plutôt que de lever une exception, l'appel a renvoyé un entier, non négatif et inférieur à 2 ** 31 - lorsque vous voyez un appel àxxx.__len__() , vous n'avez aucune certitude (sauf que l'auteur du code n'est pas familier avec Python ou n'est pas bon ;-).

D'autres éléments intégrés offrent encore plus de valeur ajoutée au-delà des simples contrôles de cohérence et de la lisibilité. En concevant uniformément tout Python pour qu'il fonctionne via des appels à des fonctions intégrées et l'utilisation d'opérateurs, jamais par des appels à des méthodes magiques, les programmeurs sont épargnés du fardeau de se souvenir de quel cas est quel cas. (Parfois une erreur se glisse: jusqu'à 2.5, vous deviez appeler foo.next()- en 2.6, alors que cela fonctionne toujours pour la compatibilité ascendante, vous devriez appeler next(foo), et dans 3.*, la méthode magique est correctement nommée __next__au lieu du "oops-ey" next! - ).

Donc, la règle générale devrait être de ne jamais appeler une méthode magique directement (mais toujours indirectement via un intégré) à moins que vous ne sachiez exactement pourquoi vous devez faire cela (par exemple, lorsque vous surchargez une telle méthode dans une sous-classe, si le la sous-classe doit s'en remettre à la superclasse qui doit être effectuée via un appel explicite à la méthode magique).

Alex Martelli
la source
Je suis un utilisateur débutant de Python (pas le programmeur débutant pensait) et je ne suis pas sûr de "Quand vous voyez un appel au len intégré, vous êtes sûr que si le programme continue après cela plutôt que de lever une exception". J'ai essayé ceci: def len(x): return "I am a string." print(len(42)) print(len([1,2,3]))et il a imprimé I am stringdeux fois. Pouvez-vous l'expliquer davantage?
Darek Nędza
4
@ DarekNędza Cela n'a rien à voir avec ce qui précède, qui concerne le len intégré. Vous venez de définir votre fonction len, qui peut bien sûr renvoyer ce que vous voulez. OP a parlé de len intégré, qui appelle __len__une méthode spéciale (et non une fonction) sur l'objet considéré.
Veky
@Veky Comment puis-je être sûr que j'appelle la fonction intégrée et lennon une autre fonction (comme dans mon exemple) qui porte le même nom - len. Il n'y a pas d'avertissement comme "Vous redéfinissez la fonction intégrée len" ou quelque chose comme ça. À mon avis, je ne peux pas être sûr de ce qu'Alex a déclaré dans sa réponse.
Darek Nędza
3
Alex a explicitement dit que si vous appelez builtin, alors vous êtes sûr ..._. Il n'a rien dit sur le fait que vous appeliez builtin. Mais si vous voulez savoir, vous pouvez: len in vars(__builtins__).values().
Veky
1
Malheureusement, c'est un autre exemple de l'absence d'une classe de base commune pour les objets en Python. Le changement de contexte syntaxique a toujours été dingue. Dans certains cas, il est courant d'utiliser une méthode de soulignement, dans d'autres, on devrait utiliser quelque chose comme une fonction pour faire quelque chose de commun à de nombreux objets. C'est aussi étrange car de nombreux objets n'ont aucune utilité sémantique pour len. Parfois, le modèle d'objet ressemble plus à C ++, cuisine sombre ..
uchuugaka
28

Vous pouvez penser à len () comme étant à peu près équivalent à

def len(x):
    return x.__len__()

Un avantage est qu'il vous permet d'écrire des choses comme

somelist = [[1], [2, 3], [4, 5, 6]]
map(len, somelist) 

au lieu de

map(list.__len__, somelist)

ou

map(operator.methodcaller('__len__'), somelist)

Il y a cependant un comportement légèrement différent. Par exemple dans le cas des ints

>>> (1).__len__()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: 'int' object has no attribute '__len__'
>>> len(1)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: object of type 'int' has no len()
John La Rooy
la source
2
Je suppose que vous voulez dire operator.methodcallerau lieu de operator.attrgetter.
Elazar
5

Vous pouvez consulter les documents Pythond :

>>> class Meta(type):
...    def __getattribute__(*args):
...       print "Metaclass getattribute invoked"
...       return type.__getattribute__(*args)
...
>>> class C(object):
...     __metaclass__ = Meta
...     def __len__(self):
...         return 10
...     def __getattribute__(*args):
...         print "Class getattribute invoked"
...         return object.__getattribute__(*args)
...
>>> c = C()
>>> c.__len__()                 # Explicit lookup via instance
Class getattribute invoked
10
>>> type(c).__len__(c)          # Explicit lookup via type
Metaclass getattribute invoked
10
>>> len(c)                      # Implicit lookup
10
Dmytro Ozarkiv
la source