Las ventajas de tener una función estática como len (), max () y min () sobre las llamadas a métodos heredados

StackOverflow https://stackoverflow.com/questions/1628222

  •  06-07-2019
  •  | 
  •  

Pregunta

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

¿Fue útil?

Solución

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); y así sucesivamente.

Por lo tanto, para mantener la coherencia, todas las operaciones con 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) , utilice una lógica alternativa cuando corresponda, 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 '': en 2.5 y anteriores, tenía que definir y llamar a los < code> next en el iterador. En 2.6 y versiones posteriores 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 proporcionar un valor predeterminado (en 2.6 todavía puede hacerlo a la antigua usanza, por compatibilidad hacia atrás, aunque en 3. * ya no puede).

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 #, & amp; c) si x es de algún tipo en tipo y y es de su propio tipo nuevo y elegante, lamentablemente no tiene suerte si el lenguaje insiste en delegar toda la lógica al método de type (x) que implementa la suma (suponiendo que el lenguaje permita 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 __add __ , y si ese no sabe qué hacer con y , intenta con el __radd__ del tipo y. 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 x + y 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 integrado).

" Funciones genéricas " (como en PEAK) es 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 único, donde todo el PUNTO es que se puede anular / sobrecargar de manera diferente en función de la combinación arbitraria de sus diferentes 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 EL camino correcto y consistente (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! -).

Otros consejos

Enfatiza las capacidades de un objeto, no sus métodos o tipo. Las capacidades son declaradas por "ayudante" funciones como __iter__ y __len__ pero no conforman 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 junto con Duck Typing de Python, solo nos importa lo que podemos hacer con un objeto, no que sea de un tipo particular.

En realidad, esos no son "estáticos" métodos en la forma en que estás pensando en ellos. Son funciones integradas que realmente solo alias a ciertos métodos en objetos 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 permitir que las personas que llaman siempre accedan a él por el len más legible (obj) en lugar de obj. methodThatDoesSomethingCommon

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, que puede usar getattr (obj , " __ len __ ") .

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

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 solucionarse 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 cambio, 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.

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top