Как использовать декораторы Python для проверки аргументов функции?

Я хотел бы определить некоторые общие декораторы для проверки аргументов перед вызовом некоторых функций.

Что-то вроде:

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

Боковые заметки:

  1. Проверка типов – вот здесь, чтобы показать пример
  2. Я использую Python 2.7, но Python 3.0 тоже интересен

6 Solutions collect form web for “Как использовать декораторы Python для проверки аргументов функции?”

От декораторов для функций и методов :

 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 

Применение:

 @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'> 

На Python 3.3 вы можете использовать аннотации функций и проверить:

 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) 

Если есть ошибка проверки, печатает:

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

Если ошибка проверки не выполняется, печатает:

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

Вы можете использовать функцию, а не лямбду, чтобы получить имя в ошибке утверждения.

Как вы, конечно, знаете, это не pythonic, чтобы отклонять аргумент только на основе его типа.
Питонический подход скорее «попытается разобраться с ним в первую очередь»,
Вот почему я предпочел бы сделать декоратор для преобразования аргументов

 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 

Таким образом, ваша функция подается с ожидаемым вами типом. Но если параметр может окунуться в поход, как поплавок, он принимается

 @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' 

Я использую этот трюк (с надлежащим методом конвертации) для обработки векторов .
Многие методы, которые я пишу, предполагают класс MyVector, поскольку он имеет множество функциональных возможностей; но когда-то вы просто хотите написать

 transpose ((2,4)) 

Чтобы принудительно вводить строковые аргументы в синтаксический анализатор, который генерировал бы критические ошибки при предоставлении нестрокового ввода, я написал следующее, которое пытается избежать вызовов распределения и функций:

 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 

У меня есть немного улучшенная версия решения @jbouwmans, использующая модуль декоратора python, который делает декоратор полностью прозрачным и держит не только подпись, но и docstrings на месте и может быть самым элегантным способом использования декораторов

 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 

Я думаю, что Python 3.5 ответит на этот вопрос beartype . Как поясняется в этом сообщении, он имеет удобные функции. Тогда ваш код будет выглядеть следующим образом:

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

и приводит к

 >>> 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'> 
Python - лучший язык программирования в мире.