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)argskwargs的值

可以发现,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)argskwargs的值

可以发现,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_ivinspect.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 @详解

python装饰器内获取函数有用信息方法

JavaScript,当变量作为参数传递时更改函数内部的变量值[重复]

python使用上下文对代码片段进行计时,非装饰器

Python3装饰器

Python的闭包和装饰器