2009-08-14 28 views
53

No presté tanta atención al desarrollo de Python 3 como me hubiera gustado, y solo noté algunos nuevos e interesantes cambios de sintaxis. Específicamente de this SO answer función de parámetros de anotación:Python 3 y tipado estático

def digits(x:'nonnegative number') -> "yields number's digits": 
    # ... 

Sin saber nada de esto, pensé que tal vez podría ser utilizado para la aplicación de tipos estáticos en Python!

Después de algunas búsquedas, no parecía haber una discusión mucho con respecto a (totalmente opcional) tipos estáticos en Python, tal como el que se menciona en PEP 3107 y "Adding Optional Static Typing to Python" (y part 2)

..pero, no estoy aclarar cuánto ha progresado esto ¿Hay alguna implementación de tipado estático, usando el parámetro-anotación? ¿Alguna de las ideas de tipo parametrizado lo convirtió en Python 3?

Respuesta

31

¡Gracias por leer mi código!

De hecho, no es difícil crear un ejecutor de anotación genérico en Python. Aquí está mi opinión:

'''Very simple enforcer of type annotations. 

This toy super-decorator can decorate all functions in a given module that have 
annotations so that the type of input and output is enforced; an AssertionError is 
raised on mismatch. 

This module also has a test function func() which should fail and logging facility 
log which defaults to print. 

Since this is a test module, I cut corners by only checking *keyword* arguments. 

''' 

import sys 

log = print 


def func(x:'int' = 0) -> 'str': 
    '''An example function that fails type checking.''' 
    return x 


# For simplicity, I only do keyword args. 
def check_type(*args): 
    param, value, assert_type = args 
    log('Checking {0} = {1} of {2}.'.format(*args)) 
    if not isinstance(value, assert_type): 
     raise AssertionError(
      'Check failed - parameter {0} = {1} not {2}.' 
      .format(*args)) 
    return value 

def decorate_func(func):  
    def newf(*args, **kwargs): 
     for k, v in kwargs.items(): 
      check_type(k, v, ann[k]) 
     return check_type('<return_value>', func(*args, **kwargs), ann['return']) 

    ann = {k: eval(v) for k, v in func.__annotations__.items()} 
    newf.__doc__ = func.__doc__ 
    newf.__type_checked = True 
    return newf 

def decorate_module(module = '__main__'): 
    '''Enforces type from annotation for all functions in module.''' 
    d = sys.modules[module].__dict__ 
    for k, f in d.items(): 
     if getattr(f, '__annotations__', {}) and not getattr(f, '__type_checked', False): 
      log('Decorated {0!r}.'.format(f.__name__)) 
      d[k] = decorate_func(f) 


if __name__ == '__main__': 
    decorate_module() 

    # This will raise AssertionError. 
    func(x = 5) 

Dada esta simplicidad, es extraño a primera vista que esto no sea convencional. Sin embargo, creo que hay buenas razones por las cuales no es tan útil como podría parecer.En general, la verificación de tipos ayuda porque si agrega un entero y un diccionario, es probable que haya cometido un error obvio (y si quiere decir algo razonable, todavía es mejor que explícito que implícito).

Pero en la vida real a menudo se mezclan cantidades del mismo tipo equipo como se ve por el compilador, pero claramente diferente tipo humano, por ejemplo, el siguiente fragmento contiene un error evidente:

height = 1.75 # Bob's height in meters. 
length = len(sys.modules) # Number of modules imported by program. 
area = height * length # What's that supposed to mean??? 

Cualquier humana debería ver inmediatamente un error en la línea anterior siempre que conozca el 'tipo humano' de las variables height y length aunque parezca que la computadora es perfectamente legal multiplicación de int y float.

Hay más que se puede decir acerca de las posibles soluciones a este problema, pero la aplicación de "tipos de ordenador es, al parecer una solución de la mitad, por lo que, al menos en mi opinión, es peor que ninguna solución. Es la misma razón por la cual Systems Hungarian es una idea terrible, mientras que Apps Hungarian es excelente. Hay más en el informativo post of Joel Spolsky.

Ahora bien, si alguien fue implementar algún tipo de biblioteca de terceros Pythonic que asignar automáticamente a los datos del mundo real su tipo humano y luego se encargó de transformar ese tipo como width * height -> area y hacer cumplir el registro de entrada con función anotaciones, ¡Creo que sería un tipo de control que la gente realmente podría usar!

+0

Y esto es lo que acabo de escribir sobre los peligros del tipado fuerte: http://stackoverflow.com/questions/1251791/what-are-the-limits-of-type-checking-and-type-systems/1276675#1276675 –

+32

What si Bob tuvo que pintar un metro cuadrado de su habitación con pintura roja por cada módulo que importa? –

+2

Luego deberías usar un cast de tipo explícito para castigar a Bob. Todos los sistemas de mecanografía fuerte, ya sean estáticos o dinámicos, tienen disposiciones para los tipos de conversión: p. ''cadena '+ 5' está prohibida, pero''cadena' + str (5) 'está bien. –

14

Como se menciona en esa PEP, la comprobación de tipos estáticos es una de las posibles aplicaciones para las que se pueden utilizar las anotaciones, pero la dejan en manos de bibliotecas de terceros para decidir cómo hacerlo. Es decir, no va a haber una implementación oficial en core python.

En lo que respecta a las implementaciones de terceros, hay algunos fragmentos (como http://code.activestate.com/recipes/572161/), que parecen hacer el trabajo bastante bien.

EDIT:

Como nota, quiero mencionar que el control de comportamiento es preferible a chequear el tipo, luego pienso verificación de tipos estáticos no es tan grande una idea. Mi respuesta anterior está dirigida a responder la pregunta, no porque me escriba de esa manera.

+2

Tenga en cuenta que el fragmento mencionado impone la comprobación dinámica de tipo, no la verificación de tipo ** estática **. –

+1

@Andrea Zillio: pero podría ser posible que IDEs o scripts verifiquen el código fuente antes de que se ejecute (estático) cuando se proporciona una anotación. Piense en def f (x: int) -> int y alguien intente hacer f ('prueba'). – Joschua

+0

@Joschua: lamentablemente no puedo ver a los IDE saltando a bordo con ninguna solución no oficial. – Timmmm

12

La "tipificación estática" en Python solo se puede implementar para que la verificación de tipos se realice en tiempo de ejecución, lo que significa que ralentiza la aplicación. Por lo tanto, no quiere eso como una generalidad. En su lugar, desea que algunos de sus métodos verifiquen sus entradas. Esto se puede hacer fácilmente con aseveraciones sencillas, o con decoradores si (erróneamente) cree que lo necesita mucho.

También existe una alternativa a la comprobación de tipos estáticos, y es utilizar una arquitectura de componentes orientada a aspectos como The Zope Component Architecture. En lugar de verificar el tipo, lo adaptas. Así que en lugar de:

assert isinstance(theobject, myclass) 

hacer esto:

theobject = IMyClass(theobject) 

Si theObject ya implementa IMyClass no pasa nada. Si no lo hace, se buscará un adaptador que envuelva lo que el objeto es a IMyClass, y se usará en lugar del objeto. Si no se encuentra un adaptador, se obtiene un error.

Esto combina el dinamismo de Python con el deseo de tener un tipo específico de una manera específica.

+5

Las anotaciones pueden ser usadas por herramientas de análisis estático como pylint para realizar una comprobación de cordura muy similar al tipado estático dentro de regiones del código donde el tipo de variable o parámetro es cognoscible y existen anotaciones en las API a las que se llama. – gps

+0

@LennartRegebro: Esta es una idea brillante y simple. Lucho en cada idioma con el problema de "cuerda no vacía".La mayoría de los métodos que aceptan cadenas como parámetros no están bien preparados para cadenas vacías o todas las cadenas de espacios en blanco. En la mayoría de los casos, estos son valores no válidos, pero pocos programadores se preocupan por verificarlo. Me gusta su idea de utilizar un tipo de delegación (pseudo-) para aplicar restricciones de tipo/valor * una vez *: durante la construcción. Ejemplo: 'str' ->' NonEmptyStr' o 'Text' (que implica que no está vacío y no todos los espacios en blanco). – kevinarpe

+0

Sí, pero como todavía puedes pasar una cadena vacía, eso no soluciona nada. A menos que * también * escriba check en la función, y luego el pseudo-tipo en realidad no agrega nada. –

12

Esta no es una respuesta directa a la pregunta, pero descubrí una horquilla de Python que agrega tipado estático: mypy-lang.org, por supuesto no se puede confiar en ella, ya que aún es un esfuerzo pequeño, pero interesante.

0

Claro, la tipificación estática parece un poco "antiponética" y no la uso todo el tiempo. Pero hay casos (por ejemplo, clases anidadas, como en el análisis de lenguaje específico del dominio) donde realmente puede acelerar su desarrollo.

Entonces prefiero usar beartype explicado en este post *. Viene con un git repo, pruebas y una explicación de lo que puede y lo que no puede hacer ... y me gusta el nombre;)

* Por favor, no presten atención a la diatriba de Cecil sobre por qué Python no lo hace vienen con baterías incluidas en este caso.