Introducción
Esta sección examina cómo se puede personalizar la iteración mediante una función generadora.
This tutorial is from open-source community. Access the source code
💡 Este tutorial está traducido por IA desde la versión en inglés. Para ver la versión original, puedes hacer clic aquí
Esta sección examina cómo se puede personalizar la iteración mediante una función generadora.
Supongamos que desea crear su propio patrón de iteración personalizado.
Por ejemplo, una cuenta atrás.
>>> for x in countdown(10):
... print(x, end=' ')
...
10 9 8 7 6 5 4 3 2 1
>>>
Hay una manera fácil de hacer esto.
Un generador es una función que define una iteración.
def countdown(n):
while n > 0:
yield n
n -= 1
Por ejemplo:
>>> for x in countdown(10):
... print(x, end=' ')
...
10 9 8 7 6 5 4 3 2 1
>>>
Un generador es cualquier función que utiliza la declaración yield
.
El comportamiento de los generadores es diferente al de una función normal. Llamar a una función generadora crea un objeto generador. No ejecuta inmediatamente la función.
def countdown(n):
## Added a print statement
print('Counting down from', n)
while n > 0:
yield n
n -= 1
>>> x = countdown(10)
## There is NO PRINT STATEMENT
>>> x
## x is a generator object
<generator object at 0x58490>
>>>
La función solo se ejecuta en la llamada a __next__()
.
>>> x = countdown(10)
>>> x
<generator object at 0x58490>
>>> x.__next__()
Counting down from 10
10
>>>
yield
produce un valor, pero suspende la ejecución de la función. La función se reanuda en la siguiente llamada a __next__()
.
>>> x.__next__()
9
>>> x.__next__()
8
Cuando el generador finalmente devuelve, la iteración lanza un error.
>>> x.__next__()
1
>>> x.__next__()
Traceback (most recent call last):
File "<stdin>", line 1, in? StopIteration
>>>
Observación: Una función generadora implementa el mismo protocolo de bajo nivel que utiliza la instrucción for en listas, tuplas, diccionarios, archivos, etc.
Si alguna vez se encuentra deseando personalizar la iteración, siempre debe pensar en funciones generadoras. Son fáciles de escribir: cree una función que realice la lógica de iteración deseada y use yield
para emitir valores.
Por ejemplo, pruebe este generador que busca en un archivo líneas que contengan una subcadena coincidente:
>>> def filematch(filename, substr):
with open(filename, 'r') as f:
for line in f:
if substr in line:
yield line
>>> for line in open('portfolio.csv'):
print(line, end='')
name,shares,price
"AA",100,32.20
"IBM",50,91.10
"CAT",150,83.44
"MSFT",200,51.23
"GE",95,40.37
"MSFT",50,65.10
"IBM",100,70.44
>>> for line in filematch('portfolio.csv', 'IBM'):
print(line, end='')
"IBM",50,91.10
"IBM",100,70.44
>>>
Esto es un poco interesante: la idea de que se puede ocultar un montón de procesamiento personalizado en una función y usarlo para alimentar un bucle for
. El siguiente ejemplo examina un caso más inusual.
Los generadores pueden ser una manera interesante de monitorear fuentes de datos en tiempo real, como archivos de registro o flujos de mercado de valores. En esta parte, exploraremos esta idea. Para comenzar, siga detenidamente las siguientes instrucciones.
El programa stocksim.py
es un programa que simula datos del mercado de valores. Como salida, el programa escribe constantemente datos en tiempo real en un archivo stocklog.csv
. En una ventana de comando separada, vaya al directorio correspondiente y ejecute este programa:
$ python3 stocksim.py
Si está en Windows, simplemente localice el programa stocksim.py
y haga doble clic en él para ejecutarlo. Ahora, olvide este programa (simplemente déjelo en ejecución). Usando otra ventana, vea el archivo stocklog.csv
que está siendo escrito por el simulador. Debería ver nuevas líneas de texto agregadas al archivo cada pocos segundos. Una vez más, simplemente déjelo ejecutar en segundo plano: se ejecutará durante varias horas (no debe preocuparse por ello).
Una vez que el programa anterior está en ejecución, escribamos un pequeño programa para abrir el archivo, buscar hasta el final y esperar a nuevos datos. Cree un archivo follow.py
y ponga este código en él:
## follow.py
import os
import time
f = open('stocklog.csv')
f.seek(0, os.SEEK_END) ## Mueva el puntero del archivo 0 bytes desde el final del archivo
while True:
line = f.readline()
if line == '':
time.sleep(0.1) ## Duerma brevemente y vuelva a intentarlo
continue
fields = line.split(',')
name = fields[0].strip('"')
price = float(fields[1])
change = float(fields[4])
if change < 0:
print(f'{name:>10s} {price:>10.2f} {change:>10.2f}')
Si ejecuta el programa, verá un cotizador de valores en tiempo real. En el fondo, este código es similar al comando Unix tail -f
que se utiliza para monitorear un archivo de registro.
Nota: El uso del método readline()
en este ejemplo es un poco inusual en el sentido de que no es la forma habitual de leer líneas de un archivo (normalmente se usaría un bucle for
). Sin embargo, en este caso, lo estamos usando para examinar repetidamente el final del archivo para ver si se han agregado más datos (readline()
devolverá ya sea nuevos datos o una cadena vacía).
Si se examina el código del Ejercicio 6.5, la primera parte del código está produciendo líneas de datos mientras que las declaraciones al final del bucle while
están consumiendo los datos. Una característica principal de las funciones generadoras es que se puede mover todo el código de producción de datos a una función reutilizable.
Modifique el código del Ejercicio 6.5 de modo que la lectura del archivo se realice mediante una función generadora follow(filename)
. Haga que el siguiente código funcione:
>>> for line in follow('stocklog.csv'):
print(line, end='')
... Should see lines of output produced here...
Modifique el código del cotizador de valores para que se vea así:
if __name__ == '__main__':
for line in follow('stocklog.csv'):
fields = line.split(',')
name = fields[0].strip('"')
price = float(fields[1])
change = float(fields[4])
if change < 0:
print(f'{name:>10s} {price:>10.2f} {change:>10.2f}')
Modifique el programa follow.py
de modo que observe el flujo de datos de las acciones y imprima un cotizador que muestre información solo para aquellas acciones que se encuentran en una cartera. Por ejemplo:
if __name__ == '__main__':
import report
portfolio = report.read_portfolio('portfolio.csv')
for line in follow('stocklog.csv'):
fields = line.split(',')
name = fields[0].strip('"')
price = float(fields[1])
change = float(fields[4])
if name in portfolio:
print(f'{name:>10s} {price:>10.2f} {change:>10.2f}')
Nota: Para que esto funcione, su clase Portfolio
debe admitir el operador in
. Consulte el Ejercicio 6.3 y asegúrese de implementar el operador __contains__()
.
Acaba de suceder algo muy poderoso aquí. Se trasladó un patrón de iteración interesante (leer líneas al final de un archivo) a su propia pequeña función. La función follow()
ahora es una utilidad completamente general que se puede usar en cualquier programa. Por ejemplo, se podría usar para monitorear registros de servidor, registros de depuración y otras fuentes de datos similares. Eso es bastante genial.
¡Felicitaciones! Has completado el laboratorio de Personalización de Iteración. Puedes practicar más laboratorios en LabEx para mejorar tus habilidades.