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

85

¿Cuál es una mejor práctica en una función definida por el usuario en Python: raiseuna excepción o return None? Por ejemplo, tengo una función que busca 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 de la persona que llama, pero creo que es más claro lidiar con FileNotFoundErrorun IndexError. ¿O es una mala forma volver a generar una excepción con un nombre diferente?

parcydarks
fuente
3
Me inclino por generar una excepción, por lo que me veo obligado a manejar la excepción en la función de llamada. Si me olvido de verificar si la salida es Ninguna en la función de llamada, podría tener un error latente. Si devolvió None, es de esperar que la siguiente línea en la función de llamada genere un AttributeError. Sin embargo, si el valor devuelto se agrega a un diccionario y luego 100 llamadas a funciones en un archivo de origen diferente se genera un AttributeError, se divertirá buscando por qué ese valor era Ninguno.
IceArdor
En general, también evito valores que tienen un significado especial o que tienen múltiples firmas para una función (podría devolver una cadena o Ninguno).
IceArdor

Respuestas:

91

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

¿Es perfectamente razonable que no haya un archivo más reciente? Entonces seguro, devuelve None.

¿Espera encontrar siempre un archivo más reciente? Plantee una excepción. Y sí, volver a plantear una excepción más apropiada está bien.

Si esta es solo una función general que se supone que se aplica a cualquier directorio, haría lo primero y devolvería Ninguno. Si, por ejemplo, el directorio está destinado a ser un directorio de datos específico que contiene el conjunto de archivos conocido de una aplicación, plantearía una excepción.

Eevee
fuente
3
Otro punto a considerar: si se genera una excepción, se puede adjuntar un mensaje, pero no podemos hacerlo al regresar None.
kawing-chiu
9

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

  • Siempre asigne un nombre descriptivo a sus funciones. latestpdfsignifica muy poco para cualquiera, pero al revisar su función se latestpdf()obtiene el último pdf. Sugeriría que lo nombre getLatestPdfFromFolder(folder).

Tan pronto como hice esto, quedó claro qué debería devolver. Si no hay un pdf, plantee una excepción. Pero espera ahí más ...

  • Mantenga las funciones claramente definidas. Dado que no es evidente lo que se supone que debe hacer somefuc y no es (aparentemente) obvio cómo se relaciona con obtener el último pdf, le sugiero que lo mueva. 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!

rh0dium
fuente
O get_latest_pdf_from_folder. De hecho, Pep8: "Los nombres de funciones deben estar en minúsculas, con palabras separadas por guiones bajos según sea necesario para mejorar la legibilidad".
PatrickT
7

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

  1. Cualquiera que llame a su función no es notificado de las excepciones que se pueden lanzar. Se convierte en una especie de forma de arte saber qué tipo de excepción está buscando (y los bloques de excepción genérica deben evitarse).
  2. if val is Nonees un poco más fácil que except ComplicatedCustomExceptionThatHadToBeImportedFromSomeNameSpace. En serio, odio tener que recordar escribir from django.core.exceptions import ObjectDoesNotExisten la parte superior de todos mis archivos de django solo para manejar un caso de uso realmente común. En un mundo de tipos estáticos, deje que el editor lo haga por usted.

Honestamente, sin embargo, siempre es una decisión de criterio, 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 exacta correcta, pero a menos que sea una excepción, proporcionará información más significativa en un seguimiento de pila que

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

que, nueve de cada diez veces, es lo que verá la persona que llama si devuelve un Ninguno sin controlar, no se moleste.

(Todo esto me hace desear que las excepciones de Python tuvieran los causeatributos por defecto, como en Java, lo que le permite pasar excepciones a nuevas excepciones para que pueda volver a lanzar todo lo que quiera y nunca perder la fuente original del problema).

David Berger
fuente
El argumento de que las posibles excepciones no están definidas y, por lo tanto, es difícil de captar es un argumento muy válido para Python.
snorberhuis
4

con Python 3.5 escribiendo :

La función de ejemplo al devolver None será:

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

y al plantear una excepción será:

def latestpdf(folder: str) -> str 

la opción 2 parece más legible y pitónica

(+ opción para agregar un comentario a la excepción como se indicó anteriormente).

Asaf
fuente
4
Union[str, None]debería serOptional[str]
Georgy
2
una taquigrafía, pero tienes razón, es más legible. no editar, por lo que ambas opciones están aquí.
Asaf
2 es potencialmente más legible pero (¿desafortunadamente?) Las sugerencias de tipo no indican que se pueda lanzar una excepción. Últimamente he descubierto que 1 ayudará a detectar más errores, ya que se ve obligado a manejar una devolución None.
jonespm
2

En general, diría que se debe lanzar una excepción si ha ocurrido algo catastrófico del que no se puede recuperar (es decir, su función se ocupa de algún recurso de Internet al que no se puede conectar), y debe devolver Ninguno si su función realmente debería 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).


fuente