¿Aumentar excepción vs. retorno Ninguno en funciones?

¿Cuál es la mejor práctica en una función definida por el usuario en Python: raise una excepción o return None ? Por ejemplo, tengo una función que encuentra el archivo más reciente en una carpeta.

 def latestpdf(folder): # list the files and sort them try: latest = files[-1] except IndexError: # Folder is empty. return None # One possibility raise FileNotFoundError() # Alternative else: return somefunc(latest) # In my case, somefunc parses the filename 

Otra opción es dejar la excepción y manejarla en el código del llamante, pero creo que es más claro tratar con un FileNotFoundError que con un IndexError . ¿O es una mala forma reenviar una excepción con un nombre diferente?

Es realmente una cuestión de semántica. ¿Qué significa foo = latestpdf(d) ?

¿Es perfectamente razonable que no haya un archivo más reciente? Entonces claro, solo devuelve Ninguno.

¿Estás esperando encontrar siempre un archivo más reciente? Levantar una excepción. Y sí, volver a levantar una excepción más apropiada está bien.

Si esta es solo una función general que se supone se aplica a cualquier directorio, yo haría la primera y devolvería Ninguno. Si el directorio, por ejemplo, pretende ser un directorio de datos específico que contiene el conjunto de archivos conocidos de una aplicación, provocaría una excepción.

Haría un par de sugerencias antes de responder su pregunta, ya que puede responder la pregunta por usted.

  • Siempre nombre sus funciones descriptivas. latestpdf significa muy poco para cualquier persona, pero revisando su función latestpdf() obtiene el último pdf. Le sugiero que lo nombre getLatestPdfFromFolder(folder) .

Tan pronto como lo hice, quedó claro lo que debería devolver … Si no hay un pdf, haga una excepción. Pero espera más …

  • Mantener las funciones claramente definidas. Dado que no es evidente lo que se supone que debe hacer algo y no es (aparentemente) obvio cómo se relaciona con obtener el último pdf, sugeriría que lo elimine. Esto hace que el código sea mucho más legible.

 for folder in folders: try: latest = getLatestPdfFromFolder(folder) results = somefuc(latest) except IOError: pass 

¡Espero que esto ayude!

Por lo general, prefiero manejar las excepciones internamente (es decir, probar / excepto dentro de la función llamada, posiblemente devolviendo una Ninguna) porque Python se escribe dinámicamente. En general, lo considero una decisión de juicio de una manera u otra, pero en un lenguaje tipificado dinámicamente, hay pequeños factores que inclinan la balanza a favor de no pasar la excepción a la persona que llama:

  1. No se notifica a las personas que llaman a su función las excepciones que se pueden lanzar. Es una forma de arte un poco saber qué tipo de excepción está buscando (y generics, excepto los bloques, deben evitarse).
  2. if val is None es un poco más fácil que except ComplicatedCustomExceptionThatHadToBeImportedFromSomeNameSpace . En serio, odio tener que recordar escribir from django.core.exceptions import ObjectDoesNotExist en la parte superior de todos mis archivos django solo para manejar un caso de uso muy común. En un mundo estático, deja que el editor lo haga por ti.

Honestamente, sin embargo, siempre es una decisión de juicio, y la situación que está describiendo, donde la función llamada recibe un error que no puede ayudar, es una excelente razón para volver a generar una excepción que sea significativa. Tiene la idea correcta exacta, pero a menos que sea una excepción, proporcionará información más significativa en un seguimiento de stack que

 AttributeError: 'NoneType' object has no attribute 'foo' 

que, nueve de cada diez veces, es lo que verá la persona que llama si devuelve una Ninguna no manejada, no se moleste.

(Todo este tipo me hace desear que las excepciones de Python tengan los atributos de cause de forma predeterminada, como en java, lo que le permite pasar excepciones a nuevas excepciones para que pueda volver a generar todo lo que quiera y nunca pierda la fuente original del problema).

En general, diría que se debe lanzar una excepción si ha ocurrido algo catastrófico del cual no se puede recuperar (es decir, su función se relaciona con algún recurso de Internet al que no se puede conectar), y debe devolver Ninguno si su función realmente debe devolver algo pero no sería apropiado devolver nada (es decir, “Ninguno” si su función intenta hacer coincidir una subcadena en una cadena, por ejemplo).

con la tipificación de Python 3.5:

función de ejemplo al devolver Ninguno será:

 def latestpdf(folder: str) -> Union[str, None] 

y cuando se levanta una excepción será:

 def latestpdf(folder: str) -> str 

La opción 2 parece más legible y python.

(Opción + para agregar comentarios a la excepción como se indicó anteriormente.)