La mejor práctica para afirmar Python

483
  1. ¿Existe un problema de rendimiento o mantenimiento de código con el uso assertcomo parte del código estándar en lugar de usarlo solo para fines de depuración?

    Es

    assert x >= 0, 'x is less than zero'

    mejor o peor que

    if x < 0:
        raise Exception, 'x is less than zero'
  2. Además, ¿hay alguna forma de establecer una regla de negocio como if x < 0 raise erroresa? Siempre se verifica sin try/except/finallyeso, si en algún momento a lo largo del código xes menor que 0, se genera un error, como si se configura assert x < 0al inicio de una función, en cualquier lugar dentro de la función donde se xconvierte en menos de 0 se genera una excepción?

meade
fuente
29
Los parámetros Python -O y -OO eliminarán sus afirmaciones. Eso debería conducir su pensamiento sobre para qué es bueno.
Peter Lada
44
El enlace de Thomasz Zielinski se rompió, ahora es: mail.python.org/pipermail/python-list/2013-November/660568.html . Estoy bastante seguro de que pipermail tiene una función de ID inestable, encontré otros enlaces desde dentro del mismo pipermail que apuntan a la misma url con la misma intención.
quodlibetor
3
En caso de que mail.python.org/pipermail/python-list/2013-November/660568.html se mueva nuevamente, se archivará en archive.is/5GfiG . El título de la publicación es "Cuándo usar aserción" y es una publicación excelente (un artículo realmente) sobre las mejores prácticas para Python assert.
clacke

Respuestas:

144

Para poder lanzar automáticamente un error cuando x sea menor que cero en toda la función. Puedes usar descriptores de clase . Aquí hay un ejemplo:

class LessThanZeroException(Exception):
    pass

class variable(object):
    def __init__(self, value=0):
        self.__x = value

    def __set__(self, obj, value):
        if value < 0:
            raise LessThanZeroException('x is less than zero')

        self.__x  = value

    def __get__(self, obj, objType):
        return self.__x

class MyClass(object):
    x = variable()

>>> m = MyClass()
>>> m.x = 10
>>> m.x -= 20
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "my.py", line 7, in __set__
    raise LessThanZeroException('x is less than zero')
LessThanZeroException: x is less than zero
Nadia Alramli
fuente
10
Aunque las propiedades se implementan como descriptores, no llamaría a esto un ejemplo de uso de ellas. Este es más un ejemplo de propiedades en sí mismas: docs.python.org/library/functions.html#property
Jason Baker
3
Las propiedades deben usarse dentro de MyClass cuando se establece x. Esta solución es demasiado general.
113
Muy buena respuesta, me gusta, pero no tiene que ver NADA con la pregunta ... ¿No podemos marcar la respuesta de Deestan o John Mee como la respuesta válida?
Vajk Hermecz
44
Esto no parece responder el título de la pregunta. Además, esta es una alternativa pobre a la característica de propiedad de clase de Python.
Dooms101
10
@VajkHermecz: En realidad, si relees la pregunta, estas son dos preguntas en una. Las personas que solo miran el título solo están familiarizadas con la primera pregunta, que esta respuesta no responde. Esta respuesta en realidad contiene una respuesta a la segunda pregunta.
ArtOfWarfare
742

Las afirmaciones deben usarse para probar condiciones que nunca deberían suceder . El propósito es colapsar temprano en el caso de un estado corrupto del programa.

Deben usarse excepciones para los errores que pueden ocurrir, y casi siempre debe crear sus propias clases de Excepción .


Por ejemplo, si está escribiendo una función para leer desde un archivo de configuración a un dictformato incorrecto en el archivo debería aparecer un ConfigurationSyntaxError, mientras que puede assertque no esté a punto de regresar None.


En su ejemplo, si xse establece un valor a través de una interfaz de usuario o de una fuente externa, lo mejor es una excepción.

Si xsolo lo establece su propio código en el mismo programa, vaya con una aserción.

Deestan
fuente
126
Esta es la forma correcta de usar afirmaciones. No deben usarse para controlar el flujo del programa.
Thane Brimhall
41
+1 para el último párrafo, aunque debería mencionar explícitamente que assertcontiene un implícito if __debug__y puede optimizarse , como dice la respuesta de John Mee
Tobias Kienzler
3
Volviendo a leer su respuesta, creo que probablemente no quiso decir condiciones que nunca deberían suceder como una regla, sino que el propósito es colapsar temprano en el caso de un estado de programa corrupto que generalmente coincide con una condición que no espera a suceder jamás .
Bentley4
10
asir solo debe usarse para detectar problemas sin recuperación conocida; casi siempre codifica errores (no son malas entradas). cuando se activa una afirmación, debería significar que el programa se encuentra en un estado en el que puede ser peligroso continuar, ya que puede comenzar a hablar con la red o escribir en el disco. El código robusto se mueve 'atómicamente' de un estado válido a un estado válido ante una entrada incorrecta (o maliciosa). El nivel superior de cada hilo debe tener una barrera de falla. Las barreras de falla que consumen información del mundo exterior generalmente fallan solo por una iteración de la barrera (while / try), rollback / log on error.
Rob
10
"Las afirmaciones deben usarse para probar condiciones que nunca deberían suceder". Si. Y el significado del segundo "debería" es: si esto sucede, el código del programa es incorrecto.
Lutz Prechelt
362

Las declaraciones de "afirmar" se eliminan cuando se optimiza la compilación . Entonces, sí, hay diferencias funcionales y de rendimiento.

El generador de código actual no emite ningún código para una declaración de aserción cuando se solicita la optimización en tiempo de compilación. - Documentos de Python 2 Documentos de Python 3

Si utiliza assert para implementar la funcionalidad de la aplicación, luego optimice la implementación para la producción, estará plagado de defectos "but-it-works-in-dev".

Ver PYTHONOPTIMIZE y -O -OO

John Mee
fuente
26
¡Guauu! Nota super importante que es! Había planeado usar afirmaciones para verificar algunas cosas que nunca deberían fallar, cuyo error indicaría que alguien estaba manipulando con mucho cuidado los datos que estaban enviando en un intento de obtener acceso a datos a los que no deberían tener acceso. No funcionaría, pero quiero cerrar rápidamente su intento con una afirmación, por lo que tener eso optimizado en la producción frustraría el propósito. Supongo que solo en raisesu Exceptionlugar. ¡Oh, acabo de descubrir un nombre apropiado SuspiciousOperation Exceptioncon subclases Django! ¡Perfecto!
ArtOfWarfare
Por cierto, @ArtOfWarfare si ejecuta banditsu código, le advertirá de esto.
Nagev
132

Los cuatro propósitos de assert

Suponga que trabaja en 200,000 líneas de código con cuatro colegas Alice, Bernd, Carl y Daphne. Ellos llaman a tu código, tú llamas a su código.

Luego asserttiene cuatro roles :

  1. Informe a Alice, Bernd, Carl y Daphne qué espera su código.
    Suponga que tiene un método que procesa una lista de tuplas y la lógica del programa puede romperse si esas tuplas no son inmutables:

    def mymethod(listOfTuples):
        assert(all(type(tp)==tuple for tp in listOfTuples))

    Esto es más confiable que la información equivalente en la documentación y es mucho más fácil de mantener.

  2. Informe a la computadora lo que su código espera.
    assertimpone el comportamiento adecuado de las personas que llaman de su código. Si su código llama al código de Alices y Bernd llama al suyo, entonces sin el assert, si el programa falla en el código de Alices, Bernd podría asumir que fue culpa de Alice, Alice investiga y podría asumir que fue su culpa, usted investiga y le dice a Bernd que en realidad fue su. Mucho trabajo perdido.
    Con afirma, quien recibe una llamada incorrecta, rápidamente podrán ver que fue su culpa, no la suya. Alice, Bernd y todos ustedes se benefician. Ahorra inmensas cantidades de tiempo.

  3. Informe a los lectores de su código (incluido usted mismo) qué ha logrado su código en algún momento.
    Suponga que tiene una lista de entradas y que cada una de ellas puede estar limpia (lo cual es bueno) o puede ser borrosa, trale, gullup o centelleante (que no son aceptables). Si es borroso, debe ser desenfadado; si es trale, debe ser baludo; si es gullup, debe ser troteado (y luego posiblemente también estimulado); si está centelleado, debe volver a brillar excepto los jueves. Ya tienes la idea: es algo complicado. Pero el resultado final es (o debería ser) que todas las entradas están limpias. Lo correcto es hacer un resumen del efecto de su ciclo de limpieza como

    assert(all(entry.isClean() for entry in mylist))

    Estas declaraciones ahorran un dolor de cabeza para todos los que intentan entender exactamente qué es lo que está logrando el maravilloso ciclo. Y la más frecuente de estas personas probablemente sea usted mismo.

  4. Informe a la computadora lo que su código ha logrado en algún momento.
    Si alguna vez se olvida de marcar una entrada que la necesita después del trote, assertle ahorrará el día y evitará que su código rompa el de Daphne mucho más tarde.

En mi opinión, assertlos dos propósitos de documentación (1 y 3) y salvaguarda (2 y 4) son igualmente valiosos.
Informar a las personas puede ser incluso más valioso que informar a la computadora porque puede evitar los mismos errores que los assertobjetivos deben atrapar (en el caso 1) y muchos errores posteriores en cualquier caso.

Lutz Prechelt
fuente
34
5. afirmar isinstance () ayuda a PyCharm (python IDE) a conocer el tipo de variable, se utiliza para autocompletar.
Cjkjvfnby
1
Afirma suposiciones de código de autodocumento para lo que es verdadero en el momento de ejecución actual. Es un comentario de suposición, que se verifica.
pyj
99
Con respecto a 2 y 4: debe tener mucho cuidado de que sus afirmaciones no sean demasiado estrictas. De lo contrario, las afirmaciones en sí mismas pueden ser lo único que hace que su programa se use en un entorno más general. Especialmente la afirmación de tipos va en contra de la tipificación de pato de Python.
zwirbeltier
99
@Cjkjvfnby Tenga cuidado con el uso excesivo de isinstance () como se describe en esta entrada del blog: " isinstance () se considera dañino ". Ahora puede usar docstrings para especificar tipos en Pycharm.
binarysubstrate
2
Usar afirma de una manera de asegurar el contrato. Más información sobre Design by Contract en.wikipedia.org/wiki/Design_by_contract
Leszek Zarna
22

Además de las otras respuestas, las afirmaciones arrojan excepciones, pero solo AssertionErrors. Desde un punto de vista utilitario, las afirmaciones no son adecuadas para cuando necesita un control de grano fino sobre las excepciones que captura.

outis
fuente
3
Derecha. Parecería una tontería captar excepciones de error de aserción en la persona que llama.
Raffi Khatchadourian
Muy buen punto. Un matiz que puede pasarse por alto fácilmente cuando solo se miran las preguntas originales desde un nivel macro. Incluso si no fuera por el problema con las afirmaciones que se descartan al optimizar, perder los detalles específicos de qué tipo de error ocurrió haría que la depuración sea mucho más desafiante. Saludos, outis!
cfwschmidt
Su respuesta puede leerse como si quisiera atraparla AssertionErrors, cuando esté de acuerdo con que sea de grano grueso. En realidad, no deberías atraparlos.
Tomasz Gandor
19

Lo único que está realmente mal con este enfoque es que es difícil hacer una excepción muy descriptiva usando declaraciones de afirmación. Si está buscando la sintaxis más simple, recuerde que también puede hacer algo como esto:

class XLessThanZeroException(Exception):
    pass

def CheckX(x):
    if x < 0:
        raise XLessThanZeroException()

def foo(x):
    CheckX(x)
    #do stuff here

Otro problema es que el uso de la aserción para la verificación de condición normal es que hace que sea difícil deshabilitar las afirmaciones de depuración utilizando el indicador -O.

Jason Baker
fuente
24
Puede agregar un mensaje de error a una aserción. Es el segundo parámetro. Eso lo hará descriptivo.
Raffi Khatchadourian
10

La palabra del idioma inglés afirmar aquí se usa en el sentido de jurar , afirmar , declarar . No significa "verificar" o "debería ser" . Significa que usted como codificador está haciendo una declaración jurada aquí:

# I solemnly swear that here I will tell the truth, the whole truth, 
# and nothing but the truth, under pains and penalties of perjury, so help me FSM
assert answer == 42

Si el código es correcto, salvo las perturbaciones de evento único , fallas de hardware y demás, ninguna afirmación fallará . Es por eso que el comportamiento del programa para un usuario final no debe verse afectado. Especialmente, una afirmación no puede fallar incluso bajo condiciones programáticas excepcionales . Simplemente nunca sucede. Si sucede, el programador debería ser eliminado por ello.

Antti Haapala
fuente
8

Como se ha dicho anteriormente, las afirmaciones deben usarse cuando su código NO DEBE llegar a un punto, lo que significa que hay un error allí. Probablemente la razón más útil que puedo ver para usar una aserción es una condición invariante / pre / post. Estos son algo que debe ser cierto al principio o al final de cada iteración de un bucle o una función.

Por ejemplo, una función recursiva (2 funciones separadas, por lo que 1 maneja la entrada incorrecta y la otra maneja el código incorrecto, porque es difícil de distinguir con recursividad). Esto haría obvio si se me olvida escribir la declaración if, lo que salió mal.

def SumToN(n):
    if n <= 0:
        raise ValueError, "N must be greater than or equal to 0"
    else:
        return RecursiveSum(n)

def RecursiveSum(n):
    #precondition: n >= 0
    assert(n >= 0)
    if n == 0:
        return 0
    return RecursiveSum(n - 1) + n
    #postcondition: returned sum of 1 to n

Estos invariantes de bucle a menudo se pueden representar con una afirmación.

matts1
fuente
2
Esto se hace mejor con decoradores (@precondition y @postcondition)
Caridorc
@ Caridorc, ¿cuál es el beneficio concreto de eso?
Chiel ten Brinke
@ChieltenBrinke código autodocumentado, en lugar de #precondition: n >= 0 una afirmación, puede escribir@precondition(lambda n: n >= 0)
Caridorc
@ Caridorc ¿Son esos decoradores incorporados entonces? ¿Y cómo se genera documentación a partir de eso?
Chiel ten Brinke
@ChieltenBrinke no está integrado pero es fácil de implementar stackoverflow.com/questions/12151182/… . Para obtener documentación, simplemente parchee el __doc__atributo dando una cadena adicional
Caridorc,
4

¿ Hay algún problema de rendimiento?

  • Recuerde "hacer que funcione primero antes de hacerlo rápido" .
    Muy poco porcentaje de cualquier programa suele ser relevante por su velocidad. Siempre puede expulsar o simplificar y assertsi alguna vez resulta ser un problema de rendimiento, y la mayoría de ellos nunca lo hará.

  • Sea pragmático :
    suponga que tiene un método que procesa una lista no vacía de tuplas y la lógica del programa se romperá si esas tuplas no son inmutables. Deberías escribir:

    def mymethod(listOfTuples):
        assert(all(type(tp)==tuple for tp in listOfTuples))

    Esto probablemente esté bien si sus listas tienden a tener diez entradas, pero puede convertirse en un problema si tienen un millón de entradas. Pero en lugar de descartar este valioso cheque por completo, simplemente podría degradarlo a

    def mymethod(listOfTuples):
        assert(type(listOfTuples[0])==tuple)  # in fact _all_ must be tuples!

    que es barato pero que probablemente atrapará la mayor parte del todos modos errores reales del programa.

Lutz Prechelt
fuente
2
Debe ser assert(len(listOfTuples)==0 or type(listOfTyples[0])==tuple).
osa
No, no debería. Esa sería una prueba mucho más débil, porque ya no verifica la propiedad 'no vacía', que la segunda afirmación verifica. (El primero no, aunque debería)
Lutz Prechelt
1
La segunda afirmación no verifica explícitamente la propiedad no vacía; Es más un efecto secundario. Si se produjera una excepción debido a que la lista está vacía, la persona que trabaja con el código (otra persona o el autor, un año después de escribirlo) lo miraría fijamente, tratando de averiguar si la afirmación realmente tenía la intención de captarla. la situación de la lista vacía, o si eso es un error en la afirmación misma. Además, no veo cómo no verificar si la caja vacía es "mucho más débil", mientras que solo verificar el primer elemento es "97% correcto".
osa
3

Bueno, esta es una pregunta abierta, y tengo dos aspectos que quiero tocar: cuándo agregar aserciones y cómo escribir los mensajes de error.

Propósito

Para explicárselo a un principiante, las aserciones son declaraciones que pueden generar errores, pero no las detectará. Y normalmente no se deben criar, pero en la vida real a veces se crían de todos modos. Y esta es una situación grave, de la cual el código no puede recuperarse, lo que llamamos un "error fatal".

A continuación, es para 'propósitos de depuración', lo que, aunque es correcto, suena muy desdeñoso. Me gusta más la formulación de 'declarar invariantes, que nunca deberían violarse', aunque funciona de manera diferente en diferentes principiantes ... Algunos 'simplemente lo entienden', y otros no encuentran ningún uso para él o reemplazan las excepciones normales, o incluso controlar el flujo con él.

Estilo

¡En Python, assertes una declaración, no una función! (recuerdaassert(False, 'is true') que no subirá. Pero, teniendo eso fuera del camino:

¿Cuándo y cómo escribir el 'mensaje de error' opcional?

Esto se aplica realmente a los marcos de prueba de unidad, que a menudo tienen muchos métodos dedicados para hacer afirmaciones ( assertTrue(condition), assertFalse(condition), assertEqual(actual, expected)etc.). A menudo también proporcionan una forma de comentar sobre la afirmación.

En el código desechable, podría prescindir de los mensajes de error.

En algunos casos, no hay nada que agregar a la afirmación:

volcado de def (algo): afirmar isinstance (algo, Dumpable) # ...

Pero aparte de eso, un mensaje es útil para la comunicación con otros programadores (que a veces son usuarios interactivos de su código, por ejemplo, en Ipython / Jupyter, etc.).

Bríndeles información, no solo filtrar detalles de implementación internos.

en vez de:

assert meaningless_identifier <= MAGIC_NUMBER_XXX, 'meaningless_identifier is greater than MAGIC_NUMBER_XXX!!!'

escribir:

assert meaningless_identifier > MAGIC_NUMBER_XXX, 'reactor temperature above critical threshold'

o tal vez incluso:

assert meaningless_identifier > MAGIC_NUMBER_XXX, f'reactor temperature({meaningless_identifier }) above critical threshold ({MAGIC_NUMBER_XXX})'

Lo sé, lo sé, este no es un caso para una aserción estática, pero quiero señalar el valor informativo del mensaje.

¿Mensaje negativo o positivo?

Esto puede ser controvertido, pero me duele leer cosas como:

assert a == b, 'a is not equal to b'
  • Estas son dos cosas contradictorias escritas una al lado de la otra. Entonces, cada vez que tengo una influencia en la base de código, presiono para especificar lo que queremos, usando verbos adicionales como 'must' y 'should', y no decir lo que no queremos.

    afirmar a == b, 'a debe ser igual a b'

Entonces, obtener AssertionError: a must be equal to btambién es legible, y la declaración parece lógica en el código. Además, puede obtener algo sin leer el rastreo (que a veces ni siquiera puede estar disponible).

Tomasz Gandor
fuente
1

Tanto el uso assertcomo el aumento de excepciones tienen que ver con la comunicación.

  • Las afirmaciones son declaraciones sobre la exactitud del código dirigido a los desarrolladores : una afirmación en el código informa a los lectores del código sobre las condiciones que deben cumplirse para que el código sea correcto. Una afirmación que falla en tiempo de ejecución informa a los desarrolladores que hay un defecto en el código que necesita ser reparado.

  • Las excepciones son indicaciones sobre situaciones no típicas que pueden ocurrir en tiempo de ejecución pero que no pueden resolverse mediante el código en cuestión, que se aborda en el código de llamada que se maneja allí. La aparición de una excepción no indica que hay un error en el código.

Mejores prácticas

Por lo tanto, si considera la ocurrencia de una situación específica en tiempo de ejecución como un error sobre el que desea informar a los desarrolladores ("Hola desarrollador, esta condición indica que hay un error en alguna parte, por favor corrija el código"). ir para una afirmación. Si la aserción verifica los argumentos de entrada de su código, normalmente debe agregar a la documentación que su código tiene un "comportamiento indefinido" cuando los argumentos de entrada violan esas condiciones.

Si, en cambio, la ocurrencia de esa misma situación no es una indicación de un error en sus ojos, sino una posible (quizás rara pero) situación que cree que debería ser manejada por el código del cliente, plantee una excepción. Las situaciones en las que se plantea una excepción deben formar parte de la documentación del código respectivo.

¿Hay algún problema de [...] rendimiento al usar assert

La evaluación de las afirmaciones lleva algún tiempo. Sin embargo, pueden eliminarse en tiempo de compilación. Esto tiene algunas consecuencias, sin embargo, ver más abajo.

¿Hay algún [...] problema de mantenimiento de código al usar assert

Normalmente, las aserciones mejoran la capacidad de mantenimiento del código, ya que mejoran la legibilidad al hacer explícitos los supuestos y durante el tiempo de ejecución verificar regularmente estos supuestos. Esto también ayudará a atrapar regresiones. Sin embargo, hay un problema que debe tenerse en cuenta: las expresiones utilizadas en las afirmaciones no deberían tener efectos secundarios. Como se mencionó anteriormente, las afirmaciones se pueden eliminar en el momento de la compilación, lo que significa que también desaparecerían los posibles efectos secundarios. Esto puede, sin querer, cambiar el comportamiento del código.

Dirk Herrmann
fuente
1

Una afirmación es verificar:
1. la condición válida,
2. la declaración válida,
3. lógica verdadera;
del código fuente. En lugar de fallar en todo el proyecto, da la alarma de que algo no es apropiado en su archivo fuente.

En el ejemplo 1, dado que la variable 'str' no es nula. Por lo tanto, no se plantea ninguna afirmación o excepción.

Ejemplo 1:

#!/usr/bin/python

str = 'hello Python!'
strNull = 'string is Null'

if __debug__:
    if not str: raise AssertionError(strNull)
print str

if __debug__:
    print 'FileName '.ljust(30,'.'),(__name__)
    print 'FilePath '.ljust(30,'.'),(__file__)


------------------------------------------------------

Output:
hello Python!
FileName ..................... hello
FilePath ..................... C:/Python\hello.py

En el ejemplo 2, var 'str' es nulo. Por lo tanto, estamos evitando que el usuario se adelante al programa defectuoso al afirmar comunicado.

Ejemplo 2

#!/usr/bin/python

str = ''
strNull = 'NULL String'

if __debug__:
    if not str: raise AssertionError(strNull)
print str

if __debug__:
    print 'FileName '.ljust(30,'.'),(__name__)
    print 'FilePath '.ljust(30,'.'),(__file__)


------------------------------------------------------

Output:
AssertionError: NULL String

En el momento en que no queremos depurar y nos damos cuenta del problema de aserción en el código fuente. Deshabilitar la bandera de optimización

python -O afirmarStatement.py
no se imprimirá nada

akD
fuente
0

En IDE como PTVS, PyCharm, las assert isinstance()declaraciones Wing se pueden usar para permitir la finalización del código para algunos objetos poco claros.

denfromufa
fuente
Esto parece ser anterior al uso de anotaciones de tipo o de typing.cast.
Acumenus
-1

Para lo que vale, si se trata de un código que se basa assertpara funcionar correctamente, agregar el siguiente código garantizará que las afirmaciones estén habilitadas:

try:
    assert False
    raise Exception('Python assertions are not working. This tool relies on Python assertions to do its job. Possible causes are running with the "-O" flag or running a precompiled (".pyo" or ".pyc") module.')
except AssertionError:
    pass
Emilio M Bumachar
fuente
2
Esto no responde la pregunta de OP que trata sobre las mejores prácticas.
codeforester