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_page
es 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.
hits
es 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í: IOError
se 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_status
de 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 None
de 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 hits
está vacío, se IndexError
lanzará un cuando lo intentemos hits[0]
. Pero no debería esperarse que la persona que llama maneje un IndexError
lanzamiento por mi función, ya que no tiene idea de dónde IndexError
vino; 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 status
no 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.
fuente
La respuesta aceptada merece ser aceptada y responde la pregunta, escribo esto solo para proporcionar un poco de antecedentes adicionales.
Uno de los credos de Python es: es más fácil pedir perdón que permiso. Esto significa que normalmente solo haces cosas, y si esperas excepciones, las manejas. A diferencia de lo que hace antes, verifica para asegurarte de que no obtendrás una excepción.
Quiero proporcionar un ejemplo para mostrarle cuán dramática es la diferencia en la mentalidad de C ++ / Java. Un bucle for en C ++ generalmente se parece a:
Una forma de pensar en esto: acceder a
myvector[k]
donde k> = myvector.size () causará una excepción. Entonces, en principio, podría escribir esto (muy torpemente) como un try-catch.O algo similar. Ahora, considere lo que sucede en un bucle python for:
¿Cómo funciona esto? El ciclo for toma el resultado del rango (1) y lo llama iter (), agarrando un iterador.
Luego llama a continuación en cada iteración de bucle, hasta que ...:
En otras palabras, un bucle for en python es en realidad un intento, excepto disfrazado.
En cuanto a la pregunta concreta, recuerde que las excepciones detienen la ejecución normal de la función y deben tratarse por separado. En Python, debe lanzarlos libremente siempre que no tenga sentido ejecutar el resto del código en su función, y / o ninguno de los retornos refleja correctamente lo que sucedió en la función. Tenga en cuenta que regresar temprano de una función es diferente: regresar temprano significa que ya descubrió la respuesta y no necesita el resto del código para encontrar la respuesta. Estoy diciendo que se deben lanzar excepciones cuando no se conoce la respuesta, y el resto del código para determinar la respuesta no se puede ejecutar razonablemente. Ahora, "reflejarse correctamente" en sí mismo, como las excepciones que elige lanzar, es todo una cuestión de documentación.
En el caso de su código particular, diría que cualquier situación que haga que los hits sean una lista vacía debería arrojarse. ¿Por qué? Bueno, la forma en que se configura su función, no hay forma de determinar la respuesta sin analizar los resultados. Entonces, si los hits no se pueden analizar, ya sea porque la URL es incorrecta o porque los hits están vacíos, entonces la función no puede responder la pregunta y, de hecho, ni siquiera puede intentarlo.
En este caso particular, argumentaría que incluso si logras analizar y no obtienes una respuesta razonable (viva o muerta), entonces aún debes lanzar. ¿Por qué? Porque, la función devuelve un valor booleano. Devolver ninguno es muy peligroso para su cliente. Si hacen un check if en None, no habrá falla, solo se tratará en silencio como False. Por lo tanto, su cliente básicamente siempre tendrá que hacer una comprobación de si es Ninguno de todos modos si no quiere fallas silenciosas ... por lo que probablemente solo deba lanzar.
fuente
Debe usar excepciones cuando ocurra algo excepcional . Es decir, algo que no debería ocurrir dado el uso adecuado de la aplicación. Si es permisible y se espera que el consumidor de su método busque algo que no se encontrará, entonces "no encontrado" no es un caso excepcional. En este caso, debe devolver nulo o "Ninguno" o {}, o algo que indique un conjunto de retorno vacío.
Si, por otro lado, realmente espera que los consumidores de su método siempre encuentren lo que se está buscando (a menos que se equivoquen de alguna manera), entonces no encontrarlo sería una excepción y debería continuar con eso.
La clave es que el manejo de excepciones puede ser costoso: se supone que las excepciones recopilan información sobre el estado de su aplicación cuando ocurren, como un seguimiento de la pila, para ayudar a las personas a descifrar por qué ocurrieron. No creo que sea lo que intentas hacer.
fuente
String
ay elige "Ninguno" como su indicador, esto significa que debe tener cuidado de que "Ninguno" nunca sea un valor válido. También tenga en cuenta que hay una diferencia entre mirar los datos y no encontrar un valor y no poder recuperar los datos, por lo tanto, no podemos encontrar los datos. Tener el mismo resultado para estos dos casos significa que no tiene visibilidad una vez que no obtiene ningún valor cuando espera que haya uno.Si estaba escribiendo una función
Lo escribiría en
return True
oFalse
en los casos en que estoy absolutamente seguro de uno u otro, yraise
un error en cualquier otro caso (por ejemploraise ValueError("Status neither 'dead' nor 'alive'")
). Esto se debe a que la función que llama a la mía espera un valor booleano, y si no puedo proporcionarlo con certeza, el flujo regular del programa no debería continuar.Algo parecido a su ejemplo de obtener un número diferente de "hits" de lo esperado, probablemente lo ignoraría; mientras uno de los éxitos aún coincida con mi patrón "Abe Vigoda está {muerto | vivo}", está bien. Esto permite reorganizar la página, pero aún obtiene la información adecuada.
Más bien que
Comprobaría explícitamente:
ya que esto tiende a ser "más barato" que configurar
try
.Estoy de acuerdo contigo
IOError
; Tampoco trataría de manejar por error la conexión al sitio web; si no podemos, por alguna razón, este no es el lugar apropiado para manejarlo (ya que no nos ayuda a responder nuestra pregunta) y debería pasar fuera a la función de llamada.fuente