Je voudrais définir quelques décorateurs génériques pour vérifier les arguments avant d'appeler des fonctions.
Quelque chose comme:
@checkArguments(types = ['int', 'float'])
def myFunction(thisVarIsAnInt, thisVarIsAFloat)
''' Here my code '''
pass
Notes de côté:
De la Décorateurs pour fonctions et méthodes :
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
Usage:
@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'>
Sur Python 3.3, vous pouvez utiliser les annotations de fonctions et inspecter:
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)
S'il y a une erreur de validation, affiche:
AssertionError: call to xXy(x=12, y=3): y failed <lambda>)
S'il n'y a pas d'erreur de validation, affiche:
wrapped call to xXy({'y': 3.0, 'x': 12})
returning 36.0 with annotation: "('x times y', 'in X and Y units')"
Vous pouvez utiliser une fonction plutôt qu'un lambda pour obtenir un nom lors de l'échec de l'assertion.
Comme vous le savez certainement, ce n'est pas Pythonic de rejeter un argument uniquement en fonction de son type.
L’approche Pythonic est plutôt "essayez d’y faire face en premier"
C'est pourquoi je préférerais faire un décorateur pour convertir les arguments
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 cette façon, votre fonction est alimentée avec le type que vous attendez Mais si le paramètre peut trembler comme un float, il est accepté
@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'
J'utilise cette astuce (avec la méthode de conversion appropriée) pour traiter vectors .
De nombreuses méthodes que j'écris attendent de la classe MyVector car elle comporte de nombreuses fonctionnalités; mais parfois vous voulez juste écrire
transpose ((2,4))
Pour appliquer des arguments de chaîne à un analyseur syntaxique pouvant générer des erreurs cryptiques avec une entrée non chaîne, j'ai écrit ce qui suit pour éviter les appels d'allocation et de fonction:
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
Toutes ces publications semblent obsolètes - pint fournit désormais cette fonctionnalité intégrée. Voir ici . Copié ici pour la postérité:
Vérification de la dimensionnalité Lorsque vous souhaitez utiliser les quantités de pinte comme entrées pour vos fonctions, pint fournit un wrapper pour s’assurer que les unités sont de type correct - ou plus précisément, ils correspondent à l'attendu dimensionnalité de la quantité physique.
Semblable à wraps (), vous pouvez passer Aucun pour ignorer la vérification de certains paramètres, mais le type de paramètre de retour n'est pas vérifié.
>>> mypp = ureg.check('[length]')(pendulum_period)
Dans le format décorateur:
>>> @ureg.check('[length]') ... def pendulum_period(length): ... return 2*math.pi*math.sqrt(length/G)
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
J'ai une version légèrement améliorée de @jbouwmans sollution, en utilisant le module de décorateur en python, ce qui rend le décorateur entièrement transparent et conserve non seulement la signature, mais aussi la documentation, et peut-être la manière la plus élégante d'utiliser des décorateurs.
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
Je pense que la réponse de Python 3.5 à cette question est beartype . Comme expliqué dans ce post , il comporte des fonctionnalités pratiques. Votre code ressemblerait alors à ceci
from beartype import beartype
@beartype
def sprint(s: str) -> None:
print(s)
et aboutit à
>>> 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'>