高阶python | 装饰器

python版本:3.10.0

在学习装饰器前先了解一下闭包

阿-岳同学【python技巧060】形象理解闭包,玩转闭包

通过视频首先可以了解到主要的三个知识点

  1. 闭包是嵌套结构
  2. 内层函数有调用外层函数的变量为闭包,同时内层函数是闭包函数(所以闭包是函数,函数不一定是闭包)
  3. 内层函数可以调用外层函数的变量,而外层函数不能访问内层函数的变量
  4. 内层函数对外层函数中变量的值进行修改的时候需要使用nonlocal关键词(仅调用不需要该关键词直接使用)
  5. 对闭包的调用时对外层变量进行的是深拷贝

这是一个简单的闭包案例

def outer_func(x):
    def inner_func(y):
        return x + y

    return inner_func


add_5 = outer_func(5)
print(add_5(3))  # 输出 8
print(add_5(6))  # 输出 11

闭包结构中,外层函数先接一个x的值,在下方调用时传参为5,所以在闭包结构中x=5,然后进入内层函数(闭包函数),对内层函数调用时传参为3,所以在闭包结构中y=3,而后内层返回x+y的结果值给外层,外层返回内层返回的结果值给调用的print函数输出到终端。而后的y传6原理同样。

高阶python | 装饰器_第1张图片

然后来讲装饰器

首先有一个普通的函数

def count_nums(n):
    for i in range(n):
        for j in range(2000):
            pass

这是一个有双层嵌套循环的函数,接收n为外层循环的范围,内层循环两千次

count_nums(33_0000)
print('运行结束')

我们调用这个函数并且把外层函数的范围n传入函数

运行

然后等啊等,等了大概6秒才结束运行

运行结束

这样等了这么久,那么就好奇这个函数具体的运行时间有多久,但又不想破坏这个函数,此时我希望为这个函数加上一个计算运行时间的装饰器来计算运行时间

首先构造一个装饰器

装饰器是在闭包的基础上发展的结构,但其外层函数接收的不再是接收简单的数值,而是以一个函数对象打包接收

首先计算时间必要的time包是必要导入的

import time

然后构造装饰器

def make_timer(func):  # 装饰器
    def wrapper(*args, **kwargs):
        t1 = time.time()
        ret_val = func(*args, **kwargs)
        t2 = time.time()
        print('Time elapsed was', t2 - t1)
        return ret_val

    return wrapper

要让外层函数接收被装饰的函数,然后将被装饰的函数的调用夹在t1和t2之间作为闭包函数的内层函数,同时把内层函数接收到的传参传给被修饰函数,将被修饰函数的对象赋值给ret_vsl并在内层函数的结尾返回给外层,外层函数再返回给调用的地方

在原先调用被装饰的函数前,被装饰后的函数与装饰前的函数已经是两个函数,所以为保证不影响调用被装饰的函数和装饰器中的功能,我们将装饰器装饰后的新函数的对象同名赋值给老函数名

count_nums = make_timer(count_nums)

此时再调用该函数名,其功能已经被装饰器装饰,旧名新用

完整代码如下:

import time


def make_timer(func):  # 装饰器
    def wrapper(*args, **kwargs):
        t1 = time.time()
        ret_val = func(*args, **kwargs)
        t2 = time.time()
        print('Time elapsed was', t2 - t1)
        return ret_val

    return wrapper


# @make_timer
def count_nums(n):
    for i in range(n):
        for j in range(2000):
            pass


count_nums = make_timer(count_nums)

count_nums(33_0000)
print('运行结束')

Time elapsed was 6.586411237716675
运行结束

而在python中,复杂的旧名新用的那行代码,可以用语法糖代替

@make_timer
def count_nums(n):
    for i in range(n):
        for j in range(2000):
            pass

语法糖“@make_timer”的意义就是告诉python我在被装饰函数后有一行

count_nums = make_timer(count_nums)

语法糖版本:

import time


def make_timer(func):  # 装饰器
    def wrapper(*args, **kwargs):
        t1 = time.time()
        ret_val = func(*args, **kwargs)
        t2 = time.time()
        print('Time elapsed was', t2 - t1)
        return ret_val

    return wrapper


@make_timer
def count_nums(n):
    for i in range(n):
        for j in range(2000):
            pass

count_nums(33_0000)
print('运行结束')

Time elapsed was 6.771592855453491
运行结束

 

再来一个加强的小例子来强化理解

首先看完整代码

def outer_func(func):
    def inner_func(*args, **kwargs):
        print('调用inner_func')
        a, b = args if args else kwargs.values()
        a += 3
        b += 4
        return func(a, b)

    return inner_func


def native_func(num1, num2):
    print('调用native_func')
    num1 += 1
    num2 += 1
    return num1, num2


func = outer_func(native_func)  # 将装饰后的函数重新命名为被装饰的函数

word1, word2 = func(1, 1)
print(word1)
print(word2)
print()

word1, word2 = func(num1=2, num2=4)
print(word1)
print(word2)

调用inner_func
调用native_func
5
6

调用inner_func
调用native_func
6
9

详细讲解:

首先看装饰器部分

def outer_func(func):
    def inner_func(*args, **kwargs):
        print('调用inner_func')
        a, b = args if args else kwargs.values()
        a += 3
        b += 4
        return func(a, b)

    return inner_func

 外层接收函数,内层接收元组或字典,然后为了看调用顺序对控制台输出标记,然后对内层的变量进行赋值<该部分不懂的请看补充1>,然后先对a和b进行一次加数,然后再把a和b的新值传参给被装饰函数并调用后返回

(整个装饰器的过程可以想象成一个小箱子放进了一个大箱子套的中箱子里,这个过程就是装饰,当你拆快递时,大箱子打开是中箱子,中箱子打开就是被装饰的小箱子,小箱子里是你想要的结果,拆箱子的过程就是返回顺序也是机器运行顺序,其中的变量和传参就是就是开始的+三个箱子中的a小球和b小球的数量)

然后是被装饰函数的主体

def native_func(num1, num2):
    print('调用native_func')
    num1 += 1
    num2 += 1
    return num1, num2

这个就没什么好说的了,先加一行打印标记,各自加一后返回结果

func = outer_func(native_func)

为了便于和装饰器的部分连贯,这里没有使用语法糖代替也没有使用原名,便于理解

word1, word2 = func(1, 1)
print(word1)
print(word2)
print()

word1, word2 = func(num1=2, num2=4)
print(word1)
print(word2)

然后给func传参,对于这两地方,他们合并后就是这个样子

word1, word2 = outer_func(native_func)(1, 1)  

outer_func(native_func) 指向 inner_func 并传入参数(1,1)分别赋值给a和b,其中的func就是native_func函数

我们可以利用__name__来打印一下装饰器内形参func的函数对象的名字是什么来证实上面的总结

修改装饰器部分的代码:

def outer_func(func):
    def inner_func(*args, **kwargs):
        print('调用inner_func')
        print(func.__name__)  # 是这一行哦
        a, b = args if args else kwargs.values()
        a += 3
        b += 4
        return func(a, b)

    return inner_func

运行: 

调用inner_func
native_func      <<<<<看这里
调用native_func

...后面省略

实际使用过程中,使用语法糖即可,完整代码如下

def outer_func(func):
    def inner_func(*args, **kwargs):
        print('调用inner_func')
        print(func.__name__)
        a, b = args if args else kwargs.values()
        a += 3
        b += 4
        return func(a, b)

    return inner_func


@outer_func
def native_func(num1, num2):
    print('调用native_func')
    num1 += 1
    num2 += 1
    return num1, num2


word1, word2 = native_func(1, 1)
print(word1)
print(word2)
print()

word1, word2 = native_func(num1=2, num2=4)
print(word1)
print(word2)

然后为了看装饰器是不是深拷贝,我们把上面的代码进行修改,全代码如下

def outer_func(func):
    a_list = [[], []]

    def inner_func(*args, **kwargs):
        a_list[0].append(111)
        return func(a_list)

    return inner_func


@outer_func
def list1_func(a_list):
    return a_list


@outer_func
def list2_func(a_list):
    return a_list


list1_func()
list2_func()
list1_func()
list2_func()
list1_func()
list1_func()
print('list1_func调用5次:', id(list1_func), list1_func(), sep=' ')
print('list2_func调用3次:', id(list2_func), list2_func(), sep=' ')

运行:

list1_func调用5次: 3042185555984 [[111, 111, 111, 111, 111], []]
list2_func调用3次: 3042185556128 [[111, 111, 111], []]

 事实证明,装饰器和闭包一样具有深拷贝的特性

也可以证明,如果有代码复用的部分,完全可以封装为一个装饰器,这样不但可以减少整体项目的代码量,同时还能方便其他程序员的语法糖使用

补充部分

1.python的快速赋值

python与其他需要变量声明的开发语言不同的是,python是先赋值后识别类型的开发语言,对于多个变量赋值可以采用以下方式

a, b = 2, 3
print(a)
print(b)

2
3

或者

a, *b = 2, 3, 4, 5, 6
print(a)
print(b)

2
[3, 4, 5, 6]

也可以

a, b = (2, 3)
print(a)
print(b)

2
3

args在上面第二个例子中的第一组的*args的结果是一个元组:(1, 1)

kwargs在第二个例子中的第二组的**kwargs的结果是一个字典:{'num1': 2, 'num2': 4}

kwargs.values()的结果是:dict_values([2, 4])

2.python的装饰器与继承的区别?

Python中的装饰器和继承都是以不同的方式扩展类的能力。

装饰器是一种可调用的对象,它接受一个函数作为参数,并返回一个新的函数。装饰器可以用来修改或者增强函数的行为。通过使用装饰器,
可以将一些通用的功能封装到可重用的装饰器中,从而减少代码冗余。

继承是Python中的一种面向对象编程技术,它被用来创建新的类,这些新的类继承了现有的类的属性和方法。
通过继承,子类可以重写或扩展父类的方法,从而实现特定的需求。

装饰器和继承的主要区别如下:

1. 作用对象不同:装饰器作用于函数、方法或者类,而继承只适用于类。

2. 能力扩展方式不同:装饰器通过增强函数的行为来扩展其能力,而继承则是通过从现有类派生新类来扩展其能力。

3. 处理方式不同:装饰器是在运行时动态地修改函数的行为,而继承是在定义类时静态地确定新类的行为。

虽然装饰器和继承具有一些相似之处,但它们的主要目的和应用不同。在编写Python代码时,应根据具体需求选择最合适的技术来实现特定的功能。

3.python中的闭包是一种递归嘛?

不一定。

闭包和递归是两个不同的概念,尽管它们有时会同时使用。

闭包是一个函数和它的环境变量的组合体,它用于捕获函数定义时可见的状态。这个函数可以在定义它的范围之外执行,并且可以访问和修改这些状态。

递归是一种函数或算法调用自身的方式。它通常用于解决重复的问题,例如遍历树或列表。

在Python中,闭包常常用于创造一个带有状态的函数或者对象。而递归则是实现算法或者遍历数据结构的一种有效手段。 虽然在某些情况下,我们可以使用递归实现闭包,但是它们并不是同一概念。

你可能感兴趣的:(python精进,python)