¿Qué excepción debo plantear en combinaciones de argumentos malos / ilegales en Python?

545

Me preguntaba sobre las mejores prácticas para indicar combinaciones de argumentos no válidos en Python. Me he encontrado con algunas situaciones en las que tienes una función como esta:

def import_to_orm(name, save=False, recurse=False):
    """
    :param name: Name of some external entity to import.
    :param save: Save the ORM object before returning.
    :param recurse: Attempt to import associated objects as well. Because you
        need the original object to have a key to relate to, save must be
        `True` for recurse to be `True`.
    :raise BadValueError: If `recurse and not save`.
    :return: The ORM object.
    """
    pass

La única molestia con esto es que cada paquete tiene el suyo, generalmente ligeramente diferente BadValueError. Sé que en Java existe java.lang.IllegalArgumentException, ¿se entiende bien que todos crearán sus propios BadValueErrors en Python o hay otro método preferido?

cdleary
fuente

Respuestas:

609

Simplemente aumentaría ValueError , a menos que necesite una excepción más específica ...

def import_to_orm(name, save=False, recurse=False):
    if recurse and not save:
        raise ValueError("save must be True if recurse is True")

Realmente no tiene sentido hacerlo class BadValueError(ValueError):pass: su clase personalizada es idéntica en uso a ValueError , entonces, ¿por qué no usar eso?

dbr
fuente
65
> "Entonces, ¿por qué no usar eso?" - Especificidad. Tal vez quiero atrapar en alguna capa externa "MyValueError", pero no ninguna / all "ValueError".
Kevin Little el
77
Sí, así que parte de la cuestión de la especificidad es dónde se plantea ValueError. Si a la función de llamada le gustan sus argumentos pero llama internamente a math.sqrt (-1), una persona que llama puede detectar ValueError y esperar que sus argumentos sean inapropiados. Tal vez lo que comprueba el mensaje en este caso ...
cdleary
3
No estoy seguro de que el argumento se mantenga: si alguien está llamando math.sqrt(-1), ese es un error de programación que debe ser reparado de todos modos. ValueErrorno está destinado a quedar atrapado en la ejecución normal del programa o derivaría de RuntimeError.
antes
2
Si el error está en el NÚMERO de argumentos, para una función con un número variable de argumentos ... por ejemplo, una función donde los argumentos deben ser un número par de argumentos, entonces debe generar un TypeError, para ser coherente. Y no cree su propia clase a menos que a) tenga un caso de uso ob) esté exportando la biblioteca para que otros la usen. La funcionalidad prematura es la muerte del código.
Erik Aronesty
104

Yo heredaría de ValueError

class IllegalArgumentError(ValueError):
    pass

A veces es mejor crear sus propias excepciones, pero heredar de uno incorporado, que es lo más parecido posible a lo que desea.

Si necesita detectar ese error específico, es útil tener un nombre.

Markus Jarderot
fuente
26
Deje de escribir clases y excepciones personalizadas - pyvideo.org/video/880/stop-writing-classes
Hamish Grubijan
41
@HamishGrubijan ese video es terrible. Cuando alguien sugirió un buen uso de una clase, simplemente gritó "No uses clases". Brillante. Las clases son buenas. Pero no confíes en mi palabra .
Rob Grant
12
@RobertGrant No, no lo entiendes. Ese video no se trata literalmente de "no usar clases". Se trata de no complicar demasiado las cosas.
RayLuo
16
@RayLuo, es posible que haya comprobado la cordura de lo que dice el video y lo haya convertido en un mensaje alternativo sensible al paladar, pero eso es lo que dice el video, y es lo que alguien que no tiene mucha experiencia y sentido común saldrá con.
Rob Grant
44
@SamuelSantana como dije, cada vez que alguien levantaba la mano y decía "¿qué pasa con X?" donde X era una buena idea, simplemente dijo, "no hagas otra clase". Bastante claro. Estoy de acuerdo en que la clave es el equilibrio; el problema es que es demasiado vago para vivir :-)
Rob Grant
18

Creo que la mejor manera de manejar esto es la forma en que Python lo maneja. Python plantea un TypeError. Por ejemplo:

$ python -c 'print(sum())'
Traceback (most recent call last):
File "<string>", line 1, in <module>
TypeError: sum expected at least 1 arguments, got 0

Nuestro desarrollador junior acaba de encontrar esta página en una búsqueda en Google de "argumentos erróneos de excepción de Python" y me sorprende que la respuesta obvia (para mí) nunca se sugirió en la década desde que se hizo esta pregunta.

J Bones
fuente
8
Nada me sorprende, pero estoy de acuerdo al 100% en que TypeError es la excepción correcta si el tipo es incorrecto en algunos de los argumentos pasados ​​a la función. Un ValueError sería adecuado si las variables son del tipo correcto pero sus contenidos y valores no tienen sentido.
user3504575
Creo que esto probablemente se deba a argumentos faltantes o no solicitados, mientras que la pregunta es sobre argumentos que se dan correctamente, pero que son incorrectos en un nivel de abstracción más alto que involucra el valor del argumento dado. Pero como en realidad estaba buscando lo primero, de todos modos tengo un voto positivo.
Nadie el
2
Como @ user3504575 y @Nobody dijeron, TypeError se usa si los argumentos no coinciden con la firma de la función (número incorrecto de argumentos posicionales, argumentos de palabras clave con el nombre incorrecto, tipo de argumento incorrecto), pero se usa un ValueError cuando se llama a la función coincide con la firma pero los valores del argumento no son válidos (por ejemplo, llamar int('a')). fuente
goodmami
Como la pregunta del OP se refería a "combinaciones de argumentos inválidas", parece que un TypeError sería apropiado ya que este sería un caso en el que la firma de la función es esencialmente incorrecta para los argumentos pasados.
J Bones
Su ejemplo llama sum()sin argumentos, que es un TypeError, pero el OP estaba preocupado con combinaciones "ilegales" de valores de argumento cuando los tipos de argumento son correctos. En este caso, tanto savey recurseson Bools, pero si recursees Trueentonces saveno debería ser False. Se trata de una ValueError. Estoy de acuerdo en que alguna interpretación del título de la pregunta sería respondida por TypeError, pero no por el ejemplo que se presenta.
goodmami
11

Sobre todo acabo de ver el builtin ValueErrorutilizado en esta situación.

Eli Courtwright
fuente
8

Depende de cuál sea el problema con los argumentos.

Si el argumento tiene el tipo incorrecto, genere un TypeError. Por ejemplo, cuando obtienes una cadena en lugar de uno de esos booleanos.

if not isinstance(save, bool):
    raise TypeError(f"Argument save must be of type bool, not {type(save)}")

Sin embargo, tenga en cuenta que en Python rara vez hacemos verificaciones como esta. Si el argumento realmente no es válido, alguna función más profunda probablemente se quejará por nosotros. Y si solo verificamos el valor booleano, tal vez algún usuario de código luego lo alimente con una cadena sabiendo que las cadenas no vacías son siempre verdaderas. Podría ahorrarle un yeso.

Si los argumentos tienen valores no válidos, aumente ValueError. Esto parece más apropiado en su caso:

if recurse and not save:
    raise ValueError("If recurse is True, save should be True too")

O en este caso específico, tener un valor Verdadero de recurrencia implica un valor Verdadero de guardar. Como consideraría esto una recuperación de un error, es posible que también desee quejarse en el registro.

if recurse and not save:
    logging.warning("Bad arguments in import_to_orm() - if recurse is True, so should save be")
    save = True
Gloweye
fuente
Creo que esta es la respuesta más precisa. Obviamente, esto está subestimado (7 votos hasta ahora, incluido el mío).
Siu Ching Pong -Asuka Kenji-
-1

No estoy seguro estoy de acuerdo con la herencia de ValueError- mi interpretación de la documentación es que ValueErrorse sólo se supone para ser criados por órdenes internas ... heredando de o elevándola a sí mismo parece incorrecta.

Se genera cuando una operación o función incorporada recibe un argumento que tiene el tipo correcto pero un valor inapropiado, y la situación no se describe con una excepción más precisa como IndexError.

- Documentación de ValueError

cdleary
fuente
Compare google.com/codesearch?q=lang:python+class \ + \ w Error (([^ E] \ w * | E [^ x] \ w )): con google.com/codesearch?q=lang: python + class \ + \ w * Error (excepción):
Markus Jarderot el
13
Esa propaganda simplemente significa que los elementos integrados lo aumentan, y no que solo los elementos integrados pueden aumentarlo. En esta instancia, no sería del todo apropiado que la documentación de Python hable sobre lo que generan las bibliotecas externas.
Ignacio Vázquez-Abrams
55
Cada pieza de software de Python que he visto se ha utilizado ValueErrorpara este tipo de cosas, así que creo que estás intentando leer demasiado en la documentación.
James Bennett
66
Err, si vamos a utilizar las búsquedas de Google Code para argumentar esto: google.com/codesearch?q=lang%3Apython+raise%5C+ValueError # 66,300 casos de aumento de ValueError, incluidos Zope, xen, Django, Mozilla (y eso es solo desde la primera página de resultados). Si cabe una excepción
integrada
77
Como se dijo, la documentación es ambigua. Debería haberse escrito como "Elevado cuando se recibe una operación integrada o una función integrada" o como "Elevado cuando se recibe una función u operación integrada". Por supuesto, cualquiera que sea la intención original, la práctica actual lo ha superado (como señala @dbr). Por lo tanto, debe reescribirse como la segunda variante.
Eponymous
-1

De acuerdo con la sugerencia de Markus de lanzar su propia excepción, pero el texto de la excepción debería aclarar que el problema está en la lista de argumentos, no en los valores de los argumentos individuales. Yo propondría:

class BadCallError(ValueError):
    pass

Se usa cuando faltan argumentos de palabras clave que fueron necesarios para la llamada específica, o los valores de argumento son válidos individualmente pero inconsistentes entre sí. ValueErrorseguiría siendo correcto cuando un argumento específico es del tipo correcto pero está fuera de rango.

¿No debería ser esta una excepción estándar en Python?

En general, me gustaría que el estilo de Python sea un poco más preciso al distinguir las entradas incorrectas de una función (culpa del llamante) de los malos resultados dentro de la función (mi culpa). Por lo tanto, también puede haber un BadArgumentError para distinguir los errores de valor en los argumentos de los errores de valor en los locales.

BobHy
fuente
Elevaría KeyErrorpara la palabra clave no encontrada (ya que una palabra clave explícita que falta es semánticamente idéntica a un **kwargsdict que falta esa clave).
cowbert