python - statement - ¿Para qué es bueno el generador.throw()?
python del statement (3)
PEP 342 (Coroutines a través de Generadores Mejorados) agregó un método throw()
a los objetos generadores, lo que permite a la persona que llama generar una excepción dentro del generador (como si fuera lanzada por la expresión de yield
).
Me pregunto cuáles son los casos de uso para esta característica.
Digamos que uso un generador para manejar la adición de información a una base de datos; Utilizo esto para almacenar información recibida de la red, y al usar un generador, puedo hacer esto de manera eficiente siempre que recibo datos, y hago otras cosas de otra manera.
Entonces, mi generador abre primero una conexión de base de datos, y cada vez que le envíes algo, agregará una fila:
def add_to_database(connection_string):
db = mydatabaselibrary.connect(connection_string)
cursor = db.cursor()
while True:
row = yield
cursor.execute(''INSERT INTO mytable VALUES(?, ?, ?)'', row)
Eso está todo bien y bien; Cada vez que .send()
mis datos insertará una fila.
Pero ¿y si mi base de datos es transaccional? ¿Cómo le indico a este generador cuándo cometer los datos en la base de datos? ¿Y cuándo abortar la transacción? Además, mantiene una conexión abierta con la base de datos, tal vez a veces quiero que cierre esa conexión para reclamar recursos.
Aquí es donde entra en .throw()
método .throw()
; con .throw()
puedo generar excepciones en ese método para señalar ciertas circunstancias:
def add_to_database(connection_string):
db = mydatabaselibrary.connect(connection_string)
cursor = db.cursor()
try:
while True:
try:
row = yield
cursor.execute(''INSERT INTO mytable VALUES(?, ?, ?)'', row)
except CommitException:
cursor.execute(''COMMIT'')
except AbortException:
cursor.execute(''ABORT'')
finally:
cursor.execute(''ABORT'')
db.close()
El método .close()
en un generador hace esencialmente lo mismo; utiliza la excepción GeneratorExit
combinada con .throw()
para cerrar un generador en ejecución.
Todo esto es una base importante de cómo funcionan las coroutinas ; Las coroutinas son esencialmente generadores, junto con una sintaxis adicional para que escribir una coroutina sea más fácil y más claro. Pero bajo el capó todavía están construidos en el mismo rendimiento y envío. Y cuando está ejecutando varias coroutinas en paralelo, necesita una manera de salir limpiamente de esas corutinas si una de ellas ha fallado, solo para nombrar un ejemplo.
En mi opinión, el método throw()
es útil por muchas razones.
Simetría: no hay una razón sólida por la cual una condición excepcional deba manejarse solo en la persona que llama y no también en la función del generador. (Suponga que un generador que lee valores de una base de datos devuelve un mal valor y suponga que solo la persona que llama sabe que el valor es malo. Con el método
throw()
, la persona que llama puede indicar al generador que hay una situación anormal que tiene que ser corregido.) Si el generador puede generar una excepción, interceptado por la persona que llama, lo contrario también debería ser posible.Flexibilidad: una función del generador puede tener más de una declaración de
yield
, y la persona que llama puede no ser consciente del estado interno del generador. Al lanzar excepciones, es posible restablecer el generador a un estado conocido, o implementar un control de flujo más sofisticado que sería mucho más incómodo connext()
,send()
,close()
solo.
Pedir casos de uso puede ser engañoso: para cada caso de uso, podría producir un ejemplo de contador sin la necesidad de un método throw()
, y la discusión continuará para siempre ...
Un caso de uso es incluir información sobre el estado interno de un generador en el seguimiento de la pila cuando se produce una excepción, información que de otra manera no sería visible para la persona que llama.
Por ejemplo, digamos que tenemos un generador como el siguiente donde el estado interno que queremos es el número de índice actual del generador:
def gen_items():
for i, item in enumerate(["", "foo", "", "foo", "bad"]):
if not item:
continue
try:
yield item
except Exception:
raise Exception("error during index: %d" % i)
El siguiente código no es suficiente para activar el manejo de excepciones adicionales:
# Stack trace includes only: "ValueError: bad value"
for item in gen_items():
if item == "bad":
raise ValueError("bad value")
Sin embargo, el siguiente código proporciona el estado interno:
# Stack trace also includes: "Exception: error during index: 4"
gen = item_generator()
for item in gen:
if item == "bad":
gen.throw(ValueError, "bad value")