Pregunta

Cuando se utiliza la comprensión de listas o la in palabra clave en un contexto de bucle for, es decir:

for o in X:
    do_something_with(o)

o

l=[o for o in X]
  • ¿Cómo funciona el mecanismo detrás? in ¿obras?
  • Qué funciones\métodos dentro 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?
¿Fue útil?

Solución

La respuesta, afaik, completa y correcta.

for, tanto en bucles for como en listas por comprensión, llamadas iter() en X. iter() devolverá un iterable si X cualquiera tiene un __iter__ método o un __getitem__ método.Si implementa ambos, __iter__ se utiliza.Si no tiene ni lo obtienes 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 implementación __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 puede ver, necesita 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 solo puedes tener un iterador a la vez.Bien, en este caso podrías 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 simplemente estás reutilizando el __iter__ método de list.Una forma más sencilla es utilizar 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 que recomendaría.Fácil y eficiente.

Otros consejos

X debe ser iterable.Debe implementar __iter__() que devuelve un objeto iterador;el objeto iterador debe implementar next(), que devuelve el siguiente elemento cada vez que se llama o genera un StopIteration si no hay ningún siguiente elemento.

Las listas, tuplas y generadores son todos iterables.

Tenga en cuenta que la llanura for El operador utiliza el mismo mecanismo.

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

   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 descubrir lo que realmente sucede aquí, debe profundizar en muchos otros archivos cuya 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 al que acudir, mientras que la fuente debe verificarse solo para obtener detalles de implementación descubiertos.

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

Entonces, para comenzar un for..in bucle, o una lista de comprensión, primero X's __iter__() se llama al método para obtener un objeto iterador;entonces ese objeto es next() Se llama al método para cada iteración hasta que StopIteration se plantea, momento en el que se detiene la iteración.

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

Quizás esto ayude (tutorial http://docs.python.org/tutorial/classes.html Sección 9.9):

Detrás de escena, la instrucción para For llama iter () en el objeto contenedor.La función devuelve un objeto Iterator que define el método Next () que accede a los elementos en el contenedor uno a la vez.Cuando no hay más elementos, Next () plantea una excepción de stopiteration que le indica al bucle for que termine.

Para responder tu pregunta:

¿Cómo funciona el mecanismo detrás?

Es exactamente el mismo mecanismo que se utiliza para los bucles for ordinarios, como ya han señalado otros.

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

Como se indica en un comentario a continuación, llama iter(X) para obtener un iterador.Si X tiene una función de método __iter__() definido, esto será llamado para devolver un iterador;de lo contrario, si X define __getitem__(), esto será llamado repetidamente para iterar 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 sobre esto:

Orden de resolución de métodos (MRO) en clases de Python de nuevo estilo

¿Cómo escribir una X eficiente para que la comprensión de la lista sea rápida?

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 crear una función generadora para iterar().Aquí hay una discusión sobre generadores:

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

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