¿Diferencia entre len () y .__ len __ ()?

Respuestas:

102

lenes una función para obtener la longitud de una colección. Funciona llamando al __len__método de un objeto . __something__los atributos son especiales y, por lo general, más de lo que parece, y por lo general no deben llamarse directamente.

Se decidió en algún momento hace mucho tiempo que obtener la longitud de algo debería ser una función y no un código de método, razonando que len(a)el significado sería claro para los principiantes, pero a.len()no tan claro. Cuando Python comenzó __len__ni siquiera existía y lenera algo especial que funcionaba con algunos tipos de objetos. Ya sea que la situación que esto nos deja tenga total sentido o no, está aquí para quedarse.

Mike Graham
fuente
66

A menudo ocurre que el comportamiento "típico" de un operador integrado o es llamar (con una sintaxis diferente y más agradable) métodos mágicos adecuados (aquellos con nombres como __whatever__) en los objetos involucrados. A menudo, el operador integrado o tiene un "valor agregado" (es capaz de tomar diferentes caminos dependiendo de los objetos involucrados); en el caso de lenvs __len__, es solo un poco de verificación de cordura en el elemento integrado que falta en el método mágico:

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

Cuando vea una llamada al lenintegrado, está seguro de que, si el programa continúa después de eso en lugar de generar una excepción, la llamada ha devuelto un número entero, no negativo y menos de 2 ** 31 - cuando ve una llamada a xxx.__len__(), no tiene certeza (excepto que el autor del código no está familiarizado con Python o no está bien ;-).

Otros componentes incorporados brindan aún más valor agregado más allá de simples comprobaciones de cordura y legibilidad. Al diseñar uniformemente todo Python para que funcione a través de llamadas a incorporaciones y el uso de operadores, nunca a través de llamadas a métodos mágicos, los programadores se libran de la carga de recordar qué caso es cuál. (A veces aparece un error: hasta 2.5, tenía que llamar foo.next()- en 2.6, mientras que todavía funciona para la compatibilidad con versiones anteriores, debe llamar next(foo), y en 3.*, el método mágico tiene el nombre correcto en __next__lugar del "oops-ey" next! - ).

Por lo tanto, la regla general debería ser nunca llamar a un método mágico directamente (pero siempre indirectamente a través de un método incorporado) a menos que sepa exactamente por qué necesita hacerlo (por ejemplo, cuando está anulando un método de este tipo en una subclase, si el subclase necesita diferir a la superclase que debe hacerse mediante una llamada explícita al método mágico).

Alex Martelli
fuente
Soy un usuario principiante de Python (no pensaba el programador principiante) y no estoy seguro acerca de "Cuando veas una llamada al len incorporado, estás seguro de que, si el programa continúa después de eso en lugar de generar una excepción". Intenté esto: def len(x): return "I am a string." print(len(42)) print(len([1,2,3]))e imprimió I am stringdos veces. ¿Puedes explicarlo más?
Darek Nędza
4
@ DarekNędza Esto no tiene nada que ver con lo anterior, que se trata de len incorporado. Acaba de definir su función len, que por supuesto puede devolver lo que quiera. OP habló sobre builtin len, que llama a __len__un método especial (no a una función) en el objeto en consideración.
Veky
@Veky ¿Cómo puedo estar seguro de que estoy llamando a la función incorporada, lenno a otra función (como en mi ejemplo) que tiene el mismo nombre - len. No hay ninguna advertencia como "Está redefiniendo la función incorporada len" o algo así. En mi opinión, no puedo estar seguro de lo que dijo Alex en su respuesta.
Darek Nędza
3
Alex dijo explícitamente que si llamas de forma incorporada, entonces estás seguro ..._. No dijo nada sobre estar seguro de que estás llamando integrado. Pero si quieres saber que, se puede: len in vars(__builtins__).values().
Veky
1
Lamentablemente, este es otro ejemplo de la falta de una clase base común para los objetos en Python. El cambio de contexto sintáctico siempre ha sido una locura. En algunos casos, es un modismo común usar un método de subrayado, en otros uno debería usar algo como una función para hacer algo común a muchos objetos. También es extraño porque muchos objetos no tienen uso semántico para len. A veces, el modelo de objetos es más como C ++, cocina hundida ..
uchuugaka
28

Puede pensar que len () es aproximadamente equivalente a

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

Una ventaja es que te permite escribir cosas como

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

en vez de

map(list.__len__, somelist)

o

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

Sin embargo, hay un comportamiento ligeramente diferente. Por ejemplo en el caso de 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
fuente
2
Supongo que te refieres en operator.methodcallerlugar de operator.attrgetter.
Elazar
5

Puede consultar los documentos de 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
fuente