如何使用Python decorators检查函数参数?

2024-04-19 06:56:40 发布

您现在位置:Python中文网/ 问答频道 /正文

我想在调用某些函数之前定义一些泛型修饰符来检查参数。

类似于:

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

旁注:

  1. 这里的类型检查只是为了显示一个示例
  2. 我使用的是Python2.7,但Python3.0也很有趣

Tags: 函数参数here定义mydeffloat修饰符
3条回答

Decorators for Functions and Methods

Python 2

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

Python 3

在Python 3中,func_code已更改为__code__func_name已更改为__name__

def accepts(*types):
    def check_accepts(f):
        assert len(types) == f.__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.__name__ = f.__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'>

arg2可以是intfloat

在Python3.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')"

可以使用函数而不是lambda在断言失败中获取名称。

正如你当然知道的,仅仅根据参数的类型来拒绝它不是Python 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'

我使用这个技巧(使用适当的转换方法)来处理vectors
我编写的许多方法都期望MyVector类具有很多功能;但是有时您只想编写

transpose ((2,4))

相关问题 更多 >