¿Cómo usar los decoradores de Python para verificar los argumentos de la función?

Me gustaría definir algunos decoradores generics para verificar los argumentos antes de llamar a algunas funciones.

Algo como:

@checkArguments(types = ['int', 'float']) def myFunction(thisVarIsAnInt, thisVarIsAFloat) ''' Here my code ''' pass 

Notas al margen:

  1. La comprobación de tipos está aquí para mostrar un ejemplo.
  2. Estoy usando Python 2.7 pero Python 3.0 debería ser interesante también

    De los Decoradores para Funciones y Métodos :

     def accepts(*types): def check_accepts(f): assert len(types) == f.func_code.co_argcount def new_f(*args, **kwds): for (a, t) in zip(args, types): assert isinstance(a, t), \ "arg %r does not match %s" % (a,t) return f(*args, **kwds) new_f.func_name = f.func_name return new_f return check_accepts 

    Uso:

     @accepts(int, (int,float)) def func(arg1, arg2): return arg1 * arg2 func(3, 2) # -> 6 func('3', 2) # -> AssertionError: arg '3' does not match  

    En Python 3.3, puede usar anotaciones de funciones e inspeccionar:

     import inspect def validate(f): def wrapper(*args): fname = f.__name__ fsig = inspect.signature(f) vars = ', '.join('{}={}'.format(*pair) for pair in zip(fsig.parameters, args)) params={k:v for k,v in zip(fsig.parameters, args)} print('wrapped call to {}({})'.format(fname, params)) for k, v in fsig.parameters.items(): p=params[k] msg='call to {}({}): {} failed {})'.format(fname, vars, k, v.annotation.__name__) assert v.annotation(params[k]), msg ret = f(*args) print(' returning {} with annotation: "{}"'.format(ret, fsig.return_annotation)) return ret return wrapper @validate def xXy(x: lambda _x: 10<_x<100, y: lambda _y: isinstance(_y,float)) -> ('x times y','in X and Y units'): return x*y xy = xXy(10,3) print(xy) 

    Si hay un error de validación, se imprime:

     AssertionError: call to xXy(x=12, y=3): y failed ) 

    Si no hay un error de validación, imprime:

     wrapped call to xXy({'y': 3.0, 'x': 12}) returning 36.0 with annotation: "('x times y', 'in X and Y units')" 

    Puede usar una función en lugar de un lambda para obtener un nombre en el fallo de aserción.

    Como seguramente sabes, no es pythonic rechazar un argumento solo por su tipo.
    Enfoque pythonico es más bien “tratar de lidiar con él primero”
    Por eso preferiría hacer un decorador para convertir los argumentos.

     def enforce(*types): def decorator(f): def new_f(*args, **kwds): #we need to convert args into something mutable newargs = [] for (a, t) in zip(args, types): newargs.append( t(a)) #feel free to have more elaborated convertion return f(*newargs, **kwds) return new_f return decorator 

    De esta manera, su función se alimenta con el tipo que espera, pero si el parámetro puede mostrarse como un flotador, se acepta

     @enforce(int, float) def func(arg1, arg2): return arg1 * arg2 print (func(3, 2)) # -> 6.0 print (func('3', 2)) # -> 6.0 print (func('three', 2)) # -> ValueError: invalid literal for int() with base 10: 'three' 

    Utilizo este truco (con el método de conversión adecuado) para tratar con vectores .
    Muchos métodos que escribo esperan la clase MyVector, ya que tiene muchas funcionalidades; pero alguna vez solo quieres escribir

     transpose ((2,4)) 

    Para imponer argumentos de cadena a un analizador que arrojaría errores crípticos cuando se proporcionara una entrada sin cadena, escribí lo siguiente, que intenta evitar la asignación y las llamadas de función:

     from functools import wraps def argtype(**decls): """Decorator to check argument types. Usage: @argtype(name=str, text=str) def parse_rule(name, text): ... """ def decorator(func): code = func.func_code fname = func.func_name names = code.co_varnames[:code.co_argcount] @wraps(func) def decorated(*args,**kwargs): for argname, argtype in decls.iteritems(): try: argval = args[names.index(argname)] except ValueError: argval = kwargs.get(argname) if argval is None: raise TypeError("%s(...): arg '%s' is null" % (fname, argname)) if not isinstance(argval, argtype): raise TypeError("%s(...): arg '%s': type is %s, must be %s" % (fname, argname, type(argval), argtype)) return func(*args,**kwargs) return decorated return decorator 

    Todas estas publicaciones parecen estar desactualizadas: pinta ahora proporciona esta funcionalidad incorporada. Vea aquí . Copiado aquí para la posteridad:

    Comprobación de la dimensionalidad Cuando desea que las cantidades de pinta se utilicen como entradas para sus funciones, pinta proporciona una envoltura para garantizar que las unidades sean del tipo correcto, o más precisamente, coincidan con la dimensionalidad esperada de la cantidad física.

    Al igual que en wraps (), puede pasar Ninguno para omitir la comprobación de algunos parámetros, pero el tipo de parámetro de retorno no está marcado.

     >>> mypp = ureg.check('[length]')(pendulum_period) 

    En el formato de decorador:

     >>> @ureg.check('[length]') ... def pendulum_period(length): ... return 2*math.pi*math.sqrt(length/G) 

    Tengo una versión ligeramente mejorada de @jbouwmans sollution, que utiliza el módulo decorador de Python, lo que hace que el decorador sea completamente transparente y mantiene no solo la firma sino también las cadenas de documentos en su lugar y podría ser la forma más elegante de usar decoradores

     from decorator import decorator def check_args(**decls): """Decorator to check argument types. Usage: @check_args(name=str, text=str) def parse_rule(name, text): ... """ @decorator def wrapper(func, *args, **kwargs): code = func.func_code fname = func.func_name names = code.co_varnames[:code.co_argcount] for argname, argtype in decls.iteritems(): try: argval = args[names.index(argname)] except IndexError: argval = kwargs.get(argname) if argval is None: raise TypeError("%s(...): arg '%s' is null" % (fname, argname)) if not isinstance(argval, argtype): raise TypeError("%s(...): arg '%s': type is %s, must be %s" % (fname, argname, type(argval), argtype)) return func(*args, **kwargs) return wrapper 

    Creo que la respuesta de Python 3.5 a esta pregunta es beartype . Como se explica en este post , viene con características útiles. Tu código se vería así

     from beartype import beartype @beartype def sprint(s: str) -> None: print(s) 

    y resultados en

     >>> sprint("s") s >>> sprint(3) Traceback (most recent call last): File "", line 1, in  File "", line 13, in func_beartyped TypeError: sprint() parameter s=3 not of  
     def decorator(function): def validation(*args): if type(args[0]) == int and \ type(args[1]) == float: return function(*args) else: print('Not valid !') return validation