Aún no tienes acceso a esta clase

Crea una cuenta y continúa viendo este curso

Curso Profesional de Python

Curso Profesional de Python

Facundo García Martoni

Facundo García Martoni

Iteradores

12/21
Recursos

Aportes 64

Preguntas 10

Ordenar por:

¿Quieres ver más aportes, preguntas y respuestas de la comunidad? Crea una cuenta o inicia sesión.

Qué mágico momento fue el de la explicación del funcionamiento interno del ciclo for! Probablemente ha sido de mis momentos favoritos de la trilogía de FacMartoni

Estructuras de datos avanzadas

Iteradores

Antes de entender qué son los iteradores, primero debemos entender a los iterables.

Son todos aquellos objetos que podemos recorrer en un ciclo. Son aquellas estructuras de datos divisibles en elementos únicos que yo puedo recorrer en un ciclo.

Pero en Python las cosas no son así. Los iterables se convierten en iteradores.

Ejemplo:

# Creando un iterador

my_list = [1,2,3,4,5]
my_iter = iter(my_list)

# Iterando un iterador

print(next(my_iter))

# Cuando no quedan datos, la excepción StopIteration es elevada

# Creando un iterador

my_list = [1,2,3,4,5]
my_iter = iter(my_list)

# Iterando un iterador

while True: #ciclo infinito
  try:
    element = next(my_iter)
    print(element)
  except StopIteration:
    break

Momento impactante: El ciclo “for” dentro de Python, no existe. Es un while con StopIteration. 🤯🤯🤯

my_list = [1,2,3,4,5]

for element in my_list:
  print(element)

evenNumbers.py:

class EvenNumbers:
  """Clase que implementa un iterador de todos los números pares,
  o los números pares hasta un máximo
  """

  #* Constructor de la clase
  def __init__(self, max = None): #self hace referencia al objeto futuro que voy a crear con esta clase
    self.max = max


  # Método para tener elementos o atributos que voy a necesitar para que el iterador funcione
  def __iter__(self):
    self.num = 0 #Primer número par
    #* Convertir un iterable en un iterador
    return self

  # Método para tener la función "next" de Python
  def __next__(self):
    if not self.max or self.num <= self.max:
      result = self.num
      self.num += 2
      return result
    else:
      raise StopIteration

Ventajas de usar iteradores:

  1. Nos ahorra recursos.
  2. Ocupan poca memoria.


EL CLICLO FOR NO EXISTE

Iterables aquellas estructuras de datos divisibles en elementos únicos que se pueden recorrer en un ciclo.

Todos los iterables pueden convertirse en iteradores para poder ser recorridos en un ciclo.

¡El ciclo for no existe!

Es un alias de un ciclo While infinito con un Try except que controla el Error StopIteration

Construir un iterador desde cero en Python.

Solo tenemos que implementar los métodos

__iter__() :
	"El método __iter__() devuelve el objeto iterador en sí. Si es necesario, se puede realizar alguna inicialización."

__next__():
	"El  método __next__() debe devolver el siguiente elemento de la secuencia.Al llegar al final, y  debe subir el error StopIteration."

Ventajas:

Es una expresión que explica con obtener el resultado.
Por ello ahorra la memoria y aumenta la velocidad.

APUNTES de la clase 🐍

Los iteradores son estructuras de datos divisibles en elementos únicos que puedo recorrer en un ciclo.

  • Cuando hacemos un ciclo, python no está iterando(internamente). Lo que sucede es que ese iterable se convierte en un objeto especial el cual se llama iterador
my_list = [1,2,3]
my_iter= iter(my_list)
#iterando un iterador

print(next(my_iter))
#Cuándo no quedan datos la excepción StopIteration es elevada

  1. iter convierte un iterable en un iterador
  2. Una vez que se hace, se usa al next() para ir al siguiente elemento del iterador
  3. Cuando ya no quede ningún elemento python imprimirá un error
# creando un iterador
my_list = [1,2,3,4,5,6]
my_iter = iter(my_list)

#iterando un iterador
while True:
    try:
        print(next(my_iter))
        print(element)
    except StopIteration:
        break

Resultado de aplicar iter sobre nuestra lista y el ciclo while infinito.

  • si usamos while true hará un ciclo infinito

  • Usamos el manejo de errores. Con try usamos el next() para que ejecute el siguiente elemento

  • except para cuando devuelva el error StopIteration el ciclo se corta con un break

Ciclo for (azúcar sintáctica)

El ciclo for hace lo mismo que el cico while true. en vez de escribir todo ese código se puede resumir en:

for element in my_list:
    print(element)

el ciclo for no existe en python, lo que hace python es convertir una iterable en un iterador y con un ciclo while recorrerlo hasta encontrar el StopIteration

¿Cómo construyo un iterador?

El protocolo de los iteradores: para construir un iterador, hay que construir una clase que contenga dos métodos importantes:

__iter__() y __next__()
class EventNumber:
    
    """
    Clase que implementa un iterador de todos los números pares, o los números pares hasta un máximo."""

def __init__(self,max=None):
    self.max = max
def __iter__(self):
    self.num = 0
    return self

def __next__(self):
    if not self.max or self.num <= self.max:
        result= self.num
        self.num += 2
        return result

    else:
        raise StopIteration
  • __ iter __(): retorna al objeto al en sí mismo. Convierte un iterable en un iterador.

  • _ next __(): Convierte a la función next. Nos permite extraer cada uno de los elementos del iterador

Con los iteradores trabajamos más rápido y ahorramos memoria

ventajas de usar iteradores

Es una expresi+on que exlica como obtener el resultado. También ahorra memoria y es más rápido

Iterables -> Todos los objetos que podemos recorrer en un ciclo, ejem: una lista, un string
Iteradores -> Ahorra recursos, puedo almacenar secuencias y progreciones matematicas, ocupa poca memoria.

Iteradores

Son una estructura de datos para guardar datos infinitos.

Los iterables son los objetos que podemos recorrer a través de un ciclo dicho de otra manera, son estructuras de datos divisibles en elementos que puedo recorrer en un ciclo.

Todos los iterables pueden convertirse en iteradores. De esta manera es que internamente Python los puede recorrer realmente, esto mediante parsing usando el comando iter.

# Creando un iterador
my_list = [1, 2, 3, 4, 5]
my_iter = iter(my_list)

# Iterando un iterador
print(next(my_iter)) # Next nos permite acceder al siguiente elemento del iterador por cada llamada 

# Cuando no quedan datos, la excepción StopIteration es elevada

Si queremos crear un código que nos permita recorrer todos los elementos de nuestra lista usando la función next para como aparece en el ejemplo anterior, tendríamos que realizar un bloque try-except:

# Creando un iterador
my_list = [1, 2, 3, 4, 5]
my_iter = iter(my_list)

# Iterando un iterador
while True:
	try:
		element = next(my_iter)
		print(next(my_iter)) # Next nos permite acceder al siguiente elemento del iterador por cada llamada 
	except StopIteration:
		break                # Salimos del ciclo una vez que obtenemos el último valor iterable

Lo anterior es posible hacerlo de una manera mucho más sencilla, mediante el ciclo for el cual es azúcar sintáctica pues facilita y realiza de una manera mas estética y sencilla una operación:

my_list = [1, 2, 3, 4, 5]

for element in my_list
	print ("element")

Es posible crear un iterador personalizado (directamente, sin castear/casting) el cual nos permita recorrer un infinito número de elementos de acuerdo a una función dada, utiliza dos métodos internos importantes: “iter” y “next”.

El uso de una función que determina los valores a iterar nos permite ahorrar memoria y trabajar más rápido, pues no tenemos que almacenar cada uno de los valores, sino solo una función para extraer cada uno de los elementos.

El siguiente ejemplo crea un iterador que recorre todos los números pares:

class EvenNumbers:
  """Clase que implementa un iterador de todos los números pares,
  o los números pares hasta un máximo que definimos
  """

  # Constructor
  def __init__(self, max = None): # self = objeto futuro creado con esta clase
    self.max = max

  # Método para tener elementos o atributos que voy a necesitar para que el iterador funcione
  def __iter__(self):
    self.num = 0            # Primer número par
    return self             

  # Método para tener la función "next" del ciclo for, es decir, recorrer cada valor.
  def __next__(self):
    if not self.max or self.num <= self.max:
      result = self.num
      self.num += 2
      return result
    else:
      raise StopIteration

⭐️⭐️⭐️⭐️⭐️
El ciclo “for” dentro de Python no existe. Es realmente un while con StopIteration.
⭐️⭐️⭐️⭐️⭐️

Si lees esto, pasaste la mitad del curso !!
Animo, falta poco!!

Aprendimos geniales conceptos del lenguaje Python !!! Cada vez me agrada más… y que buen profesor!! para hacer entender estos temas… 🤠🤠

Los iteradores son todos aquellos elementos que podemos recorrer en un ciclo. Por ejemplo, una lista, un string o un diccionario.

Cuando se hace un ciclo, Python internamente no está recorriendo a ese iterable, es decir, el for no funciona directamente como lo vemos en la pantalla, sino que ese iterable se convierte a un objeto especial llamado Iterador. Y es este el que recorre el objeto.

Lo que hace Python internamente:

# Creando iterador
my_list = [1,2,3,4,5]
my_iter = iter(my_list)

# Iterando
print(next(my_iter))
# Cuando no quedan datos, lavanta la expeción StopIteration

Una forma de iterar en un conjunto muy grande:

Un ciclo for no es más que azúcar sintáctica de este proceso. El ciclo for realmente es solo un alias de una iteración.

my_list = [1,2,3,4,5]
my_iter = iter(my_list)

while True:
	try:
		element = next(my_iter)
		print(element)
	except StopIteration:
		break

Construir iterador

El protocolo de los iteradores nos dice que para construir un iterador se necesita una clase que contenga dos métodos: el método iter y el método next .

class EvenNumbers:
    """Clase que implementa un iterador
    de todos los números pares o los números
    pares hasta un máximo"""

    def __init__(self, max = None) -> None:
        self.max = max

    def __iter__(self):
        self.num = 0
        return self

    def __next__(self):
        if not self.max or self.num <= self.max:
            result = self.num
            self.num += 2
            return result
        else:
            raise StopIteration

El método iter sirve para tener elementos o atributos que se van a necesitar para que el iterador funcione. En este caso, el único atributo necesario son cada uno de los números de esa iteración. Se le asigna el valor inicial de 0 y se retorna al objeto en sí mismo.

El método next es el que se necesita para tener la función next en Python. Este método permite extraer cada uno de los elementos de iterador. En este caso se coloca la condición “si no se definió un self.max (limite) o el número que se está por recorrer es menor o igual a self.max, se le asigna a la variable result el número de la iteración (self.num). Después se le suma dos a self.num y se retorna. Si la condición se cumple se levanta la excepción Stop Iteration”.

Ventajas de usar iteradores

  • Ahorra espacio al poder almacenar secuencias matemáticas infinitas de forma teórica.
  • Ocupan poca memoria.

Iteradores

  • Un iterator es una estructura de datos para guardar datos infinitos 🤯. Para entenderlo, primero debemos saber que un iterable es todo aquel objeto que puedo recorrer en un ciclo (lista, strings, etc). Un iterable es divisible.

  • Cuando hacemos un ciclo, Python internamente no está recorriendo a ese iterable, si no más bien ese iterable se convierte internamente en un iterador, que si puede ser recorrido. 🤔

  • Para crear un iterador:

    # Creando un iterador
    my_list = [1, 2, 3, 4, 5]
    my_iter = iter(my_list)  # Se recibe un iterable
    
    # Iterando un iterador
    print(next(my_iter))
    # Cuando no quedan datos, la excepción StopIteration es elevada
    
  • Para que no se rompa el código, hacemos manejo de errores: 🧠

    # Creando un iterador
    my_list = [1, 2, 3, 4, 5]
    my_iter = iter(my_list)
    
    # Iterando un iterador
    while True: 
    	try: 
    		element = next(my_iter)
    		print(element)
    	except StopIteration:
    		break
    

    Esta es una manera eficiente de extraer todos los elementos de un iterable. De hecho, esta es la manera en la que funciona un ciclo for, es la azúcar sintaxis de este código anterior 😍. El ciclo for en si mismo no existe.

  • ¿Cómo construyo un iterador?. Una opción es castear desde un iterable. Para hacerlo desde cero, debemos usar el protocolo de los iteradores que contiene dos clases importantes __iter__ y __next__:

    class EvenNumbers:
    	"""Calse que implementa un iterador de todos los
    		 números pares, o los números pares hasta un máximo"""
    	def __init__(self, max = None):
    		self.max = max
    
    	def __iter__(self):
    		self.num = 0
    		return self
    
    	def __next__(self):
    		if not self.max or self.num <= self.max:
    			result = self.num
    			self.num += 2
    			return self
    		else:
    			raise StopIteration
    
  • Las ventajas de usar iteradores: Nos ahorra recursos computacionales y de memoria, ya que tenemos una función matemática de como obtener los siguientes elementos, sin necesidad de guardarlos todos 👀.

Por si alguno se pregunta como usar clases para construir iteradores, aquí va:

Primero se define la clase con sus 3 métodos fundamentales

class EvenNumbers:
  """Clase que implementa un iterador de todos los números pares,
  o los números pares hasta un máximo
  """

  #* Constructor de la clase
  def __init__(self, max = None): #self hace referencia al objeto futuro que voy a crear con esta clase
    self.max = max


  # Método para tener elementos o atributos que voy a necesitar para que el iterador funcione
  def __iter__(self):
    self.num = 0 #Primer número par
    #* Convertir un iterable en un iterador
    return self

  # Método para tener la función "next" de Python
  def __next__(self):
    if not self.max or self.num <= self.max:
      result = self.num
      self.num += 2
      return result
    else:
      raise StopIteration 

Luego, en nuestro archivo principal, importamos la clase creada con el iterador

from EvenNumbers import EvenNumbers

n = EvenNumbers(18) #Instaciamos un objeto

myIter = n.__iter__()  #Aplicamos el método __iter__

while True: #ciclo infinito #Utilizamos nuestro for not for
  try:
    element = n.__next__()  #Llamamos al método __next__
    print(element)
  except StopIteration:
    break

For no existe, he vivido en una mentira, noooooooooo, que bueno que tienes que ver dos cursos antes de esta revelación sino te volves [email protected]

Una forma de aplicar la función que el profesor explica con el while infinito es hacer una llamada recursiva a la misma función siempre que no tengamos un error al intentar next().

def loop_iterador(iterador):
    iterador = iter(iterador)
    try:
        print(next(iterador))
    except StopIteration:
        print('Fin de los elementos del iterador')
    else:
        return loop_iterador(iterador)

Les comparto como aplicando el concepto de iteradores realice el calculo del número e a través de una suma infinita.

class NumESum():
    def __init__(self, iterations: int):
        self.iterations = iterations
    
    def __iter__(self):
        self.fact = 1
        self.sum = 0
        if self.iterations <= 0:
           self.iterations = -1 
        self.k = 1
        return self

    def __next__(self):
        self.sum+=self.k**2/(2*self.fact) # sum(k^2/(2*k!)) from k = 1 to ∞
        if self.k > self.iterations:
            raise StopIteration
        else:
            self.k += 1
            self.fact=self.fact*self.k
            return self.sum

if __name__ == "__main__":
    for element in NumESum(100): # Ejecutar el algoritmo con 100 interacciones.
        print(element)

Plantee otra solución para iterar n elementos.
Con la ayuda de una función recursiva y un decorador.

def iterador(func):
	def wrapper(lista):
		lista = iter(lista)
		func(lista)
	return wrapper

@iterador
def iterame(lista):
	try:
		print(next(lista))
		return iterame(lista)
	except StopIteration:
		pass

iterame('Platzi<3')

raise sin ningún argumento es un uso especial de la sintaxis de python. Significa obtener la excepción y volver a subirla.

Una estructura de datos para guardar datos infinitos.

Un iterable son todos los objetos que podemos recorrer en un ciclo, ejemplo, una lista a la cual puedo acceder a sus elementos con un ciclo for.

# creando un iterador

my_list = [1,2,3,4,5]
my_iter = iter(my_list)

# iterando un iterador

print(next(my_iter))

# cuando no quedan datos, la excepción StopIteration es elevada

Esta sería la forma de como trabajaría el iterador:

# creando un iterador

my_list = [1,2,3,4,5]
my_iter = iter(my_list)

# iterando un iterador

while True:
		try:
				element = next(my_iter)
				print(element)
		except StopIteration:
				break

Ya conocemos el azúcar sintáctico para evitar código complejo y ayudarnos con:

for element in my_list:
print(elment)

Cómo construyo un iterador?

Para construir un iterador se necesita saber del protocolo de los iteradores.

El protocolo de los iteradores dice que para construir un iterador tenemos que tener una clase qeu contenga dos metodos importantes __iter__ y __next__.

class EvenNumbers:
		"""Clase que implementa un iterador de todos los números pares, 
		o los números pares hasta un máximo"""
		
		def __init__(self, max=None):
				self.max = max

		def __iter__(self):
				self.num = 0
				return self

		def __next__(self):
				if not self.max or self.num <= self.max:
						result = self.num
						self.num += 2
						return result
				else:
						raise StopIteration		

Ventajas de usar los iteradores:

  • Ahorra recursos
  • Puedo almacenar secuencias y progresiones matemáticas
  • Ocupa poca memoria

Probe con el modulo random para elegir un numero entre 1 y el que seleccione el usuario , esto me da la direfencia de tiempo de ejecucion sobre la base de numeros analizados para elegir el random:

# choose a random element from a list
from random import seed
from random import choice
from datetime import datetime

def execution_time(func):  #funcion para determinar el tiempo que una funcion tarda en ejecutarse
    def wrapper(*args, **kwargs):
        initial_time = datetime.now()  #devuelve fecha z hora exactas de cuando se ejecuta la linea
        func(*args, **kwargs) #no importa la cantidad de argumentos poscionales o nombrados, la funcion los va a recibir.
        final_time = datetime.now()
        time_elapsed = final_time - initial_time
        print("pasaron "+ str(time_elapsed.total_seconds()) + " segundos")
    return wrapper


def random_num(n: int):
    sequence = [i for i in range(n)]   
    selection = choice(sequence)
    return(selection)

@execution_time
def run():
    print(random_num(int(input("Un numero entre 1 y ....?: "))))


if __name__ == '__main__':
  • Los iterables son aquellos objetos que se pueden recorrer en un cliclo.
  • Para generar un nuevo iterable se necesita una clase y dos métodos iter y next.
  • Cuando un iterable llega al máximo de elementos se lanza el error StopIteration.
  • Los iterables ahorra recursos, ya que en lugar de guardar los datos de una secuencia, guardan la función matemática que genera dicha secuencia.

TSSSSSSSAAAAAAAAAAALAAAAAA que no existen los for jajajajaja 🤯🤯🤯

Porque dice dandar iter y dandar next el profesor. 😦

Demasiado pro la manera de explicar, Facundo.

Lit

Este video puede ayudar como repaso de POO
https://youtu.be/VYXdpjCZojA

class EvenNumbers:
    """Clase que implementa un iterador
    de todos los numeros pares, o los numeros pares
    hasta un maximo dado por el usuario"""

    def __init__(self, max=None):
        self.max = max
        
    def __iter__(self):
        self.num = 0
        return self

    def __next__(self):
        if not self.max or self.num <= self.max:
            num = self.num
            self.num += 2
            return num
        else:
            raise StopIteration

for i in EvenNumbers(11):
    print(i)

VENTAJAS DE USAR ITERADORES

  • Ahorra recursos.
  • Ocupan poca memoria.
  • Se trabaja más rápido.

Sucesión de Fibonacci en una lista hasta un valo máximo indicado:

import os

class FiboIter():
    number=int(input('enter a number as maximum elemnent of Fibonacci succession : ' ))

    def __init__(self,max=number):
        self.max = max

    def __iter__(self):
        self.n1 = 0
        self.n2 = 1
        self.counter = 0
        return self
            
    def __next__(self):
        
        if self.counter== 0:
            self.next = []
            self.counter +=1
            self.next.append(self.n1)                
            return ' '
        elif self.counter == 1:
            self.counter +=1
            self.next.append(self.n2)            
            return ' '
        else:
            # try:
            
                self.aux = self.n1 + self.n2 
                if self.aux <= self.max:  
                        # self.n1 = self.n2
                        # self.n2 = self.aux
                    self.n1, self.n2=self.n2,self.aux
                    self.counter +=1
                    self.next.append(self.aux)
                    # return self.next 
                    return ' '
                else:
                    os.system('clear')
                    raise StopIteration(print(f'Finonacci Succession:\n{self.next}\nStop Iteration Error:  There is not an element higher than {self.max}'))
                
          

if __name__ == '__main__':
    fibonacci= FiboIter()
    for element in fibonacci:
        print(element)

woooo esto esta genial

Protocolo de los iteradores

Fact:

También puedes sacar los valores iterables de esta manera

StopIteration

Bueno, ejecutando el iterador creado, por medio de un for.

for num in EvenNumbers(10):
    print(num)

MI código de Fibo con limite de 99999:

import time

class FiboIter():

    def __iter__(self, max=99999):
        self.max = max
        self.n1 = 0 #Primer numero de la sucesión
        self.n2 = 1 #Segundo número de la sucesión
        self.counter = 0
        return self

    def __next__(self):
        if self.counter == 0: #Si estamos en el primer elemento del iterador
            self.counter += 1
            return self.n1
        elif self.counter == 1: #Segunda vuelta del iterador
            self.counter += 1
            return self.n2
        elif not self.max or self.n2 <=self.max :
            #Crear atributo auxiliar, que es igual a la suma de los
            #dos números anteriores de la sucesion
            self.aux = self.n1 + self.n2

            #Reasignar valores
            #self.n1 = self.n2
            #self.n2 = self.aux

            #Que también se puedde hacer con SWAP
            self.n1, self.n2 = self.n2, self.aux
            self.counter += 1
            return self.aux
        else:
            raise StopIteration

if __name__ == '__main__':
    #Instanciamos la clase FiboIter() a una variable para poder crear objetos.
    fibonacci = FiboIter() #En este punto se crea __iter__

    for element in fibonacci: #se ejecuta __next__
        print(element)
        time.sleep(0.025) #Pausar 0.05 segundos para poder ver más claramente la sucesión.

Deberías de usar el cursor para señalar lo que estás explicando, así no se distingue de que estás hablando

6:07 🤯

Por lo que veo en los comentarios, hay un gran asombro por lo explicado respecto a que el ciclo for no es más que un alias para un while más complejo. Sin embargo, a pesar de ser una buena explicación como método pedagógico, se encuentra lejos de la realidad. De hecho, claro que existen los ciclos for en Python y no son azúcar sintáctica para un while del mismo lenguaje. En Python, los ciclos for son transpilados a lenguaje C (cosa que no ocurre con los ciclos while), por lo que son mucho más eficientes que un ciclo while. Una explicación simple puede ser encontrada en este video de Youtube. Y pueden probar el siguiente código en sus máquinas, viendo que el ciclo for es bastante más rápido que el while (implicando que no puede ser solo azúcar sintáctica).

# for.py
from timeit import default_timer as timer
from datetime import timedelta


def main():
    start = timer()
    for i in range(100000000):
        pass
    end = timer()
    print(timedelta(seconds=end - start))


if __name__ == "__main__":
    main()
# while_iter.py
from timeit import default_timer as timer
from datetime import timedelta

start = timer()


def main():
    my_list = range(100000000)
    my_iter = iter(my_list)

    start = timer()
    while True:
        try:
            element = next(my_iter)
        except StopIteration:
            break
    end = timer()
    print(timedelta(seconds=end - start))


if __name__ == "__main__":
    main()
# while.py
from timeit import default_timer as timer
from datetime import timedelta

start = timer()


def main():
    start = timer()
    i = 0
    while i < 100000000:
        i += 1
    end = timer()
    print(timedelta(seconds=end - start))


if __name__ == "__main__":
    main()

✨Como aplicación del concepto de iteradores hice un rango para floats.

class frange():
    """frange(start, stop, step) -> frange object

    Iterates over a range of floats from start(inclusive) to stop(exclusive) by step."""

    def __init__(self, *args):
        if len(args) == 3:
            self.start = args[0]
            self.stop = args[1]
            self.step = args[2]
        elif len(args) == 2:
            self.start = args[0]
            self.stop = args[1]
            self.step = 0.5
        elif len(args) == 1:
            self.start = 0
            self.stop = args[0]
            self.step = 0.5

    def __iter__(self):
        self.__allowed = True
        if self.start < self.stop and self.step < 0:
            self.__allowed = False
        elif self.start > self.stop and self.step > 0:
            self.__allowed = False
        elif self.start == self.stop:
            self.__allowed = False
        elif self.step == 0:
            self.__allowed = False
        self.__current = self.start*1
        return self

    def __next__(self):
        if self.__allowed:
            if self.step > 0:
                if self.__current + self.step <= self.stop:
                    self.__aux = self.__current*1
                    self.__current += self.step
                    if self.__aux == self.start:
                        return self.start
                    else:
                        return self.__aux
                else:
                    raise StopIteration
            else:
                if self.__current + self.step >= self.stop:
                    self.__aux = self.__current*1
                    self.__current += self.step
                    if self.__aux == self.start:
                        return self.start
                    else:
                        return self.__aux
                else:
                    raise StopIteration
        else:
            raise StopIteration

En minuto 12:41 hubo un raise de la voz del profe. (se acabaron las iteraciones a partir de aquí 😦 )

en Python, los iteradores tienen que implementar un método next que debe devolver los elementos, de a uno por vez, comenzando por el primero. Y al llegar al final de la estructura, debe levantar una excepción de tipo StopIteration.

Es decir que las siguientes estructuras son equivalentes

for elemento in secuencia:
# hacer algo con elemento

iterador = iter(secuencia)
while True:
try:
elemento = iterador.next()
except StopIteration:
break
# hacer algo con elemento

En particular, si queremos implementar un iterador para la lista enlazada, la mejor solución implica crear una nueva clase, _IteradorListaEnlazada, que implemente el método next() de la forma apropiada.

Advertencia
Utilizamos la notación de clase privada, utilizada también para la clase _Nodo, ya que si bien se devolverá el iterador cuando sea necesario, un programador externo no debería construir el iterador sin pasar a través de la lista enlazada.

Para inicializar la clase, lo único que se necesita es una referencia al primer elemento de la lista.

class _IteradorListaEnlazada(object):
" Iterador para la clase ListaEnlazada “
def init(self, prim):
”"" Constructor del iterador.
prim es el primer elemento de la lista. “”"
self.actual = prim

A partir de allí, el iterador irá avanzando a través de los elementos de la lista mediante el método next. Para verificar que no se haya llegado al final de la lista, se corroborará que la referencia self.actual sea distinta de None.

if self.actual == None:
raise StopIteration(“No hay más elementos en la lista”)

Una vez que se pasó la verificación, la primera llamada a next debe devolver el primer elemento, pero también debe avanzar, para que la siguiente llamada devuelva el siguiente elemento. Por ello, se utiliza la estructura guardar, avanzar, devolver.

Guarda el dato

dato = self.actual.dato

Avanza en la lista

self.actual = self.actual.prox

Devuelve el dato

return dato

En el Código 16.4 se puede ver el código completo del iterador.

Código 16.4: _IteradorListaEnlazada: Un iterador para la lista enlazada

class _IteradorListaEnlazada(object):
" Iterador para la clase ListaEnlazada “
def init(self, prim):
”"" Constructor del iterador.
prim es el primer elemento de la lista. “”"
self.actual = prim

def next(self):
    """ Devuelve uno a uno los elementos de la lista. """
    if self.actual == None:
        raise StopIteration("No hay más elementos en la lista")

    # Guarda el dato
    dato = self.actual.dato
    # Avanza en la lista
    self.actual = self.actual.prox
    # Devuelve el dato
    return dato

Finalmente, una vez que se tiene el iterador implementado, es necesario modificar la clase ListaEnlazada para que devuelva el iterador cuando se llama al método iter.

def iter(self):
" Devuelve el iterador de la lista. "
return _IteradorListaEnlazada(self.prim)

Con todo esto será posible recorrer nuestra lista con la estructura a la que estamos acostumbrados.

l = ListaEnlazada()
l.append(1)
l.append(3)
l.append(5)
for valor in l:
… print valor
…
1
3
5

Curso muy bueno y profesor 10/10.

#Ejercicio para poner a prueba el concepto de decorador
#programa para decorar listas, si el elemento es un entero lo multiplicamos por 2
#Si es un string lo pasamos a mayuscula

def decorator(function):
def wrapper(lista):
j=0
for i in lista:
if type(i)==str:
lista[j]=i.upper()
elif type(i)==int:
lista[j]=i*2
j+=1
return function(lista)
return wrapper

@decorator
def lista_decorada(lista):
return lista

lista=[5,“herman”,8,9,“hola”]

print(lista_decorada(lista))

Entendido, a practicar

Con esta clase pude entender mucho mejor el ciclo for. Admito que quedé impresionado cuando explicó todo el funcionamiento interno jaja. ¡Brutal!

Los iteradores son eficientes porque en vez de almacenar datos saben como generar el siguiente, en pocas palabras no necesitan almacenar toda la información pues saben como construirla mediante las instrucciones que se le hayan dado

Eso del ciclo for me dejo sorprendido de las cosas que uno aprende en Platzi con estos geniales cursos

🤯

Ahora a ver el curso de POO

Queeeeeeeeeeeeeeeeeeeeeeeeeeee no existe el FOR en python

Aquí una explicación que también podría ayudar iteradores

🤯

el ciclo for como tal no existe es un alias del while

Si lees esto, pasaste la mitad del curso!!
Animo, falta poco!!

iteradores:
Una estructura de datos para guardar datos infinitos

Me ha gustador mucho esta clase, se comprende lo que significa la exploración de Python a fondo.

Los ciclos for son los papás.

Esto de los metodos iter y next es todo un negocio del cruel del imperialismo.

Metodo __next__

>>> 🟢 JPEG is also a mathematical formula 📝

ame esta clase!

mi solución

import time

def fibo_gen(stop = None):
   n1 = 0
   n2 = 1
   counter = 0
   
   while True:
      if stop == counter:
         break
      if counter == 0:
         yield n1
      elif counter == 1:
         yield n2
      else:
         aux = n1 + n2
         n1, n2 = n2, aux
         yield aux
      counter += 1


if __name__ == "__main__":
   fibonacci = fibo_gen()
   for element in fibonacci:
      print(element)
      time.sleep(0.5)