while terminar for español ejemplos como ciclo bucle python iterator

terminar - Python para el comportamiento de iteración y bucle



for i in range python español (6)

Algunos detalles adicionales sobre el comportamiento de iter() con las clases __getitem__ que carecen de su propio método __iter__ .

Antes de __iter__ había __getitem__ . Si el __getitem__ funciona con int s desde 0 - len(obj)-1 , entonces iter() admite estos objetos. Construirá un nuevo iterador que repetidamente llama a __getitem__ con 0 , 1 , 2 , ... hasta que obtenga un IndexError , que convierte a StopIteration .

Consulte esta respuesta para obtener más detalles sobre las diferentes formas de crear un iterador.

Quería entender un poco más acerca de los iterators , así que por favor corrígeme si estoy equivocado.

Un iterador es un objeto que tiene un puntero al siguiente objeto y se lee como un búfer o secuencia (es decir, una lista vinculada). Son particularmente eficientes porque todo lo que hacen es decirte lo que sigue por referencias en lugar de utilizar la indexación.

Sin embargo, todavía no entiendo por qué está sucediendo el siguiente comportamiento:

In [1]: iter = (i for i in range(5)) In [2]: for _ in iter: ....: print _ ....: 0 1 2 3 4 In [3]: for _ in iter: ....: print _ ....: In [4]:

Después de un primer ciclo a través del iterador ( In [2] ) es como si se hubiera consumido y se hubiera dejado vacío, por lo que el segundo ciclo ( In [3] ) no imprime nada.

Sin embargo, nunca le asigné un nuevo valor a la variable iter .

¿Qué está sucediendo realmente bajo el capó del bucle for ?


Existe un protocolo de iterador en python que define cómo se comportará la instrucción for con las listas y los dicts, y otras cosas que pueden enlazarse.

Está en los documentos de Python here y here .

La forma en que el protocolo de iterador funciona normalmente tiene la forma de un generador de python. yield un valor siempre que tengamos un valor hasta que lleguemos al final y luego levantamos StopIteration

Así que vamos a escribir nuestro propio iterador:

def my_iter(): yield 1 yield 2 yield 3 raise StopIteration() for i in my_iter(): print i

El resultado es:

1 2 3

Un par de cosas para notar sobre eso. El my_iter es una función. my_iter () devuelve un iterador.

Si hubiera escrito usando un iterador como este en su lugar:

j = my_iter() #j is the iterator that my_iter() returns for i in j: print i #this loop runs until the iterator is exhausted for i in j: print i #the iterator is exhausted so we never reach this line

Y el resultado es el mismo que el anterior. El iter se agota cuando ingresamos al segundo ciclo for.

Pero eso es bastante simplista ¿y algo más complicado? Tal vez tal vez en un bucle ¿por qué no?

def capital_iter(name): for x in name: yield x.upper() raise StopIteration() for y in capital_iter(''bobert''): print y

Y cuando se ejecuta, usamos el iterador en el tipo de cadena (que está integrado en iter ). Esto, a su vez, nos permite ejecutar un ciclo de bucle y arrojar los resultados hasta que hayamos terminado.

B O B E R T

Así que ahora esto plantea la pregunta, entonces, ¿qué ocurre entre los rendimientos en el iterador?

j = capital_iter("bobert") print i.next() print i.next() print i.next() print("Hey there!") print i.next() print i.next() print i.next() print i.next() #Raises StopIteration

La respuesta es que la función está pausada en el rendimiento esperando la siguiente llamada a next ().

B O B Hey There! E R T Traceback (most recent call last): File "", line 13, in StopIteration


For loop básicamente llama al next método de un objeto que se aplica a ( __next__ en Python 3).

Puedes simular esto simplemente haciendo:

iter = (i for i in range(5)) print(next(iter)) print(next(iter)) print(next(iter)) print(next(iter)) print(next(iter)) # this prints 1 2 3 4

En este punto, no hay próximo elemento en el objeto de entrada. Haciendo esto:

print(next(iter))

Se StopIteration excepción StopIteration . En este punto, se detendrá. Y el iterador puede ser cualquier objeto que responda a la función next() y arroje la excepción cuando no haya más elementos. No tiene que ser ningún puntero o referencia (no hay tales cosas en Python de todos modos en sentido C / C ++), lista enlazada, etc.


Su sospecha es correcta: el iterador se ha consumido.

En realidad, su iterador es un generator , que es un objeto que tiene la capacidad de repetirse solo una vez.

type((i for i in range(5))) # says it''s type generator def another_generator(): yield 1 # the yield expression makes it a generator, not a function type(another_generator()) # also a generator

La razón por la cual son eficientes no tiene nada que ver con decirte lo que sigue "por referencia". Son eficientes porque solo generan el siguiente artículo a pedido; todos los artículos no se generan a la vez. De hecho, puedes tener un generador infinito:

def my_gen(): while True: yield 1 # again: yield means it is a generator, not a function for _ in my_gen(): print(_) # hit ctl+c to stop this infinite loop!

Algunas otras correcciones para ayudar a mejorar su comprensión:

  • El generador no es un puntero y no se comporta como un puntero, como es posible que esté familiarizado en otros idiomas.
  • Una de las diferencias con respecto a otros lenguajes: como se dijo anteriormente, cada resultado del generador se genera sobre la marcha. El siguiente resultado no se produce hasta que se solicite.
  • La combinación de palabras clave for in acepta un objeto iterable como su segundo argumento.
  • El objeto iterable puede ser un generador, como en su caso de ejemplo, pero también puede ser cualquier otro objeto iterable, como una list o dict , o un objeto str (cadena), o un tipo definido por el usuario que proporcione el funcionalidad.
  • La función iter se aplica al objeto para obtener un iterador (por cierto, no utilices iter como nombre de variable en Python, como lo has hecho, es una de las palabras clave). En realidad, para ser más precisos, se __iter__ método __iter__ del objeto (que es, en su mayor parte, toda la función iter hace de todos modos; __iter__ es uno de los llamados "métodos mágicos" de Python).
  • Si la llamada a __iter__ es exitosa, la función next() se aplica al objeto iterable una y otra vez, en un bucle, y la primera variable proporcionada para for se asigna al resultado de la función next() . (Recuerde: el objeto iterable podría ser un generador, o un iterador de un objeto contenedor, o cualquier otro objeto iterable). En realidad, para ser más precisos: llama al método __next__ del objeto iterador, que es otro "método mágico".
  • El bucle for finaliza cuando next() genera la excepción StopIteration (que generalmente ocurre cuando iterable no tiene otro objeto que ceder cuando se llama a next() ).

Puede implementar manualmente un bucle for en python de esta manera (probablemente no perfecto, pero lo suficientemente cerca):

try: temp = iterable.__iter__() except AttributeError(): raise TypeError("''{}'' object is not iterable".format(type(iterable).__name__)) else: while True: try: _ = temp.__next__() except StopIteration: break except AttributeError: raise TypeError("iter() returned non-iterator of type ''{}''".format(type(temp).__name__)) # this is the "body" of the for loop continue

No hay diferencia entre el código de ejemplo anterior y el anterior.

En realidad, la parte más interesante de un bucle for no es for , sino para in . Usar in en sí mismo produce un efecto diferente que for in , pero es muy útil para entender qué hace con sus argumentos, ya que for implementa un comportamiento muy similar.

  • Cuando se usa por sí mismo, la palabra clave in invoca primero el método __contains__ del objeto, que es otro "método mágico" (tenga en cuenta que este paso se omite al usar for in ). Usando in sí mismo en un contenedor, puede hacer cosas como esta:

    1 in [1, 2, 3] # True ''He'' in ''Hello'' # True 3 in range(10) # True ''eH'' in ''Hello''[::-1] # True

  • Si el objeto iterable NO es un contenedor (es decir, no tiene un método __contains__ ), in siguientes intentos llama al método __iter__ del objeto. Como se dijo anteriormente: el método __iter__ devuelve lo que se conoce en Python como un iterator . Básicamente, un iterador es un objeto que puede usar la función genérica incorporada next() en 1 . Un generador es solo un tipo de iterador.

  • Si la llamada a __iter__ es exitosa, la palabra clave in aplica la función next() al objeto iterable una y otra vez. (Recuerde: el objeto iterable podría ser un generador, un iterador de un objeto contenedor o cualquier otro objeto iterable). En realidad, para ser más precisos: llama al método __next__ del objeto iterador).
  • Si el objeto no tiene un método __iter__ para devolver un iterador, entonces vuelve al protocolo de iteración antiguo utilizando el método __getitem__ del objeto 2 .
  • Si todos los intentos anteriores fallan, obtendrá una excepción TypeError .

Si desea crear su propio tipo de objeto para iterar (es decir, puede usarlo for , o simplemente, sobre él), es útil conocer la palabra clave yield , que se usa en generators (como se mencionó anteriormente).

class MyIterable(): def __iter__(self): yield 1 m = MyIterable() for _ in m: print(_) # 1 1 in m # True

La presencia de yield convierte una función o método en un generador en lugar de una función / método regular. No necesita el método __next__ si usa un generador (trae __next__ junto con él automáticamente).

Si desea crear su propio tipo de objeto contenedor (es decir, puede usarlo solo, pero NO for ), solo necesita el método __contains__ .

class MyUselessContainer(): def __contains__(self, obj): return True m = MyUselessContainer() 1 in m # True ''Foo'' in m # True TypeError in m # True None in m # True

1 Tenga en cuenta que, para ser un iterador, un objeto debe implementar iterator . Esto solo significa que los métodos __next__ y __iter__ deben implementarse correctamente (los generadores vienen con esta funcionalidad "gratis", por lo que no tiene que preocuparse al usarlos). También tenga en cuenta que el método ___next__ es el next (sin guiones bajos) en Python 2 .

2 Consulte esta respuesta para conocer las diferentes formas de crear clases iterables.


Concepto 1

Todos los generadores son iteradores pero todos los iteradores no son generadores

Concepto 2

Un iterador es un objeto con un método siguiente (Python 2) o siguiente (Python 3).

Concepto 3

Citar desde wiki generator funciones de los generadores le permiten declarar una función que se comporta como un iterador, es decir, puede usarse en un ciclo for.

En tu caso

>>> it = (i for i in range(5)) >>> type(it) <type ''generator''> >>> callable(getattr(it, ''iter'', None)) False >>> callable(getattr(it, ''next'', None)) True


Extracto del libro de práctica de Python :

5. Iteradores y generadores

5.1. Iteradores

Usamos como enunciado para pasar por encima de una lista.

>>> for i in [1, 2, 3, 4]: ... print i, ... 1 2 3 4

Si lo usamos con una cadena, pasa por encima de sus caracteres.

>>> for c in "python": ... print c ... p y t h o n

Si lo usamos con un diccionario, pasa por encima de sus teclas.

>>> for k in {"x": 1, "y": 2}: ... print k ... y x

Si lo usamos con un archivo, pasa por encima de las líneas del archivo.

>>> for line in open("a.txt"): ... print line, ... first line second line

Entonces, hay muchos tipos de objetos que se pueden usar con un ciclo for. Estos se llaman objetos iterables.

Hay muchas funciones que consumen estos iterables.

>>> ",".join(["a", "b", "c"]) ''a,b,c'' >>> ",".join({"x": 1, "y": 2}) ''y,x'' >>> list("python") [''p'', ''y'', ''t'', ''h'', ''o'', ''n''] >>> list({"x": 1, "y": 2}) [''y'', ''x'']

5.1.1. El protocolo de iteración

La función incorporada iter toma un objeto iterable y devuelve un iterador.

>>> x = iter([1, 2, 3]) >>> x <listiterator object at 0x1004ca850> >>> x.next() 1 >>> x.next() 2 >>> x.next() 3 >>> x.next() Traceback (most recent call last): File "<stdin>", line 1, in <module>

StopIteration

Cada vez que llamamos al siguiente método en el iterador, nos da el siguiente elemento. Si no hay más elementos, se levanta una StopIteration.

Los iteradores se implementan como clases. Aquí hay un iterador que funciona como una función incorporada de xrange.

class yrange: def __init__(self, n): self.i = 0 self.n = n def __iter__(self): return self def next(self): if self.i < self.n: i = self.i self.i += 1 return i else: raise StopIteration()

El método iter es lo que hace que un objeto sea iterable. Detrás de escena, la función iter llama al método iter en el objeto dado.

El valor de retorno de iter es un iterador. Debería tener un siguiente método y elevar StopIteration cuando no hay más elementos.

Vamos a probarlo:

>>> y = yrange(3) >>> y.next() 0 >>> y.next() 1 >>> y.next() 2 >>> y.next() Traceback (most recent call last): File "<stdin>", line 1, in <module> File "<stdin>", line 14, in next

StopIteration

Muchas funciones incorporadas aceptan iteradores como argumentos.

>>> list(yrange(5)) [0, 1, 2, 3, 4] >>> sum(yrange(5)) 10

En el caso anterior, tanto el iterable como el iterador son el mismo objeto. Tenga en cuenta que el método iter se devolvió a sí mismo. No es necesario que sea el caso siempre.

class zrange: def __init__(self, n): self.n = n def __iter__(self): return zrange_iter(self.n) class zrange_iter: def __init__(self, n): self.i = 0 self.n = n def __iter__(self): # Iterators are iterables too. # Adding this functions to make them so. return self def next(self): if self.i < self.n: i = self.i self.i += 1 return i else: raise StopIteration()

Si tanto iteratable como iterator son el mismo objeto, se consume en una sola iteración.

>>> y = yrange(5) >>> list(y) [0, 1, 2, 3, 4] >>> list(y) [] >>> z = zrange(5) >>> list(z) [0, 1, 2, 3, 4] >>> list(z) [0, 1, 2, 3, 4]

5.2. Generadores

Los generadores simplifican la creación de iteradores. Un generador es una función que produce una secuencia de resultados en lugar de un solo valor.

def yrange(n): i = 0 while i < n: yield i i += 1

Cada vez que se ejecuta la instrucción de rendimiento, la función genera un nuevo valor.

>>> y = yrange(3) >>> y <generator object yrange at 0x401f30> >>> y.next() 0 >>> y.next() 1 >>> y.next() 2 >>> y.next() Traceback (most recent call last): File "<stdin>", line 1, in <module>

StopIteration

Entonces un generador también es un iterador. No tiene que preocuparse por el protocolo del iterador.

La palabra "generador" se usa confusamente para referirse tanto a la función que genera como a lo que genera. En este capítulo, usaré la palabra "generador" para referirme al objeto generado y "función del generador" para referirme a la función que lo genera.

¿Puedes pensar en cómo funciona internamente?

Cuando se llama a una función de generador, devuelve un objeto generador sin siquiera comenzar la ejecución de la función. Cuando se llama al siguiente método por primera vez, la función comienza a ejecutarse hasta que alcanza la declaración de rendimiento. El valor obtenido es devuelto por la próxima llamada.

El siguiente ejemplo demuestra la interacción entre el rendimiento y la llamada al siguiente método en el objeto generador.

>>> def foo(): ... print "begin" ... for i in range(3): ... print "before yield", i ... yield i ... print "after yield", i ... print "end" ... >>> f = foo() >>> f.next() begin before yield 0 0 >>> f.next() after yield 0 before yield 1 1 >>> f.next() after yield 1 before yield 2 2 >>> f.next() after yield 2 end Traceback (most recent call last): File "<stdin>", line 1, in <module>

StopIteration

Veamos un ejemplo:

def integers(): """Infinite sequence of integers.""" i = 1 while True: yield i i = i + 1 def squares(): for i in integers(): yield i * i def take(n, seq): """Returns first n values from the given sequence.""" seq = iter(seq) result = [] try: for i in range(n): result.append(seq.next()) except StopIteration: pass return result print take(5, squares()) # prints [1, 4, 9, 16, 25]