6

Al usar lista por comprensión o la palabra clave in en una para el contexto de bucle, es decir:Python: el mecanismo detrás de lista por comprensión

for o in X: 
    do_something_with(o) 

o

l=[o for o in X] 
  • ¿Cómo funciona el mecanismo detrás de in obras ?
  • ¿Qué funciones \ métodos dentro de X llama?
  • Si X puede cumplir con más de un método, ¿cuál es la precedencia?
  • Cómo escribir un eficiente X, para que la comprensión de la lista sea rápida?
+2

Tenga en cuenta que la palabra clave "in" se usa en dos contextos distintos en Python. Hay iteraciones (con el teclado "para") y hay contextos booleanos/condicionales (a veces con "si" o "mientras"). Este último recurre a los métodos __contains__ para sus objetos. –

Respuesta

9

The, afaik, respuesta completa y correcta.

for, tanto en bucles como en listas de comprensión, llamadas iter() en X. iter() devolverá un iterable si X tiene un método __iter__ o un método __getitem__. Si implementa ambos, se usa __iter__. Si ninguno de los dos tiene TypeError: 'Nothing' object is not iterable.

Esto implementa un __getitem__:

class GetItem(object): 
    def __init__(self, data): 
     self.data = data 

    def __getitem__(self, x): 
     return self.data[x] 

Uso:

>>> data = range(10) 
>>> print [x*x for x in GetItem(data)] 
[0, 1, 4, 9, 16, 25, 36, 49, 64, 81] 

Este es un ejemplo de la aplicación de __iter__:

class TheIterator(object): 
    def __init__(self, data): 
     self.data = data 
     self.index = -1 

    # Note: In Python 3 this is called __next__ 
    def next(self): 
     self.index += 1 
     try: 
      return self.data[self.index] 
     except IndexError: 
      raise StopIteration 

    def __iter__(self): 
     return self 

class Iter(object): 
    def __init__(self, data): 
     self.data = data 

    def __iter__(self): 
     return TheIterator(data) 

Uso:

>>> data = range(10) 
>>> print [x*x for x in Iter(data)] 
[0, 1, 4, 9, 16, 25, 36, 49, 64, 81] 

Como ve, necesita ambos implementar un iterador, y __iter__ que devuelve el iterador.

Puedes combinarlos:

class CombinedIter(object): 
    def __init__(self, data): 
     self.data = data 

    def __iter__(self): 
     self.index = -1 
     return self 

    def next(self): 
     self.index += 1 
     try: 
      return self.data[self.index] 
     except IndexError: 
      raise StopIteration 

Uso:

>>> well, you get it, it's all the same... 

Pero entonces sólo puede tener un iterador funcionando al mismo tiempo. bien, en este caso, usted podría hacer esto:

class CheatIter(object): 
    def __init__(self, data): 
     self.data = data 

    def __iter__(self): 
     return iter(self.data) 

Pero eso es hacer trampa, porque se acaba de volver a utilizar el método de list__iter__. Una forma más fácil es usar el rendimiento, y hacer __iter__ en un generador:

class Generator(object): 
    def __init__(self, data): 
     self.data = data 

    def __iter__(self): 
     for x in self.data: 
      yield x 

Esta última es la forma en que yo recomendaría. Fácil y eficiente

+0

+1 o más si pudiera. Esa es una respuesta fabulosa. ¡Gracias! –

+0

+1 Respuesta excelente y completa. Gracias por todos los comentarios, también. –

2

Tal vez esto ayuda (tutorial http://docs.python.org/tutorial/classes.html sección 9.9):

Detrás de las escenas, la sentencia for llamadas iter() en el objeto contenedor. La función devuelve un iterador objeto que define el siguiente método() que accede a los elementos en el contenedor uno a la vez. Cuando hay no hay más elementos, next() provoca una excepción StopIteration que indica que el ciclo for finaliza.

5

X deben ser iterables. Debe implementar __iter__() que devuelve un objeto iterador; el objeto iterador debe implementar next(), que devuelve el siguiente elemento cada vez que se lo llama o aumenta StopIteration si no hay un siguiente elemento.

Las listas, tuplas y generadores son iterables.

Tenga en cuenta que el operador simple for utiliza el mismo mecanismo.

+0

Los iteradores también tienen que implementar '__iter __()', aunque pueden simplemente devolver una referencia a sí mismos – Cameron

+0

Correcto, los generadores hacen exactamente eso. – 9000

+1

X puede implementar '__getitem__' en su lugar. –

3

X debe ser un objeto iterable, lo que significa que debe tener un método __iter__().

Por lo tanto, para iniciar un ciclo for..in, o una lista de comprensión, primero se llama al método X__iter__() para obtener un objeto iterador; luego se llama al método next() de ese objeto para cada iteración hasta que se genera StopIteration, en cuyo punto la iteración se detiene.

No estoy seguro de qué significa su tercera pregunta, y cómo proporcionar una respuesta significativa a su cuarta pregunta, excepto que su iterador no debe construir toda la lista en la memoria a la vez.

+0

No, necesita tener '__iter __()' o '__geitem __()'. –

+0

@Lennart: Gracias por ayudarme. –

0

para responder a sus preguntas:

¿Cómo funciona el mecanismo detrás de las obras?

Es el mismo mecanismo exacto utilizado para los bucles for normales, como ya se ha indicado.

¿Qué funciones \ métodos dentro de X llama?

Como se señala en un comentario a continuación, llama al iter(X) para obtener un iterador. Si X tiene una función de método __iter__() definida, se invocará para devolver un iterador; de lo contrario, si X define __getitem__(), se invocará repetidamente para iterar sobre X. Consulte la documentación de Python para iter() aquí: http://docs.python.org/library/functions.html#iter

Si X puede cumplir con más de un método, ¿cuál es la precedencia?

No estoy seguro de cuál es exactamente su pregunta aquí, pero Python tiene reglas estándar sobre cómo resuelve los nombres de los métodos, y se siguen aquí. Aquí hay una discusión de esto:

Method Resolution Order (MRO) in new style Python classes

cómo escribir una X eficiente, de manera que la lista de la comprensión será rápido?

Le sugiero que lea más sobre iteradores y generadores en Python. Una manera fácil de realizar cualquier iteración de soporte de clase es hacer que una función de generador sea iter(). Aquí es una discusión de los generadores:

http://linuxgazette.net/100/pramode.html

+0

En realidad, no invoca 'iter()' que llama a '__iter __()' o devuelve un iterador que usa '__getitem__'. –

4

Responder a los comentarios de interrogación puedo decir que la lectura de la fuente no es la mejor idea en este caso. El código que es responsable de la ejecución del código compilado (ceval.c) no parece ser muy detallado para una persona que ve fuentes de Python por primera vez. Aquí es el fragmento que representa iteración de los bucles:

TARGET(FOR_ITER) 
     /* before: [iter]; after: [iter, iter()] *or* [] */ 
     v = TOP(); 

     /* 
      Here tp_iternext corresponds to next() in Python 
     */ 
     x = (*v->ob_type->tp_iternext)(v); 
     if (x != NULL) { 
      PUSH(x); 
      PREDICT(STORE_FAST); 
      PREDICT(UNPACK_SEQUENCE); 
      DISPATCH(); 
     } 
     if (PyErr_Occurred()) { 
      if (!PyErr_ExceptionMatches(
          PyExc_StopIteration)) 
       break; 
      PyErr_Clear(); 
     } 
     /* iterator ended normally */ 
     x = v = POP(); 
     Py_DECREF(v); 
     JUMPBY(oparg); 
     DISPATCH(); 

Para encontrar lo que realmente sucede aquí lo que necesita para sumergirse en montón de otros archivos, que nivel de detalle no es mucho mejor. Por lo tanto, creo que, en tales casos, la documentación y los sitios como SO son el primer lugar donde debe ir, mientras que la fuente debe verificarse solo para detalles de implementación descubiertos.

Cuestiones relacionadas