Soy un novato de Python, y no estoy seguro de por qué Python implementó len (obj), max (obj) y min (obj) como funciones estáticas (soy del lenguaje java) sobre obj.len (), obj .max () y obj.min ()

¿Cuáles son las ventajas y desventajas (aparte de la inconsistencia obvia) de tener len () ... sobre las llamadas al método?

¿Por qué Guido eligió esto sobre las llamadas al método? (esto podría haberse resuelto en python3 si fuera necesario, pero no se cambió en python3, por lo que debe haber buenas razones ... espero)

¡¡Gracias!!

9
user186477 27 oct. 2009 a las 04:02

4 respuestas

La mejor respuesta

La gran ventaja es que las funciones integradas (y los operadores) pueden aplicar lógica adicional cuando sea apropiado, más allá de simplemente llamar a los métodos especiales. Por ejemplo, min puede ver varios argumentos y aplicar las comprobaciones de desigualdad adecuadas, o puede aceptar un único argumento iterable y proceder de manera similar; abs cuando se llama a un objeto sin un método especial __abs__ podría intentar comparar dicho objeto con 0 y usar el método de signo de cambio de objeto si es necesario (aunque actualmente no lo hace); Etcétera.

Por lo tanto, para mantener la coherencia, todas las operaciones con una amplia aplicabilidad siempre deben pasar por operadores y / u operadores integrados, y es responsabilidad de los usuarios integrados buscar y aplicar los métodos especiales apropiados (en uno o más de los argumentos), usar alternativas lógica donde sea aplicable, y así sucesivamente.

Un ejemplo en el que este principio no se aplicó correctamente (pero la inconsistencia se arregló en Python 3) es "avanzar un iterador hacia adelante": en 2.5 y anteriores, tenía que definir y llamar a next que no tiene un nombre especial. método en el iterador. En 2.6 y posterior puede hacerlo de la manera correcta: el objeto iterador define __next__, el nuevo next incorporado puede llamarlo y aplicar lógica adicional, por ejemplo para proporcione un valor predeterminado (en 2.6 todavía puede hacerlo de la manera antigua, por compatibilidad con versiones anteriores, aunque en 3.* ya no puede hacerlo).

Otro ejemplo: considere la expresión x + y. En un lenguaje tradicional orientado a objetos (capaz de distribuir solo en el tipo del argumento más a la izquierda, como Python, Ruby, Java, C ++, C #, & c) si x es de algún tipo integrado y y es de su propio tipo nuevo y elegante, lamentablemente no tendrá suerte si el lenguaje insiste en delegar toda la lógica al método de type(x) que implementa la suma (asumiendo que el lenguaje permite la sobrecarga del operador ;-).

En Python, el operador + (y de manera similar, por supuesto, el operator.add incorporado, si eso es lo que prefiere) intenta con el tipo de x __add__, y si ese no sabe qué hacer con y, luego prueba el tipo de y __radd__. Por lo tanto, puede definir sus tipos que saben cómo agregarse a enteros, flotantes, complejos, etc., etc., así como los que saben cómo agregar tales tipos numéricos incorporados a sí mismos (es decir, puede codificarlo para que {{ X5}} y y + x funcionan bien, cuando y es una instancia de su nuevo tipo elegante y x es una instancia de algún tipo numérico incorporado).

Las "funciones genéricas" (como en PEAK) son un enfoque más elegante (que permite cualquier anulación basada en una combinación de tipos, nunca con el loco enfoque monomaníaco en los argumentos más a la izquierda que OOP fomenta -), pero (a) desafortunadamente no fueron aceptados para Python 3, y (b) por supuesto requieren que la función genérica se exprese como independiente (sería una locura tener que considerar la función como "perteneciente" a cualquier tipo, donde todo el PUNTO es que se puede anular / sobrecargar de manera diferente en función de la combinación arbitraria de sus varios tipos de argumentos! -). Cualquiera que haya programado en Common Lisp, Dylan o PEAK, sabe de lo que estoy hablando ;-).

Por lo tanto, las funciones y los operadores independientes son LA forma correcta y coherente de hacerlo (aunque la falta de funciones genéricas, en Python, elimina la parte fracción de la elegancia inherente, sigue siendo ¡Una mezcla razonable de elegancia y practicidad! -).

19
Alex Martelli 27 oct. 2009 a las 01:30

Pensé que la razón era para que estas operaciones básicas se pudieran realizar en iteradores con la misma interfaz que los contenedores. Sin embargo, en realidad no funciona con len:

def foo():
    for i in range(10):
        yield i
print len(foo())

... falla con TypeError. len () no consumirá y contará un iterador; solo funciona con objetos que tienen una llamada __len__.

Entonces, en lo que a mí respecta, len () no debería existir. Es mucho más natural decir obj.len que len (obj), y mucho más coherente con el resto del lenguaje y la biblioteca estándar. No decimos agregar (lst, 1); decimos lst.append (1). Tener un método global separado para la longitud es un caso especial extraño e inconsistente, y se come un nombre muy obvio en el espacio de nombres global, que es un mal hábito de Python.

Esto no está relacionado con la escritura de pato puede decir getattr(obj, "len") para decidir si puede usar len en un objeto con la misma facilidad, y mucho más consistentemente, de lo que puede usar getattr(obj, "__len__").

Todo lo dicho, a medida que las verrugas del lenguaje van, para aquellos que lo consideran una verruga, es muy fácil vivir con ellas.

Por otro lado, min y max do funcionan en iteradores, lo que les da un uso aparte de cualquier objeto en particular. Esto es sencillo, así que solo daré un ejemplo:

import random
def foo():
    for i in range(10):
        yield random.randint(0, 100)
print max(foo())

Sin embargo, no hay métodos __min__ o __max__ para anular su comportamiento, por lo que no hay una manera consistente de proporcionar una búsqueda eficiente de contenedores ordenados. Si un contenedor está ordenado en la misma clave que está buscando, min / max son operaciones O (1) en lugar de O (n), y la única forma de exponerlo es mediante un método diferente e inconsistente. (Esto podría arreglarse en el idioma con relativa facilidad, por supuesto).

Para seguir con otro problema con esto: evita el uso del método de enlace de Python. Como un ejemplo simple y artificial, puede hacer esto para proporcionar una función para agregar valores a una lista:

def add(f):
    f(1)
    f(2)
    f(3)
lst = []
add(lst.append)
print lst

Y esto funciona en todas las funciones miembro. Sin embargo, no puede hacer eso con min, max o len, ya que no son métodos del objeto sobre el que operan. En su lugar, debe recurrir a functools.partial, una solución torpe de segunda clase común en otros idiomas.

Por supuesto, este es un caso poco común; pero son los casos poco comunes que nos informan sobre la consistencia de un idioma.

1
Glenn Maynard 27 oct. 2009 a las 01:52

En realidad, esos no son métodos "estáticos" en la forma en que piensas en ellos. Son funciones integradas que realmente solo alias a ciertos métodos en los objetos de Python que implementan ellos.

>>> class Foo(object):
...     def __len__(self):
...             return 42
... 
>>> f = Foo()
>>> len(f)
42

Estos siempre están disponibles para ser llamados independientemente de si el objeto los implementa o no. El punto es tener cierta consistencia. En lugar de que una clase tenga un método llamado length () y otro llamado size (), la convención es implementar len y dejar que las personas que llaman siempre accedan a él por el len más legible (obj) en lugar de obj. methodThatDoesSomethingCommon

3
Jim Ferrans 20 dic. 2011 a las 05:59

Enfatiza las capacidades de un objeto, no sus métodos o tipo. Las funciones "auxiliares" declaran las capacidades como __iter__ y __len__ pero no constituyen la interfaz. La interfaz está en las funciones incorporadas, y además de esto también en los operadores incorporados como + y [] para indexar y segmentar.

A veces, no es una correspondencia uno a uno: por ejemplo, iter(obj) devuelve un iterador para un objeto y funcionará incluso si __iter__ no está definido. Si no se define, continúa para ver si el objeto define __getitem__ y devolverá un iterador que accede al objeto en forma de índice (como una matriz).

Esto va de la mano con Duck Typing de Python, solo nos importa lo que podemos hacer con un objeto, no que sea de un tipo en particular.

3
u0b34a0f6ae 4 mar. 2010 a las 22:16