小白学 Python
课程GitHub
© 2026 小白学 Python · 基于 walter201230/Python 教程
课程目录GitHub
Python 环境加载中…
装饰器0 / 3
123

装饰器

3 道练习题·预计 30 分钟·做对一题解锁下一段

教学 01 / 04· 已读

第十六节:装饰器

上一章学了闭包,这一章我们一步步演变一个需求,来认识 Python 中非常有特色的语法——装饰器(Decorator)。

需求 1:员工打卡

先看一个最简单的打卡函数:

python
def punch():
    print('昵称:两点水  部门:研发部  上班打卡成功')

punch()

输出:

昵称:两点水  部门:研发部  上班打卡成功

很朴素。

需求 2:加上日志

产品经理跑过来说:「不行啊,怎么没有日志?打卡前后都给我打印一下提示!」

直接改函数:

python
def punch():
    print('[LOG] 开始执行')
    print('昵称:两点水  部门:研发部  上班打卡成功')
    print('[LOG] 执行结束')

punch()

可以是可以——但这样改变了函数本身的功能结构。本来 punch 只是打卡,现在硬塞进了日志。

而且想象一下:如果还有 holiday()、apply_leave()、overtime() 等等很多函数都要加日志——你打算每个函数都写一遍 [LOG] 开始执行 吗?

代码重复 = 坏味道。

需求 3:抽出来

我们知道 Python 函数有两个特点:

  • 函数也是一个对象,可以被传来传去
  • 函数里可以嵌套函数

那就把日志抽出来:

python
def punch():
    print('昵称:两点水  部门:研发部  上班打卡成功')

def add_log(func):
    print('[LOG] 开始执行')
    func()
    print('[LOG] 执行结束')

add_log(punch)

输出:

[LOG] 开始执行
昵称:两点水  部门:研发部  上班打卡成功
[LOG] 执行结束

这下 punch 没动过,任何想加日志的函数都可以扔进 add_log。

但调用方式变了——以前是 punch(),现在变成 add_log(punch)。如果项目里 punch() 已经被调用了 100 处,难道全要改成 add_log(punch)?

我们想要的是:不改函数本身,也不改调用方式,就给函数加上日志。

这就是装饰器要解决的事。

教学 03 / 04

一、第一个装饰器

1、装饰器写法

装饰器的写法和闭包很像——只不过它接收的是一个函数:

python
def decorator(func):
    def wrapper():
        print('[LOG] 开始执行')
        func()
        print('[LOG] 执行结束')
    return wrapper


def punch():
    print('昵称:两点水  部门:研发部  上班打卡成功')


f = decorator(punch)
f()

输出:

[LOG] 开始执行
昵称:两点水  部门:研发部  上班打卡成功
[LOG] 执行结束

装饰器函数一般做这三件事:

  1. 接收一个函数作为参数
  2. 嵌套一个 wrapper 函数——在里面调用原函数,并附加额外功能
  3. 返回这个 wrapper 函数

2、@ 语法糖

可是认真一看:这写法怎么看都比直接传函数还麻烦啊——还要 f = decorator(punch) 拿到包装后的函数再调用。

这就是为什么 Python 引入了 @ 语法糖——它让定义装饰器、把装饰器调用原函数再把结果赋值给原函数对象名的过程变得非常简洁。

用法:在原函数定义上方加 @装饰器名:

python
def decorator(func):
    def wrapper():
        print('[LOG] 开始执行')
        func()
        print('[LOG] 执行结束')
    return wrapper


@decorator
def punch():
    print('昵称:两点水  部门:研发部  上班打卡成功')


punch()

输出:

[LOG] 开始执行
昵称:两点水  部门:研发部  上班打卡成功
[LOG] 执行结束

@decorator 等价于在 def punch() 之后偷偷帮你执行了一句:

python
punch = decorator(punch)

所以从此以后,调用 punch() 实际上调的是 wrapper(),外层加了日志,里面再调原 punch。

3、装饰器的核心思想

Python 在引入装饰器时,没有引入任何新的语法特性——它都是基于函数是对象、函数可嵌套这些已有的能力。装饰器不是 Python 特有的概念,每种语言都可以有这种思想;只不过 Python 给了它 @ 这个糖,写起来格外漂亮。

一句话:装饰器 = 不修改原函数和调用方式,给函数额外加一层功能。

练习 1 / 3·写一个 @log 装饰器题目有问题?

请写一个装饰器 log:

  • 接收一个函数作为参数
  • 在调用原函数前 print('开始执行')
  • 在调用原函数后 print('执行结束')

然后用 @log 装饰一个 hello() 函数(函数体里 print('hello world')),再调用 hello()。

输出应该是:

开始执行
hello world
执行结束
main.py
可编辑
🔒做对当前题解锁下一段 ·0/3
本章目录

装饰器

  1. 教学 01第十六节:装饰器
  2. 教学 03一、第一个装饰器
  3. 练习 1写一个 @log 装饰器
  4. 教学 02二、装饰带参数的函数
  5. 练习 2 🔒@uppercase:改造返回值
  6. 教学 04三、带参数的装饰器与 functools.wraps
  7. 练习 3 🔒@count_calls:统计调用次数
← 上一章15 · 闭包
装饰器
下一章 →17 · 代码可读性