python装饰器获取内部函数的变量值
Posted sysu_lluozh
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了python装饰器获取内部函数的变量值相关的知识,希望对你有一定的参考价值。
一、需求场景
在装饰器中需要获取被装饰的函数中的参数值,比如需要验证参数合法性的装饰器,比如下面的validation_project
装饰器需要使用函数中project_id
参数的值
装饰器定义
def validation_project(func):
def wrapper(*args, **kwargs):
func(*args, **kwargs)
return wrapper
装饰的函数
@validation_project
def celebrate(project_id, iv, message):
print(project_id, iv, message)
二、args[n]的方式
具体调用
celebrate('swapi', 20, 'lluozh')
查看在执行装饰器函数时wrapper(*args, **kwargs)
中args
和kwargs
的值
可以发现,celebrate('swapi', 20, 'lluozh')
调用方式中参数的值以touple的方式存储在args中,而kwargs为空dict
那装饰器可以如下的方式获取参数
# 装饰器定义
def validation_project(func):
def wrapper(*args, **kwargs):
project_id = args[0]
func(*args, **kwargs)
return wrapper
这样有一个可预见很明显的问题,既需要固定被装饰器装饰的函数的参数顺序,在具体的项目使用中很可能出现错误
三、wrapper(x, *args, **kwargs)的方式
为了解决上面出现错乱的问题,那是否可以将部分使用的参数写入到装饰器的参数列表中呢?
函数的定义
@validation_project
def celebrate(project_id, iv, message):
print(project_id, iv, message)
函数调用
celebrate('swapi', 20, 'lluozh')
装饰器的定义
def validation_project(func):
@functools.wraps(func)
def wrapper(project_id, *args, **kwargs):
# 如果项目id存在
if project_id == 'swapi':
# 执行函数
return func(*args, **kwargs)
else:
raise Exception
return wrapper
调试发现,执行时在装饰器中确实可以按照顺序得到project_id的值,但是执行具体函数时报错
TypeError: celebrate() missing 1 required positional argument: 'message'
报错少了一个参数,因为在return func(*args, **kwargs)
时,此时的args的值
可见args
少了一个project_id
的参数,那在执行return func(*args, **kwargs)
时手动增加project_id
参数
装饰器的定义修改为:
def validation_project(func):
@functools.wraps(func)
def wrapper(project_id, *args, **kwargs):
# 如果项目id存在
if project_id == 'swapi':
# 执行函数
return func(project_id, *args, **kwargs)
else:
raise Exception
return wrapper
这样的方式可以执行成果,但是其实获取的方式本质上还是args[0]
的方式,这样做其实还有另外一个问题
在多层装饰器同时使用时,参数的顺序无法控制,比如:
装饰器定义
def validation_project(func):
@functools.wraps(func)
def wrapper(project_id, *args, **kwargs):
# 如果项目id存在
if project_id == 'swapi':
# 执行函数
return func(project_id, *args, **kwargs)
else:
raise Exception
return wrapper
def validation_iv(func):
@functools.wraps(func)
def wrapper(iv, *args, **kwargs):
# 如果项目id存在
if iv == 20:
# 执行函数
return func(iv, *args, **kwargs)
else:
raise Exception
return wrapper
函数定义
@validation_iv
@validation_project
def celebrate(project_id, iv, message):
print(project_id, iv, message)
具体调用
celebrate('swapi', 20, 'lluozh')
其实在装饰器validation_iv
中iv获取到的值还是参数列表中的第一个参数,无法进行控制使用
四、kwargs.get(x)的方式
既然上面两种方式不可行,那是否可以获取到参数列表不是touple
类型,而是dict
类型,这样就可以根据需要获取对应的参数而不是根据index
在celebrate('swapi', 20, 'lluozh')
的调用方式中,kwargs
为一个空字典,根据函数参数func(*args, **kwargs)
的定义,将调用方式修改如下:
celebrate(project_id='swapi', iv=20, message='lluozh')
函数的定义如下
# 修改参数列表
@validation_project
def celebrate(**kwargs):
print(kwargs.get('project_id'), kwargs.get('iv'), kwargs.get('message'))
查看在执行装饰器函数时wrapper(*args, **kwargs)
中args
和kwargs
的值
可以发现,celebrate(project_id='swapi', iv=20, message='lluozh')
调用方式中参数的值以dict的方式存储在kwargs中,而args为空touple
那装饰器可以如下的方式获取参数
# 装饰器定义
def validation_project(func):
def wrapper(*args, **kwargs):
project_id = kwargs.get(project_id)
func(*args, **kwargs)
return wrapper
这样的方式需要修改函数的参数列表,因为这次修改是在原有项目代码中修改,因此修改量比较大
五、inspect.getcallargs的方式
可以使用inspect的getcallargs方法获取
装饰的函数
@validation_project
def celebrate(project_id, iv, message):
print(project_id, iv, message)
具体调用
celebrate('swapi', 20, 'lluozh')
装饰的函数和具体调用均不用修改
装饰器的定义如下:
# 装饰器定义
def validation_project(func):
def wrapper(*args, **kwargs):
all_args = inspect.getcallargs(func, *args, **kwargs)
project_id = all_args.get('project_id')
func(*args, **kwargs)
return wrapper
调试执行可以获取到all_args
执行中的值为:
即all_args.get(x)
可以获取到对应的参数的值,问题解决
六、inspect.signature(func)的方式
在使用时出现另外一个新的需求,即需要多个装饰器同时使用
函数的定义
@validation_iv
@validation_project
def celebrate(project_id, iv, message):
print(project_id, iv, message)
执行时发现,首先@validation_iv
中inspect.getcallargs(func, *args, **kwargs)
并未获取到预期的数据
此时
iv = inspect.getcallargs(func, *args, **kwargs).get('iv')
获取到iv的值为None
如何解决这个问题呢?
使用上面第二点和第五点结合的方式
装饰器的定义
def validation_project(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
project_id = inspect.getcallargs(func, *args, **kwargs).get('project_id')
# 如果项目id存在
if project_id == 'swapi':
# 执行函数
func(*args, **kwargs)
else:
raise Exception
return wrapper
def validation_iv(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
# 如果项目id存在
iv = args[1]
if iv == 20:
# 执行函数
func(*args, **kwargs)
else:
raise Exception
return wrapper
这样确实获取数据ok,但是接下来使用装饰器很容易造成问题
那还有什么其他的方式么?
inspect
中提供了另外的方法,查看inspect
模块的源码,signature方法定义如下:
def signature(obj, *, follow_wrapped=True):
"""Get a signature object for the passed callable."""
return Signature.from_callable(obj, follow_wrapped=follow_wrapped)
可以使用signature方法获取函数参数的值
装饰器的定义
def validation_project(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
project_id = inspect.signature(func).bind_partial(*args, **kwargs).arguments.get('project_id')
# 如果项目id存在
if project_id == 'swapi':
# 执行函数
func(*args, **kwargs)
else:
raise Exception
return wrapper
函数调用
celebrate('swapi', 20, 'lluozh')
调试上面代码,函数获取的问题解决
以上是关于python装饰器获取内部函数的变量值的主要内容,如果未能解决你的问题,请参考以下文章
python 函数及变量作用域及装饰器decorator @详解