puede - qué es un decorador en python
¿Cómo usar los decoradores de Python para verificar los argumentos de la función? (7)
Como seguramente sabrás, no es pythonic rechazar un argumento solo por su tipo.
Enfoque pitónico 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 vectors .
Muchos métodos que escribo esperan la clase MyVector, ya que tiene muchas funcionalidades; pero alguna vez solo quieres escribir
transpose ((2,4))
Me gustaría definir algunos decoradores genéricos 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:
- La comprobación de tipos está aquí para mostrar un ejemplo.
- Estoy usando Python 2.7, pero Python 3.0 también sería interesante
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 "<stdin>", line 1, in <module>
File "<string>", line 13, in func_beartyped
TypeError: sprint() parameter s=3 not of <class ''str''>
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 <type ''int''>
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, imprime:
AssertionError: call to xXy(x=12, y=3): y failed <lambda>)
Si no hay un error de validación, se 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.
Para imponer argumentos de cadena a un analizador que generarí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
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
Todas estas publicaciones parecen estar desactualizadas: pinta ahora proporciona esta funcionalidad incorporada. Vea here . 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 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)