listas - sentencias en python
¿Por qué se invierten y se clasifican diferentes tipos en Python? (2)
el tipo reversed
es "tipo":
>>> type(reversed)
<class ''type''>
El tipo de sorted
es "función o método incorporado":
>>> type(sorted)
<class ''builtin_function_or_method''>
Sin embargo, parecen lo mismo en la naturaleza. Excluyendo la diferencia obvia en la funcionalidad (reversa vs. ordenación de secuencias), ¿cuál es la razón de esta diferencia en la implementación?
¿Cuál es la diferencia entre
reversed
ysorted
?
Curiosamente, reversed
no es una función, mientras que sorted
es.
Abra una sesión REPL y escriba help(reversed)
:
class reversed(object)
| reversed(sequence) -> reverse iterator over values of the sequence
|
| Return a reverse iterator
De hecho, es una clase que se utiliza para devolver un iterador inverso.
De acuerdo, lo
reversed
no es una función. ¿Pero por qué no?
Esto es un poco difícil de responder. Una explicación es que los iteradores tienen una evaluación perezosa. Esto requiere algún tipo de contenedor para almacenar información sobre el estado actual del iterador en un momento dado. Esto se hace mejor a través de un objeto, y por lo tanto, una class
.
La diferencia es que el reversed
es un iterador (también es perezoso-evaluador) y se sorted
una función que funciona "con entusiasmo".
Todos los iteradores incorporados (al menos en python-3.x) como map
, zip
, filter
, reversed
, ... se implementan como clases . Mientras que las funciones integradas que funcionan con entusiasmo son funciones , por ejemplo, min
, max
, any
, all
y sorted
.
>>> a = [1,2,3,4]
>>> r = reversed(a)
<list_reverseiterator at 0x2187afa0240>
Realmente necesita "consumir" el iterador para obtener los valores (por ejemplo, list
):
>>> list(r)
[4, 3, 2, 1]
Por otro lado, esta parte "consumidora" no es necesaria para funciones como las sorted
:
>>> s = sorted(a)
[1, 2, 3, 4]
En los comentarios se preguntó por qué se implementan como clases en lugar de funciones . Eso no es realmente fácil de responder pero haré mi mejor esfuerzo:
El uso de operaciones de evaluación perezosa tiene un gran beneficio: son muy eficientes con la memoria cuando están encadenados. No necesitan crear listas intermedias a menos que estén explícitamente "solicitadas". Esa fue la razón por la que el map
, el zip
y el filter
se cambiaron de funciones operativas ávidas (python-2.x) a clases perezosas (python-3.x).
En general, hay dos formas en Python para crear iteradores:
- Clases que
return self
en su método de__iter__
- Funciones del generador - funciones que contienen un
yield
Sin embargo (al menos CPython) implementa todas sus incorporaciones (y varios módulos de biblioteca estándar) en C. Es muy fácil crear clases de iteradores en C, pero no he encontrado ninguna forma sensata de crear funciones de generador basadas en Python-C -API. Por lo tanto, la razón por la que estos iteradores se implementan como clases (en CPython) puede ser simplemente por conveniencia o por la falta de alternativas (rápidas o implementables).
Hay una razón adicional para usar clases en lugar de generadores: puede implementar métodos especiales para las clases pero no puede implementarlas en las funciones del generador. Puede que no suene impresionante pero tiene ventajas definidas. Por ejemplo, la mayoría de los iteradores pueden ser pickled (al menos en Python-3.x) usando los métodos __reduce__
y __setstate__
. Eso significa que puede almacenarlos en el disco, y permite copiarlos. Como Python-3.4, algunos iteradores también implementan __length_hint__
que hace que consumir estos iteradores con list
(y similares) sea mucho más rápido.
Tenga en cuenta que reversed
podría implementarse fácilmente como función de fábrica (como iter
), pero a diferencia de iter
, que puede devolver dos clases únicas , reversed
solo puede devolver una clase única .
Para ilustrar las clases posibles (y únicas), debe considerar una clase que no __iter__
un método __iter__
y no __reversed__
pero que sea iterable y revertible (implementando __getitem__
y __len__
):
class A(object):
def __init__(self, vals):
self.vals = vals
def __len__(self):
return len(self.vals)
def __getitem__(self, idx):
return self.vals[idx]
Y aunque tiene sentido agregar una capa de abstracción (una función de fábrica) en caso de iter
, porque la clase devuelta depende del número de argumentos de entrada:
>>> iter(A([1,2,3]))
<iterator at 0x2187afaed68>
>>> iter(min, 0) # actually this is a useless example, just here to see what it returns
<callable_iterator at 0x1333879bdd8>
Ese razonamiento no se aplica a reversed
:
>>> reversed(A([1,2,3]))
<reversed at 0x2187afaec50>