Pourquoi le code Python utilise-t-il la fonction len () au lieu d'une méthode de longueur?

206

Je sais que python a une len()fonction qui est utilisée pour déterminer la taille d'une chaîne, mais je me demandais pourquoi ce n'était pas une méthode de l'objet chaîne.

Mettre à jour

Ok, j'ai réalisé que je me trompais d'une manière embarrassante. __len__()est en fait une méthode d'un objet chaîne. Il semble juste étrange de voir du code orienté objet en Python en utilisant la fonction len sur les objets chaîne. En outre, il est également étrange de voir __len__le nom au lieu de simplement len.

fuentesjr
la source
Lié à votre mise à jour: Y at - il tous les cas où len(someObj)ne remet pas someObjde » __len__fonction? tl; dr Oui, utilisez donc toujours len()au lieu de __len__().
wjandrea Il y a

Réponses:

182

Les chaînes ont une méthode de longueur: __len__()

Le protocole en Python consiste à implémenter cette méthode sur des objets qui ont une longueur et utilisent la len()fonction intégrée, qui l'appelle pour vous, de la même manière que vous implémenteriez __iter__()et utiliseriez la iter()fonction intégrée (ou que la méthode soit appelée derrière les scènes pour vous) sur des objets qui sont itérables.

Voir Émulation des types de conteneurs pour plus d'informations.

Voici une bonne lecture sur le sujet des protocoles en Python: Python et le principe du moindre étonnement

Jonny Buchanan
la source
124
Cela m'étonne à quel point la raison de l'utilisation lenest idiote . Ils pensent qu'il est plus facile de forcer les gens à mettre en œuvre .__len__que de forcer les gens à mettre en œuvre .len(). C'est la même chose, et on a l'air beaucoup plus propre. Si la langue va avoir un POO __len__, quel est le but de faire dans le mondelen(..)
alternative
40
len, str, etc. peuvent être utilisés avec des fonctions d'ordre supérieur comme map, réduire et filtrer sans avoir besoin de définir une fonction ou lambda juste pour appeler une méthode. Tout ne tourne pas autour de la POO, même en Python.
Evicatos
12
De plus, en utilisant un protocole, ils peuvent fournir des moyens alternatifs de mise en œuvre. Par exemple, vous pouvez créer un itérable avec __iter__ou avec seulement __getitem__et iter(x)fonctionnera dans les deux cas. Vous pouvez créer un objet utilisable dans un contexte booléen avec __bool__ou __len__, et bool(x)cela fonctionnera dans les deux cas. Etc. Je pense qu'Armin explique cela assez bien dans le post lié - mais même s'il ne l'a pas fait, appeler Python idiot à cause d'une explication extérieure par un gars qui est souvent publiquement en désaccord avec les développeurs principaux ne serait pas exactement juste ...
abarnert
8
@Evicatos: +1 pour "Tout ne tourne pas autour de la POO", mais je terminerais par " surtout en Python", même pas . Python (contrairement à Java, Ruby ou Smalltalk) n'essaye pas d'être un langage "pur OOP"; il est explicitement conçu pour être un langage "multi-paradigme". Les compréhensions de liste ne sont pas des méthodes sur les itérables. zipest une fonction de niveau supérieur, pas une zip_withméthode. Etc. Le fait que tout soit un objet ne signifie pas qu'être un objet est la chose la plus importante de chaque chose.
abarnert
7
Cet article est si mal écrit que je me sens confus et en colère après avoir essayé de le lire. "Dans Python 2.x, le type Tuple, par exemple, n'expose pas de méthodes non spéciales et pourtant vous pouvez l'utiliser pour en faire une chaîne:" Le tout est une fusion de phrases presque indéchiffrables.
MirroredFate
94

La réponse de Jim à cette question peut aider; Je le copie ici. Citant Guido van Rossum:

Tout d'abord, j'ai choisi len (x) plutôt que x.len () pour des raisons HCI (def __len __ () est venu beaucoup plus tard). Il y a en fait deux raisons étroitement liées, les deux HCI:

(a) Pour certaines opérations, la notation des préfixes se lit mieux que postfixe - 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) en x a + x b à la maladresse de faire la même chose en utilisant une notation OO brute.

(b) Quand je lis du code qui dit 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 lis x.len (), je dois déjà savoir que x est une sorte de conteneur implémentant une interface ou héritant d'une classe qui a un len () standard. Soyez témoin de la confusion que nous avons parfois lorsqu'une classe qui n'implémente pas de mappage a une méthode get () ou keys (), ou quelque chose qui n'est pas un fichier a une méthode write ().

Dire la même chose d'une autre manière, je vois «len» comme une opération intégrée. Je détesterais perdre ça. /… /

Federico A. Ramponi
la source
36

Il y a un len méthode:

>>> a = 'a string of some length'
>>> a.__len__()
23
>>> a.__len__
<method-wrapper '__len__' of str object at 0x02005650>
démonté
la source
Oui, mais il n'est pas destiné à être utilisé directement. lenvalide la sortie de __len__. Voir Y alen(someObj)someObj__len__ - t-il un cas où n'appelle pas la fonction de? Les méthodes Dunder en général ne sont pas censées être appelées directement.
wjandrea Il y a
35

Python est un langage de programmation pragmatique, et les raisons len()étant une fonction et non une méthode de str, list,dict etc. sont pragmatiques.

La len()fonction intégrée traite directement les types intégrés: l'implémentation CPython de len()retourne en fait la valeur du ob_sizechamp dans la PyVarObjectstructure C qui représente tout objet intégré de taille variable en mémoire. C'est beaucoup plus rapide que d'appeler une méthode - aucune recherche d'attribut ne doit se produire. Obtenir le nombre d'éléments dans une collection est une opération courante et doit travailler efficacement pour ces types de base et aussi divers que str, list, array.arrayetc.

Cependant, pour favoriser la cohérence, lors de l'application len(o)à un type défini par l'utilisateur, Python appelle o.__len__()comme solution de secours. __len__, __abs__Et toutes les autres méthodes spéciales documentées dans le modèle de données Python , il est facile de créer des objets qui se comportent comme les Encastrements, permettant aux API expressives et très cohérentes que nous appelons « Pythonic ».

En mettant en œuvre des méthodes spéciales, vos objets peuvent prendre en charge l'itération, surcharger les opérateurs d'infixe, gérer les contextes dans des withblocs, etc. Vous pouvez considérer le modèle de données comme un moyen d'utiliser le langage Python lui-même comme un cadre où les objets que vous créez peuvent être intégrés de manière transparente.

Une deuxième raison, appuyée par des citations de Guido van Rossum comme celle-ci , est qu'il est plus facile à lire et à écrire len(s)que s.len().

La notation len(s)est cohérente avec les opérateurs unaires avec la notation de préfixe, comme abs(n). len()est utilisé beaucoup plus souvent abs()et mérite d'être aussi facile à écrire.

Il peut également y avoir une raison historique: dans le langage ABC qui a précédé Python (et était très influent dans sa conception), il y avait un opérateur unaire écrit comme #sce qui signifiait len(s).

Luciano Ramalho
la source
11
met% python -c 'import this' | grep 'only one'
There should be one-- and preferably only one --obvious way to do it.
Alex Coventry
la source
35
La chose évidente lorsque l'on travaille avec un objet étant, bien sûr, une méthode.
Peter Cooper
4
@Peter: Je paierais 20 $ à quiconque possédant une preuve photo qu'ils ont enregistré votre commentaire sur le dos de Guido. 50 $ si c'est sur son front.
bug
1
Oui, les concepteurs de Python adhèrent au dogme mais ils ne respectent pas eux-mêmes leur propre dogme.
bitek
2
$ python -c 'importer ceci' | grep évident
Ed Randall
3

Il y a d'excellentes réponses ici, et donc avant de donner la mienne, je voudrais souligner quelques-unes des gemmes (sans jeu de mots rubis) que j'ai lues ici.

  • Python n'est pas un langage OOP pur - c'est un langage multi-paradigme à usage général qui permet au programmeur d'utiliser le paradigme avec lequel il est le plus à l'aise et / ou le paradigme le mieux adapté à sa solution.
  • Python a des fonctions de première classe, tout lencomme un objet. Ruby, d'autre part, n'a pas de fonctions de première classe. Ainsi, l' lenobjet fonction possède ses propres méthodes que vous pouvez inspecter en exécutant dir(len).

Si vous n'aimez pas la façon dont cela fonctionne dans votre propre code, il est trivial pour vous de réimplémenter les conteneurs en utilisant votre méthode préférée (voir l'exemple ci-dessous).

>>> class List(list):
...     def len(self):
...         return len(self)
...
>>> class Dict(dict):
...     def len(self):
...         return len(self)
...
>>> class Tuple(tuple):
...     def len(self):
...         return len(self)
...
>>> class Set(set):
...     def len(self):
...         return len(self)
...
>>> my_list = List([1,2,3,4,5,6,7,8,9,'A','B','C','D','E','F'])
>>> my_dict = Dict({'key': 'value', 'site': 'stackoverflow'})
>>> my_set = Set({1,2,3,4,5,6,7,8,9,'A','B','C','D','E','F'})
>>> my_tuple = Tuple((1,2,3,4,5,6,7,8,9,'A','B','C','D','E','F'))
>>> my_containers = Tuple((my_list, my_dict, my_set, my_tuple))
>>>
>>> for container in my_containers:
...     print container.len()
...
15
2
15
15
Charles Addis
la source
2

Quelque chose manque dans le reste des réponses ici: la lenfonction vérifie que la __len__méthode renvoie un résultat non négatif int. Le fait qu'il lens'agisse d'une fonction signifie que les classes ne peuvent pas remplacer ce comportement pour éviter la vérification. En tant que tel, len(obj)donne un niveau de sécurité qui obj.len()ne peut pas.

Exemple:

>>> class A:
...     def __len__(self):
...         return 'foo'
...
>>> len(A())
Traceback (most recent call last):
  File "<pyshell#8>", line 1, in <module>
    len(A())
TypeError: 'str' object cannot be interpreted as an integer
>>> class B:
...     def __len__(self):
...         return -1
... 
>>> len(B())
Traceback (most recent call last):
  File "<pyshell#13>", line 1, in <module>
    len(B())
ValueError: __len__() should return >= 0

Bien sûr, il est possible de "remplacer" la lenfonction en la réaffectant en tant que variable globale, mais le code qui le fait est beaucoup plus suspect que le code qui remplace une méthode dans une classe.

kaya3
la source
Il vérifie également que l'int n'est pas supérieur à sys.maxsize, et qu'il obj.__len__existe en premier lieu. J'ai écrit une réponse à une autre question qui répertorie toutes les différentes vérifications.
wjandrea Il y a
-1

Vous pouvez également dire

>> x = 'test'
>> len(x)
4

Utilisation de Python 2.7.3.

SpanosAngelos
la source
9
lenétait déjà mentionnée dans les réponses précédentes. Quel est l'intérêt de cette "réponse", alors?
Piotr Dobrogost