El ajuste
A menudo tengo problemas para determinar cuándo y cómo usar excepciones. Consideremos un ejemplo simple: supongamos que estoy raspando una página web, digamos " http://www.abevigoda.com/ ", para determinar si Abe Vigoda todavía está vivo. Para hacer esto, todo lo que tenemos que hacer es descargar la página y buscar los momentos en que aparece la frase "Abe Vigoda". Devolvemos la primera aparición, ya que eso incluye el estado de Abe. Conceptualmente, se verá así:
def get_abe_status(url):
# download the page
page = download_page(url)
# get all mentions of Abe Vigoda
hits = page.find_all_mentions("Abe Vigoda")
# parse the first hit for his status
status = parse_abe_status(hits[0])
# he's either alive or dead
return status == "alive"
Donde parse_abe_status(s)toma una cadena de la forma "Abe Vigoda es algo " y devuelve la parte " algo ".
Antes de argumentar que hay formas mucho mejores y más robustas de raspar esta página para el estado de Abe, recuerde que este es solo un ejemplo simple y artificial utilizado para resaltar una situación común en la que estoy.
Ahora, ¿dónde puede encontrar este código problemas? Entre otros errores, algunos "esperados" son:
download_pagees posible que no pueda descargar la página y arroje un archivoIOError.- Es posible que la URL no apunte a la página correcta o que la página se descargue incorrectamente y, por lo tanto, no haya resultados.
hitses la lista vacía, entonces. - La página web ha sido alterada, posiblemente haciendo que nuestras suposiciones sobre la página sean incorrectas. Tal vez esperamos 4 menciones de Abe Vigoda, pero ahora encontramos 5.
- Por alguna razón,
hits[0]puede no ser una cadena de la forma "Abe Vigoda es algo ", por lo que no se puede analizar correctamente.
El primer caso no es realmente un problema para mí: IOErrorse lanza un y puede ser manejado por la persona que llama de mi función. Así que consideremos los otros casos y cómo podría manejarlos. Pero primero, supongamos que implementamos parse_abe_statusde la manera más estúpida posible:
def parse_abe_status(s):
return s[13:]
Es decir, no realiza ninguna comprobación de errores. Ahora, a las opciones:
Opción 1: regreso None
Puedo decirle a la persona que llama que algo salió mal al regresar None:
def get_abe_status(url):
# download the page
page = download_page(url)
# get all mentions of Abe Vigoda
hits = page.find_all_mentions("Abe Vigoda")
if not hits:
return None
# parse the first hit for his status
status = parse_abe_status(hits[0])
# he's either alive or dead
return status == "alive"
Si la persona que llama recibe Nonede mi función, debe suponer que no hubo menciones de Abe Vigoda, por lo que algo salió mal. Pero esto es bastante vago, ¿verdad? Y no ayuda el caso donde hits[0]no es lo que pensamos que era.
Por otro lado, podemos poner algunas excepciones:
Opción 2: uso de excepciones
Si hitsestá vacío, se IndexErrorlanzará un cuando lo intentemos hits[0]. Pero no debería esperarse que la persona que llama maneje un IndexErrorlanzamiento por mi función, ya que no tiene idea de dónde IndexErrorvino; podría haber sido arrojado find_all_mentions, por lo que él sabe. Entonces crearemos una clase de excepción personalizada para manejar esto:
class NotFoundError(Exception):
"""Throw this when something can't be found on a page."""
def get_abe_status(url):
# download the page
page = download_page(url)
# get all mentions of Abe Vigoda
hits = page.find_all_mentions("Abe Vigoda")
try:
hits[0]
except IndexError:
raise NotFoundError("No mentions found.")
# parse the first hit for his status
status = parse_abe_status(hits[0])
# he's either alive or dead
return status == "alive"
Ahora, ¿qué pasa si la página ha cambiado y hay un número inesperado de visitas? Esto no es catastrófico, ya que el código aún puede funcionar, pero una persona que llama puede ser más cuidadosa o puede que desee registrar una advertencia. Entonces lanzaré una advertencia:
class NotFoundError(Exception):
"""Throw this when something can't be found on a page."""
def get_abe_status(url):
# download the page
page = download_page(url)
# get all mentions of Abe Vigoda
hits = page.find_all_mentions("Abe Vigoda")
try:
hits[0]
except IndexError:
raise NotFoundError("No mentions found.")
# say we expect four hits...
if len(hits) != 4:
raise Warning("An unexpected number of hits.")
logger.warning("An unexpected number of hits.")
# parse the first hit for his status
status = parse_abe_status(hits[0])
# he's either alive or dead
return status == "alive"
Por último, podríamos encontrar que statusno está vivo ni muerto. Tal vez, por alguna extraña razón, hoy resultó ser comatose. Entonces no quiero volver False, ya que eso implica que Abe está muerto. ¿Qué debo hacer aquí? Lanza una excepción, probablemente. ¿Pero de qué tipo? ¿Debo crear una clase de excepción personalizada?
class NotFoundError(Exception):
"""Throw this when something can't be found on a page."""
def get_abe_status(url):
# download the page
page = download_page(url)
# get all mentions of Abe Vigoda
hits = page.find_all_mentions("Abe Vigoda")
try:
hits[0]
except IndexError:
raise NotFoundError("No mentions found.")
# say we expect four hits...
if len(hits) != 4:
raise Warning("An unexpected number of hits.")
logger.warning("An unexpected number of hits.")
# parse the first hit for his status
status = parse_abe_status(hits[0])
if status not in ['alive', 'dead']:
raise SomeTypeOfError("Status is an unexpected value.")
# he's either alive or dead
return status == "alive"
Opción 3: en algún punto intermedio
Creo que el segundo método, con excepciones, es preferible, pero no estoy seguro si estoy usando excepciones correctamente dentro de él. Tengo curiosidad por ver cómo los programadores más experimentados manejarían esto.