Python3注解+可变参数实现

Posted 诸子流

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Python3注解+可变参数实现相关的知识,希望对你有一定的参考价值。

一、说明

1.1 关于可变参数

所谓可变参数,最主要是指传递给被调用函数的参数的个数可以是不固定的。

可变参数应该来说是很常见的,比如C的标准main函数就写成int main(int argc, ** char argv),再比如很常用的print()函数就是最典型的可变参数函数。

但一方面在很长一段时间内并不能理解main函数其实和普通函数没什么区别,另一方面觉得print()是系统函数实现很复杂,所以一直都没弄懂如何实现可变参数应该传递。

 

1.2 关于注解

关于注解这个东西,最早是在大学学java的时候经常会看到某些方法上边@override之类的东西,一方面不知道其作用但另一方面似乎去掉也没什么影响,所以一直都不怎么在意。

今年去看开发的代码也看到很多注解,问其用途基本都和网上类似“为了开启XXX功能我们需要添加@XXX注解的”/”添加@XXX注解是为了开启XXX功能“,不知其原理感觉颇为难受所以自己来研究了一翻。

 

1.3 关于可变参数和注解有什么关系

注解和可变参数,在感觉上是没什么关系的。但当我去实现注解,发现要让注解可作用于不同参数个数的函数时需要解决可变参数问题。

而且应当来讲注解作用于不同参数个数的函数是个普遍的需求,所以注解和可变参数关系还是关联很大的。

 

二、可变参数实现本质

python中定义/调用函数时,参数有两种方式,一种是以位置参数(如test(a)),一种是键值参数(如test(a=1))。不管是定义还是调用,位置参数必须在键值形式参数之前。

使用的演示程序如下:

# 一个简单的求合函数
def calc_sum(a, b, c, d, e):
    sum_value = a + b + c + d + e
    return sum_value

# 此函数只单纯调用calc_sum()
def call_calc_sum(a,*args,**kwargs):
    sum_value = calc_sum(a,*args,**kwargs)
    return sum_value

call_calc_sum(1, 2, 3, e=4, d=5)

 

2.1 从参数变为*args, **kwargs的过程

被调用函数通过以下步骤提取参数:

第一步,如果前面有非*args, **kwargs的参数,则在传来的参数中先分配给他。比如这里在*args前面有a,所以就把第一个参数值1赋给a。

第二步,将其他非k=v形式的参数,组成元组赋值为args。比如这是把下来的2,3组成(2,3)。

第三步,将其他的k=v形式的参数,组成字典赋值给kwargs。比如这里把e=4,d=4组成{\'e\': 4, \'d\': 5}。

 

2.2 从*args, **kwargs变回具体参数的过程

被调用函数通过以下步骤提取参数:

第一步,如果开头有非*args, **kwargs的参数,则将按正常参数解析。如1赋给第一个参数a。

第二步,将元组参数按顺序分发给接下来的参数。如将2赋给下来的第二个参数b,再将3赋给下来的第三个参数c。

第三步,将字典参数,按设置的k/v分发给对应的参数。如按e=4赋给第五个参数e,按d=5赋值给第四个参数d。

 

2.3 直接就解析和使用*args和**kwargs 

def calc_sum(a, *args, **kwargs):
    sum_value = a
    for v in args:
        sum_value += v
    for k, v in kwargs.items():
        sum_value += v
    # 另外如果想判断一个参数有没有被传参,可以通过if "key_word" in kwargs来实现
    print(sum_value)


calc_sum(1, 2, 3, d=4, e=5)

 

2.4 函数定义是参数为*的作用

# 强制给*号之后的变量传值,必须以k=v形式,不然会报错
def calc_sum(a, b, c, *, d, e):
    sum_value = a + b + c + d + e
    print(sum_value)

# 这样调用会报错
# calc_sum(1, 2, 3, 4, e=5)
# 这样才能成功
calc_sum(1, 2, 3, d=4, e=5)

 

三、注解代码实现

3.1 最典型的注解函数实现

以下是一个最典型的注解函数的写法,自己写时只需要修改下被注解函数调用之前和调用之后的代码即可,其他部分完全可以原封不动照抄就完事了。

from functools import wraps


# 这一层用来接收被注解的函数的函数名(对于这里就是add_calc)。这一层必须存在
def decorate_function(decorated_function):
    print(f"decorate_function--decorated_function: {decorated_function}")
    # 这个注解的作用是保证其他函数调用被注解函数时,仍是被注解函数原名而不是注解函数名
    # 对于这里就是保证其他调用add_calc的函数,观测到的函数名仍是add_calc而不会变成decorate_function_real
    # 如果你并不关心这个差异,可以省掉这个注解
    @wraps(decorated_function)
    # 这一层用来接收被注解函数的参数(对于这里就是a, b),同时调用被注解函数。这一层必须存在
    # *args, **kwargs的妙处就是不管被注解函数有几个参数都可以承接
    def decorate_function_real(*args, **kwargs):
        print("decorate_function_real--do something before decorated_function exec...")
        # 调用被注解函数,参数原封不动地传过去即可
        result = decorated_function(*args, **kwargs)
        print("decorate_function_real--do something after decorated_function exec...")
        # 这个return最后返回
        # 原封不动地向上层返回被注解函数的返回值
        # 如果被注解函数没有返回值,可以省掉这个返回
        return result
    # 这个return紧接着被返回,因为返回的是函数,所以返回后就被执行
    return decorate_function_real

# 一个简单的求合函数
@decorate_function
def add_calc(a, b):
    add_result = a + b
    print(f"add_calc--calc result: {add_result}")
    return add_result


if __name__ == "__main__":
    a = 1
    b = 2
    add_result = add_calc(a, b)
    print(f"__main__--{a} + {b} = {add_result}")

执行结果如下:

 

3.2 把注解函数写在类内部

为了统一好看,可能我们想直接把注解函数也写在类内部,注解函数在类内部和在类外部写法上完全可以没区别。

from functools import wraps


class Test:
    def __init__(self):
        print("Test--init")

    # 这一层用来接收被注解的函数的函数名(对于这里就是add_calc)。这一层必须存在
    def decorate_function(decorated_function):
        print(f"decorate_function--decorated_function: {decorated_function}")
        # 这个注解的作用是保证其他函数调用被注解函数时,仍是被注解函数原名而不是注解函数名
        # 对于这里就是保证其他调用add_calc的函数,观测到的函数名仍是add_calc而不会变成decorate_function_real
        # 如果你并不关心这个差异,可以省掉这个注解
        @wraps(decorated_function)
        # 这一层用来接收被注解函数的参数(对于这里就是a, b),同时调用被注解函数。这一层必须存在
        # *args, **kwargs的妙处就是不管被注解函数有几个参数都可以承接
        def decorate_function_real(*args, **kwargs):
            print("decorate_function_real--do something before decorated_function exec...")
            # 调用被注解函数,参数原封不动地传过去即可
            result = decorated_function(*args, **kwargs)
            print("decorate_function_real--do something after decorated_function exec...")
            # 这个return最后返回
            # 原封不动地向上层返回被注解函数的返回值
            # 如果被注解函数没有返回值,可以省掉这个返回
            return result
        # 这个return先被返回,因为返回的是函数,所以返回后就被执行
        return decorate_function_real

    # 一个简单的求合函数
    @decorate_function
    def add_calc(self, a, b):
        add_result = a + b
        print(f"Test--calc result: {add_result}")
        return add_result


if __name__ == "__main__":
    obj = Test()
    a = 1
    b = 2
    add_result = obj.add_calc(a, b)
    print(f"__main__--{a} + {b} = {add_result}")
View Code

运行结果如下:

 

3.3 对类进行注解

有时我们会看到有对类进行注解的写法,对类进行注解,其实质相当于对类的__init__函数进行注解。一般用于统计类被实例化了多少次。

from functools import wraps


# 这一层用来接收被注解的函数的函数名(对于这里就是add_calc)。这一层必须存在
def decorate_function(decorated_function):
    print(f"decorate_function--decorated_function: {decorated_function}")
    # 这个注解的作用是保证其他函数调用被注解函数时,仍是被注解函数原名而不是注解函数名
    # 对于这里就是保证其他调用add_calc的函数,观测到的函数名仍是add_calc而不会变成decorate_function_real
    # 如果你并不关心这个差异,可以省掉这个注解
    @wraps(decorated_function)
    # 这一层用来接收被注解函数的参数(对于这里就是a, b),同时调用被注解函数。这一层必须存在
    # *args, **kwargs的妙处就是不管被注解函数有几个参数都可以承接
    def decorate_function_real(*args, **kwargs):
        print("decorate_function_real--do something before decorated_function exec...")
        # 调用被注解函数,参数原封不动地传过去即可
        result = decorated_function(*args, **kwargs)
        print("decorate_function_real--do something after decorated_function exec...")
        # 这个return最后返回
        # 原封不动地向上层返回被注解函数的返回值
        # 如果被注解函数没有返回值,可以省掉这个返回
        return result
    # 这个return先被返回,因为返回的是函数,所以返回后就被执行
    return decorate_function_real

@decorate_function
class Test:
    def __init__(self):
        print("Test--init")

    # 一个简单的求合函数
    def add_calc(self, a, b):
        add_result = a + b
        print(f"Test--calc result: {add_result}")
        return add_result


if __name__ == "__main__":
    obj = Test()
    a = 1
    b = 2
    add_result = obj.add_calc(a, b)
    print(f"__main__--{a} + {b} = {add_result}")
View Code

执行结果如下,可以看到注解函数只在__init__函数被调用时运行:

 

3.4 使用类进行注解

在更少数情况下,我们还会看到使用类进行注解。

class decorate_class:
    # __init__函数用于承接被注解函数函数名
    def __init__(self, decorated_function):
        self.decorated_function = decorated_function
        print("decorate_class--init")

    # __call__函数用来承接被注解函数的参数
    def __call__(self, *args, **kwargs):
        print("decorate_function_real--do something before decorated_function exec...")
        # 调用被注解函数,参数原封不动地传过去即可
        result = self.decorated_function(*args, **kwargs)
        print("decorate_function_real--do something after decorated_function exec...")
        # 这个return最后返回
        # 原封不动地向上层返回被注解函数的返回值
        # 如果被注解函数没有返回值,可以省掉这个返回
        return result


# 一个简单的求合函数
@decorate_class
def add_calc(a, b):
    add_result = a + b
    print(f"Test--calc result: {add_result}")
    return add_result


if __name__ == "__main__":
    a = 1
    b = 2
    add_result = add_calc(a, b)
    print(f"__main__--{a} + {b} = {add_result}")
View Code

执行结果如下:

 

3.5 注解函数/类带参数的写法

前面4小节的写法中我们的注解函数/类都是没有带有参数的,当我们想不管三七二十一给注解函数传递参数时会引发报错。但有时我们确实会看到有些注解函数就是可以带参数的,如果我们就想给注解函数传递参数,我们该如何实现呢。

在不带参数的场景中,外层函数承接到的是被注解函数函数名、内层函数承接被注解函数的参数。当给注解函数传递参数时,外层函数承接到的是注解函数的参数、中间层函数承接到的是被注解函数函数名、内层函数承接被注解函数的参数。

总之,当要给注解函数传递参数时,要从不带参数时的两层函数结构,变成三层函数结构(对于注解类而言,就在__call__函数内再加一层函数)。

实现示例如下:

from functools import wraps

# 这一层用来接收注解函数的参数(对于这里就是"111", "222")。
def decorate_function_outer(param1, param2):
    print(f"decorate_function_outer--param1: {param1}")
    print(f"decorate_function_outer--param2: {param2}")
    # 这一层用来接收被注解的函数的函数名(对于这里就是add_calc)。这一层必须存在
    def decorate_function_inner(decorated_function):
        # 这个注解的作用是保证其他函数调用被注解函数时,仍是被注解函数原名而不是注解函数名
        # 对于这里就是保证其他调用add_calc的函数,观测到的函数名仍是add_calc而不会变成decorate_function_real
        # 如果你并不关心这个差异,可以省掉这个注解
        print(f"decorate_function_inner--decorated_function: {decorated_function}")
        @wraps(decorated_function)
        # 这一层用来接收被注解函数的参数(对于这里就是a, b),同时调用被注解函数。这一层必须存在
        # *args, **kwargs的妙处就是不管被注解函数有几个参数都可以承接
        def decorate_function_real(*args, **kwargs):
            print("decorate_function_real--do something before decorated_function exec...")
            # 调用被注解函数,参数原封不动地传过去即可
            result = decorated_function(*args, **kwargs)
            print("decorate_function_real--do something after decorated_function exec...")
            # 这个return最后返回
            # 原封不动地向上层返回被注解函数的返回值
            # 如果被注解函数没有返回值,可以省掉这个返回
            return result
        # 这个return紧接着被返回,因为返回的是函数,所以返回后就被执行
        return decorate_function_real
    # 这个return最先被返回,因为返回的是函数,所以返回后就被执行
    return decorate_function_inner


# 一个简单的求合函数
@decorate_function_outer("111", "222")
def add_calc(a, b):
    add_result = a + b
    print(f"add_calc--calc result:: {add_result}")
    return add_result


if __name__ == "__main__":
    a = 1
    b = 2
    add_result = add_calc(a, b)
    print(f"__main__--{a} + {b} = {add_result}")
View Code

运行结果如下:

 

参考:

https://www.python-course.eu/python3_decorators.php

以上是关于Python3注解+可变参数实现的主要内容,如果未能解决你的问题,请参考以下文章

Python3新特性 类型注解 以及 点点点

python3-可变参数

Python3----函数的参数是可变参数,将传进来的参数转成列表

python3 关键字和可变参数笔记

Python3.x基础学习-函数用法

(十五)python3 可变长参数(arg,*args,**kwargs)