¿Por qué el código Python usa la función len () en lugar de un método de longitud?


204

Sé que Python tiene una len()función que se utiliza para determinar el tamaño de una cadena, pero me preguntaba por qué no es un método del objeto de cadena.

Actualizar

Ok, me di cuenta de que estaba vergonzosamente equivocado. __len__()es en realidad un método de un objeto de cadena. Simplemente parece extraño ver código orientado a objetos en Python usando la función len en objetos de cadena. Además, también es raro verlo __len__como el nombre en lugar de solo len.

Respuestas:


180

Las cadenas tienen un método de longitud: __len__()

El protocolo en Python es implementar este método en objetos que tienen una longitud y usan la len()función incorporada, que lo llama para usted, de manera similar a la forma en que implementaría __iter__()y usaría la iter()función incorporada (o haga que el método se invoque detrás las escenas para ti) en objetos que son iterables.

Consulte Emulación de tipos de contenedor para obtener más información.

Aquí hay una buena lectura sobre el tema de los protocolos en Python: Python y el principio del menor asombro


124
Me sorprende lo tonta que es la razón para usar len. Piensan que es más fácil obligar a las personas a implementar .__len__que obligar a las personas a implementar .len(). Es lo mismo, y uno se ve mucho más limpio. Si el idioma va a tener un OOP __len__, ¿cuál es el objetivo del mundolen(..)
Alternativa

38
len, str, etc. pueden usarse con funciones de orden superior como mapear, reducir y filtrar sin la necesidad de definir una función o lambda solo para llamar a un método. No todo gira en torno a OOP, incluso en Python.
Evicatos

11
Además, mediante el uso de un protocolo, pueden proporcionar formas alternativas de implementar cosas. Por ejemplo, puede crear un iterable con __iter__, o con solo __getitem__, y iter(x)funcionará de cualquier manera. Puede crear un objeto de contexto utilizable en bool con __bool__o __len__, y bool(x)funcionará de cualquier manera. Y así. Creo que Armin explica esto razonablemente bien en la publicación vinculada, pero incluso si no lo hizo, llamar a Python imbécil debido a una explicación externa de un tipo que a menudo está públicamente en desacuerdo con los desarrolladores principales no sería exactamente justo ...
abarnert

8
@Evicatos: +1 para "No todo gira en torno a OOP", pero terminaría con " especialmente en Python", ni siquiera . Python (a diferencia de Java, Ruby o Smalltalk) no intenta ser un lenguaje "puro OOP"; está explícitamente diseñado para ser un lenguaje "multi-paradigma". Las comprensiones de listas no son métodos en iterables. zipes una función de nivel superior, no un zip_withmétodo. Y así. El hecho de que todo sea un objeto no significa que ser un objeto sea lo más importante de cada cosa.
abarnert

77
Ese artículo está tan mal escrito que me siento confundido y enojado después de intentar leerlo. "En Python 2.x, el tipo Tuple, por ejemplo, no expone ningún método no especial y, sin embargo, puede usarlo para formar una cadena:" Todo esto es una amalgamación de oraciones casi indescifrables.
MirroredFate

93

La respuesta de Jim a esta pregunta puede ayudar; Lo copio aquí. Citando a Guido van Rossum:

En primer lugar, elegí len (x) sobre x.len () por razones de HCI (def __len __ () llegó mucho más tarde). En realidad, hay dos razones entrelazadas, ambas HCI:

(a) Para algunas operaciones, la notación de prefijo solo se lee mejor que postfix: las operaciones de prefijo (y ¡infijo!) tienen una larga tradición en matemáticas que le gustan las anotaciones en las que las imágenes ayudan al matemático a pensar en un problema. Compare lo fácil con lo que reescribimos una fórmula como x * (a + b) en x a + x b con la torpeza de hacer lo mismo usando una notación OO sin procesar.

(b) Cuando leo el código que dice len (x) sé que está pidiendo la longitud de algo. Esto me dice dos cosas: el resultado es un número entero y el argumento es algún tipo de contenedor. Por el contrario, cuando leo x.len (), ya debo saber que x es algún tipo de contenedor que implementa una interfaz o hereda de una clase que tiene un estándar len (). Sea testigo de la confusión que ocasionalmente tenemos cuando una clase que no está implementando una asignación tiene un método get () o keys (), o algo que no es un archivo tiene un método write ().

Al decir lo mismo de otra manera, veo 'len' como una operación integrada. Odiaría perder eso. / ... /


37

Hay un lenmétodo:

>>> a = 'a string of some length'
>>> a.__len__()
23
>>> a.__len__
<method-wrapper '__len__' of str object at 0x02005650>

34

Python es un lenguaje de programación pragmática, y las razones de len()ser una función y no un método de str, list, dictetc, son pragmáticos.

La len()función incorporada trata directamente con los tipos incorporados: la implementación de CPython len()realmente devuelve el valor del ob_sizecampo en la PyVarObjectestructura C que representa cualquier objeto incorporado de tamaño variable en la memoria. Esto es mucho más rápido que llamar a un método: no es necesario realizar ninguna búsqueda de atributos. Obtener el número de elementos de una colección es una operación común y debe trabajar de manera eficiente para los tipos básicos y diversos tales como str, list, array.arrayetc.

Sin embargo, para promover la coherencia, cuando se aplica len(o)a un tipo definido por el usuario, Python llama o.__len__()como reserva. __len__, __abs__y todos los demás métodos especiales documentados en el Modelo de datos de Python facilitan la creación de objetos que se comportan como los integrados, permitiendo las API expresivas y altamente consistentes que llamamos "Pythonic".

Al implementar métodos especiales, sus objetos pueden soportar la iteración, sobrecargar operadores infix, administrar contextos en withbloques, etc. Puede pensar en el modelo de datos como una forma de usar el lenguaje Python como un marco donde los objetos que crea se pueden integrar sin problemas.

Una segunda razón, respaldada por citas de Guido van Rossum como esta , es que es más fácil de leer y escribir len(s)que s.len().

La notación len(s)es consistente con operadores unarios con notación de prefijo, como abs(n). len()se usa con mucha más frecuencia abs()y merece ser tan fácil de escribir.

También puede haber una razón histórica: en el lenguaje ABC que precedió a Python (y fue muy influyente en su diseño), había un operador unario escrito como se #squería decir len(s).


12
met% python -c 'import this' | grep 'only one'
There should be one-- and preferably only one --obvious way to do it.

34
Lo obvio cuando se trabaja con un objeto es, por supuesto, un método.
Peter Cooper

44
@Peter: Pagaría $ 20 a cualquier persona con evidencia fotográfica de que pegaron su comentario en la espalda de Guido. $ 50 si está en su frente.
error

1
Sí, los diseñadores de Python se adhieren al dogma, pero ellos mismos no respetan su propio dogma.
bitek

2
$ python -c 'importar esto' | grep obvio
Ed Randall

4

Aquí hay algunas respuestas excelentes, por lo que antes de dar la mía me gustaría destacar algunas de las gemas (sin intención de juego de palabras con rubí) que he leído aquí.

  • Python no es un lenguaje OOP puro: es un lenguaje de paradigma múltiple de propósito general que permite al programador usar el paradigma con el que se siente más cómodo y / o el paradigma que mejor se adapta a su solución.
  • Python tiene funciones de primera clase, por lenlo que en realidad es un objeto. Ruby, por otro lado, no tiene funciones de primera clase. Por lo tanto, el lenobjeto de función tiene sus propios métodos que puede inspeccionar ejecutando dir(len).

Si no le gusta cómo funciona esto en su propio código, es trivial volver a implementar los contenedores utilizando su método preferido (vea el ejemplo a continuación).

>>> 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

0

También puede decir

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

Usando Python 2.7.3.


9
lenLa función ya se mencionó en respuestas anteriores. ¿Cuál es el punto de esta "respuesta", entonces?
Piotr Dobrogost

0

Algo falta en el resto de las respuestas aquí: la lenfunción verifica que el __len__método devuelva un valor no negativo int. El hecho de que lensea ​​una función significa que las clases no pueden anular este comportamiento para evitar la verificación. Como tal, len(obj)da un nivel de seguridad que obj.len()no puede.

Ejemplo:

>>> 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

Por supuesto, es posible "anular" la lenfunción reasignándola como una variable global, pero el código que hace esto es mucho más obviamente sospechoso que el código que anula un método en una clase.


Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.