DC's blog DC's blog
首页
  • 计算机基础
  • linux基础
  • mysql
  • git
  • 数据结构与算法
  • axure
  • english
  • docker
  • opp
  • oop
  • 网络并发编程
  • 不基础的py基础
  • 设计模式
  • html
  • css
  • javascript
  • jquery
  • UI
  • 第一次学vue
  • 第二次学vue
  • Django
  • drf
  • drf_re
  • 温故知新
  • flask
  • 前后端不分离

    • BBS
    • 订单系统
    • CRM
  • 前后端部分分离

    • pear-admin-flask
    • pear-admin-django
  • 前后端分离

    • 供应链系统
  • 理论基础
  • py数据分析包
  • 机器学习
  • 深度学习
  • 华中科大的网课
  • cursor
  • deepseek
  • 杂文
  • 罗老师语录
  • 关于我

    • me
  • 分类
  • 归档
GitHub (opens new window)

DC

愿我一生欢喜,不为世俗所及.
首页
  • 计算机基础
  • linux基础
  • mysql
  • git
  • 数据结构与算法
  • axure
  • english
  • docker
  • opp
  • oop
  • 网络并发编程
  • 不基础的py基础
  • 设计模式
  • html
  • css
  • javascript
  • jquery
  • UI
  • 第一次学vue
  • 第二次学vue
  • Django
  • drf
  • drf_re
  • 温故知新
  • flask
  • 前后端不分离

    • BBS
    • 订单系统
    • CRM
  • 前后端部分分离

    • pear-admin-flask
    • pear-admin-django
  • 前后端分离

    • 供应链系统
  • 理论基础
  • py数据分析包
  • 机器学习
  • 深度学习
  • 华中科大的网课
  • cursor
  • deepseek
  • 杂文
  • 罗老师语录
  • 关于我

    • me
  • 分类
  • 归档
GitHub (opens new window)
  • python面向过程

  • python面向对象

  • 网络并发编程

  • 不基础的py基础

    • 默认参数
    • 装饰器
      • 引入
      • 函数装饰器
      • 类装饰器
    • Code Object
  • 设计模式

  • python_Need
  • 不基础的py基础
DC
2024-06-21
目录

装饰器


# 引入

我们循序渐进, 先来回顾一些知识.

在Python里,所有东西都是object,函数也不例外,也是Object(对象).
当我们在python中所谓定义一个函数的时候,我们只是新建了一个变量,
然后这个变量里面保存了一个函数对象,也就是 function object !

注: function object有一个特点,它是一个 callable, callable可以在后面加一个小括号去调用它!

Ps: 每一个function object都会有它对应的一个code object.

当我们清楚在Python里,函数不过是一个普通对象. 你就非常容易理解,函数可以被当做参数传进其它函数里.

def double(x):
    return 2 * x


def triple(x):
    return 3 * x


def calc_number(func, x):
    print(func(x))


if __name__ == '__main__':
    calc_number(double, 2)  # 4
    calc_number(triple, 2)  # 6
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

在上述程序里, 定义了double、triple, 这两个保存着函数对象的变量, 都可被传进 calc_number 这个函数里!

函数没有什么神奇的地方, 完全可以像其他变量一样被传来传去. 被传进其他函数.
那同样的, 函数不仅可以被作为变量传进其他函数, 函数本身也可以成为一个返回值!

我们可以将上述的程序进行优化

def get_multiple_func(n):
    def multiple(x):
        return n * x

    return multiple


if __name__ == '__main__':
    double = get_multiple_func(2)
    triple = get_multiple_func(3)

    print(double(2))  # 4
    print(triple(2))  # 6
1
2
3
4
5
6
7
8
9
10
11
12
13

当你理解了, 函数可以作为参数被传进其他函数 以及 函数的返回值可以是一个函数.
那么decorator, 相对来说就变得比较容易理解啦.

说在前面: 看完整篇博文, 你就能理解这句话啦! ★ decorator本身就是一个callable. 它也没有什么特殊的地方!


# 函数装饰器

我们先写一个极简的decorator.

def dec(f):
    pass


@dec
def my_func(x):
    return 2 * x
1
2
3
4
5
6
7

当我们装饰一个函数时,会用到语法糖. 在上述程序中, 语法糖具体体现在@dec . what is @dec ?

根据高天大佬对程序的字节码分析, 可知 @dec def my_func(x):return 2*n 这一坨 完全等价于 my_func = dec(my_func)
通过这个等式, 我们可以得出结论:
-1- 应用语法糖后, 我们就可以将 dec函数 称作为decorator, @后面紧跟着的 dec 是一个函数名!
-2- decorator的输入一定是一个函数, decorator的输出通常也是函数, 但不一定就是函数.

举一个极端的例子来佐证上面的第二点结论.

def dec(f):
    return 1


@dec
def my_func(x):
    return 2 * x


if __name__ == '__main__':
    print(my_func)  # 1
1
2
3
4
5
6
7
8
9
10
11

我们的decorator return的是1, 在my_func函数上加上该decorator, 打印my_func, 结果也是1.
注意哦, 我们print的是 my_func这个变量本身, 并没有对其加括号进行调用哦!

为什么 my_func 变成了 1 呢? 回归等式 my_func = dec(my_func) , 就很容易理解了!
尽管如此, 但在绝大多数场景下, 我们还是认为:
decorator是一个参数是函数, 返回值也是函数的函数! / decorator不过是一个输入和输出都是函数的函数.
(不严谨, 但现目前看来够用了!)

import time


def timeit(f):
    def wrapper(x):
        start = time.time()
        ret = f(x)
        print(time.time() - start)
        return ret

    return wrapper


@timeit
def my_func(x):
    time.sleep(x)


@timeit
def other_func(x):
    return x * 2


if __name__ == '__main__':
    print(my_func(1))
    print(other_func(3))
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26

上述程序中的 timeit 函数就可当做是 decorator, 它take(接收)的f是一个函数, 返回的wrapper也是一个函数.
返回的wrapper是干什么的呢?
1> 首先, wrapper与被装饰的my_func、other_func take(接收) 的参数应该一样.
2> 此处的wrapper函数的逻辑是: 为了方便阐述,我们将 decorator take的函数叫做A.
记录一个时间点, 再运行A并记录其返回值, 接着打印A的运行时间, 最后将A的返回值返回.

来看看运行结果

1.005234956741333
None
9.059906005859375e-06
6
1
2
3
4

刚才那个示例中的decorator只能装饰只有一个参数的函数. 为了提高通用性, 我们应该这么做:
用允许变长的函数参数, *args 、**kwargs , 这样就可以将装饰器应用到有若干个参数的函数上. (*≧ω≦)

import time


def timeit(f):
    def wrapper(*args, **kwargs):
        start = time.time()
        ret = f(*args, **kwargs)
        print(time.time() - start)
        return ret

    return wrapper


@timeit
def my_func(x):
    time.sleep(x)
    
    
@timeit
def other_func(x, y):
    return x + y
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

我们再进阶一下, 带参数的 decorator , 那是怎么回事呢?

其实带参数的decorator, 也没有任何神奇的地方, 只不过 "相当于 在等价的过程中 多了一次函数调用"!

import time


def timeit(iteration):  # iteration:运行多少次.
    def inner(f):
        def wrapper(*args, **kwargs):
            start = time.time()
            for _ in range(iteration):
                f(*args, **kwargs)
            print(time.time() - start)
            return

        return wrapper

    return inner


@timeit(100000)
def other_func(x, y):
    return x + y


if __name__ == '__main__':
    print(other_func(1, 2))
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24

原来是不带参数的装饰器, @timeit --> other_func = timeit(other_func)
现在是带参数的装饰器, @timeit(100000) --> other_func = timeit(100000)(other_func)

不难看出, 无参装饰器, 参数是函数, 返回值也是函数;
有参装饰器, 参数是你可以任意指定的东西, 然后它要返回一个函数a, 这个函数a的参数是函数, 返回值也是函数.


# 类装饰器

默认参数
Code Object

← 默认参数 Code Object→

最近更新
01
deepseek本地部署+知识库
02-17
02
实操-微信小程序
02-14
03
教学-cursor深度探讨
02-13
更多文章>
Theme by Vdoing | Copyright © 2023-2025 DC | One Piece
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式