Tipografía de pato, validación de datos y programación asertiva en Python


10

Sobre la escritura de pato :

La tipificación de los patos se ayuda al no probar habitualmente el tipo de argumentos en los cuerpos de métodos y funciones, confiando en la documentación, el código claro y las pruebas para garantizar un uso correcto.

Acerca de la validación de argumentos (EAFP: es más fácil pedir perdón que permiso). Un ejemplo adaptado de aquí :

... se considera más pitónico hacer:

def my_method(self, key):
    try:
        value = self.a_dict[member]
    except TypeError:
        # do something else

Esto significa que cualquier otra persona que use su código no tiene que usar un diccionario real o una subclase; puede usar cualquier objeto que implemente la interfaz de mapeo.

Desafortunadamente en la práctica no es tan simple. ¿Qué pasa si el miembro en el ejemplo anterior podría ser un número entero? Los enteros son inmutables, por lo que es perfectamente razonable usarlos como claves de diccionario. Sin embargo, también se utilizan para indexar objetos de tipo secuencia. Si el miembro es un número entero, el ejemplo dos podría dejar pasar listas y cadenas, así como diccionarios.

Sobre programación asertiva :

Las afirmaciones son una forma sistemática de verificar que el estado interno de un programa es el esperado por el programador, con el objetivo de detectar errores. En particular, son buenos para detectar suposiciones falsas que se hicieron al escribir el código, o el abuso de una interfaz por parte de otro programador. Además, pueden actuar como documentación en línea hasta cierto punto, haciendo obvias las suposiciones del programador. ("Explícito es mejor que implícito")

Los conceptos mencionados a veces están en conflicto, por lo que cuento con los siguientes factores al elegir si no hago ninguna validación de datos, si hago una validación fuerte o uso afirmaciones:

  1. Fuerte validación. Por validación fuerte me refiero a generar una excepción personalizada ( ApiErrorpor ejemplo). Si mi función / método es parte de una API pública, es mejor validar el argumento para mostrar un buen mensaje de error sobre un tipo inesperado. Al marcar el tipo, no me refiero a usar solo isinstance, sino también si el objeto pasado admite la interfaz necesaria (escritura de pato). Si bien documenté la API y especifiqué el tipo esperado y el usuario podría querer usar mi función de manera inesperada, me siento más seguro cuando verifico los supuestos. Usualmente uso isinstancey si luego quiero admitir otros tipos o patos, cambio la lógica de validación.

  2. Programación asertiva. Si mi código es nuevo, uso afirma mucho. ¿Cuáles son tus consejos sobre esto? ¿Luego eliminas las afirmaciones del código?

  3. Si mi función / método no es parte de una API, pero pasa algunos de sus argumentos a otro código no escrito, estudiado o probado por mí, hago muchas afirmaciones de acuerdo con la interfaz llamada. Mi lógica detrás de esto: mejor falla en mi código, luego en algún lugar 10 niveles más profundos en stacktrace con un error incomprensible que obliga a depurar mucho y luego agregar la afirmación a mi código de todos modos.

¿Comentarios y consejos sobre cuándo usar o no usar la validación de tipo / valor, afirma? Lo siento, no es la mejor formulación de la pregunta.

Por ejemplo, considere la siguiente función, donde Customeres un modelo declarativo SQLAlchemy:

def add_customer(self, customer):
    """Save new customer into the database.
    @param customer: Customer instance, whose id is None
    @return: merged into global session customer
    """
    # no validation here at all
    # let's hope SQLAlchemy session will break if `customer` is not a model instance
    customer = self.session.add(customer)
    self.session.commit()
    return customer

Entonces, hay varias formas de manejar la validación:

def add_customer(self, customer):
    # this is an API method, so let's validate the input
    if not isinstance(customer, Customer):
        raise ApiError('Invalid type')
    if customer.id is not None:
        raise ApiError('id should be None')

    customer = self.session.add(customer)
    self.session.commit()
    return customer

o

def add_customer(self, customer):
    # this is an internal method, but i want to be sure
    # that it's a customer model instance
    assert isinstance(customer, Customer), 'Achtung!'
    assert customer.id is None

    customer = self.session.add(customer)
    self.session.commit()
    return customer

¿Cuándo y por qué usaría cada uno de estos en el contexto de la escritura, verificación de tipos y validación de datos?


1
no debe eliminar afirmaciones al igual que las pruebas unitarias a menos que sea por razones de rendimiento
Bryan Chen

Respuestas:


4

Déjame darte algunos principios rectores.

Principio # 1. Como se describe en http://docs.python.org/2/reference/simple_stmts.html, la sobrecarga de rendimiento de las afirmaciones se puede eliminar con una opción de línea de comandos, mientras todavía está allí para la depuración. Si el rendimiento es un problema, hazlo. Deja las afirmaciones. (¡Pero no hagas nada importante en las afirmaciones!)

Principio # 2. Si está afirmando algo y tendrá un error fatal, utilice una afirmación. No tiene absolutamente ningún valor hacer otra cosa. Si alguien más tarde quiere cambiar eso, puede cambiar su código o evitar esa llamada al método.

Principio # 3. No rechaces algo solo porque creas que es algo estúpido. Entonces, ¿qué pasa si su método permite cadenas? Si funciona, funciona.

Principio # 4. No permita las cosas que son signos de posibles errores. Por ejemplo, considere pasar un diccionario de opciones. Si ese diccionario contiene cosas que no son opciones válidas, entonces esa es una señal de que alguien no entendió su API, o si no tuvo un error tipográfico. Volar sobre eso es más probable que atrape un error tipográfico que evitar que alguien haga algo razonable.

Basado en los primeros 2 principios, su segunda versión puede ser descartada. Cuál de los otros dos prefiere es una cuestión de gustos. ¿Cuál crees que es más probable? Que alguien pasará a un no cliente add_customery las cosas se romperán (en cuyo caso se prefiere la versión 3), o que alguien en algún momento querrá reemplazar a su cliente con un objeto proxy de algún tipo que responda a todos los métodos correctos (en cuyo caso se prefiere la versión 1).

Personalmente, he visto ambos modos de falla. Tiendo a ir con la versión 1 por el principio general de que soy vago y que es menos tipeado. (Además, ese tipo de falla generalmente tiende a aparecer tarde o temprano de una manera bastante obvia. Y cuando quiero usar un objeto proxy, me molesta mucho la gente que me ha atado las manos). Pero hay programadores que respeto a quién iría por el otro lado.


Prefiero v.3, especialmente al diseñar la interfaz, escribir nuevas clases y métodos. También considero que la v.3 es útil para los métodos API, porque mi código es nuevo para otros. Creo que el enfoque asertivo es un buen compromiso, porque se elimina en la producción cuando se ejecuta en modo optimizado. > Volar sobre eso es más probable que atrape un error tipográfico que evitar que alguien haga algo razonable. <Entonces, ¿no te importa tener esa validación?
warvariuc

Pongámoslo de esta manera. Encuentro que la herencia se asigna mal a cómo me gusta evolucionar los diseños. Prefiero composición Así que evito afirmar que esto tiene que ser de esa clase. Pero no me opongo a las afirmaciones en las que creo que me salvan algo.
btilly
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.