Saltar a contenido

8. Generadores / Iteradores

Un generador de Python es un fragmento de código especializado capaz de producir una serie de valores y controlar el proceso de iteración. Esta es la razón por la cual los generadores a menudo se llaman iteradores,

El protocolo iterador es una forma en que un objeto debe comportarse para ajustarse a las reglas impuestas por el contexto de las sentencias for e in. un objeto conforme al protocolo iterador se llama iterador.

Un iterador debe proporcionar dos métodos:

__iter__() el cual debe devolver el objeto en sí y que se invoca una vez (es necesario para que Python inicie con éxito la iteración).

__next__() el cual debe devolver el siguiente valor (primero, segundo, etc.) de la serie deseada: será invocado por las sentencias for/in para pasar a la siguiente iteración; si no hay más valores a proporcionar, el método deberá generar la excepción StopIteration cuando la iteración llega a su fin.

class Fib:
    def __init__(self, nn):
        self.__n = nn
        self.__i = 0
        self.__p1 = self.__p2 = 1

    def __iter__(self):
        print("Fib iter")
        return self

    def __next__(self):
        self.__i += 1
        if self.__i > self.__n:
            raise StopIteration
        if self.__i in [1, 2]:
            return 1
        ret = self.__p1 + self.__p2
        self.__p1, self.__p2 = self.__p2, ret
        return ret


class Class:
    def __init__(self, n):
        self.__iter = Fib(n)

    def __iter__(self):
        print("Class iter")
        return self.__iter


object = Class(8)

for i in object:
    print(i)

Yield

El protocolo iterador no es difícil de entender y usar, pero también es indiscutible que el protocolo es bastante inconveniente.

La principal molestia que tiene es que necesita guardar el estado de la iteración en las invocaciones subsecuentes de __iter__.

La sentencia yield solo puede ser utilizada dentro de funciones.

La sentencia yield suspende la ejecución de la función y hace que la función regrese el argumento de yield como resultado. Esta función no puede invocarse de forma regular, su único propósito es ser utilizada como un generador (es decir, en un contexto que requiera una serie de valores, como un bucle for).

def fun(n):
    for i in range(n):
        yield i


for v in fun(5):
    print(v)

Expresiones condicionales

Una expresión condicional es una expresión construida usando el operador if-else. Por ejemplo:

print(True if 0 >= 0 else False)

Construir tu propio generador

def powers_of_2(n):
    power = 1
    for i in range(n):
        yield power
        power *= 2


for v in powers_of_2(8):
    print(v)

Usar generador con listas por comprensión

Una lista por comprensión se convierte en un generador cuando se emplea dentro de paréntesis (usado entre corchetes, produce una lista regular). Por ejemplo:

for x in (el * 2 for el in range(5)):
    print(x)
def powers_of_2(n):
    power = 1
    for i in range(n):
        yield power
        power *= 2


t = [x for x in powers_of_2(5)]
print(t)

Una forma de seleccionar uno de dos valores diferentes en función del resultado de una expresión Booleana:

expresión_uno if condición else expresión_dos

the_list = []

for x in range(10):
    the_list.append(1 if x % 2 == 0 else 0)

print(the_list)
the_list = [1 if x % 2 == 0 else 0 for x in range(10)]

print(the_list)

Lambda

Una función lambda es una herramienta para crear funciones anónimas. Por ejemplo:

def foo(x, f):
    return f(x)

print(foo(9, lambda x: x ** 0.5))

Una función lambda es una función sin nombre (también puedes llamarla una función anónima).

lambda parámetros: expresión

two = lambda: 2
sqr = lambda x: x * x
pwr = lambda x, y: x ** y

for a in range(-2, 3):
    print(sqr(a), end=" ")
    print(pwr(a, two()))

Map

La función map() aplica la función pasada por su primer argumento a todos los elementos de su segundo argumento y devuelve un iterador que entrega todos los resultados de funciones subsequentes.

short_list = ['mython', 'python', 'fell', 'on', 'the', 'floor']
new_list = list(map(lambda s: s.title(), short_list))
print(new_list)
list_1 = [x for x in range(5)]
list_2 = list(map(lambda x: 2 ** x, list_1))
print(list_2)

for x in map(lambda x: x * x, list_2):
    print(x, end=' ')
print()

Filter

Espera el mismo tipo de argumentos que map(), pero hace algo diferente: filtra su segundo argumento mientras es guiado por direcciones que fluyen desde la función especificada en el primer argumento (la función se invoca para cada elemento de la lista, al igual que en map() ).

Los elementos que devuelven True de la función pasan el filtro, los otros son rechazados.

from random import seed, randint

seed()
data = [randint(-10,10) for x in range(5)]
filtered = list(filter(lambda x: x > 0 and x % 2 == 0, data))

print(data)
print(filtered)
short_list = [1, "Python", -1, "Monty"]
new_list = list(filter(lambda s: isinstance(s, str), short_list))
print(new_list)

Cierres

Un cierre es una técnica que permite almacenar valores a pesar de que el contexto en el que han sido creados no existe más. Por ejemplo:

def tag(tg):
    tg2 = tg
    tg2 = tg[0] + '/' + tg[1:]

    def inner(str):
        return tg + str + tg2
    return inner


b_tag = tag('<b>')
print(b_tag('Monty Python'))

Última actualización: 2022-11-08