Python 中的装饰器到底是什么意思?
你说的“装饰符”,在 Python 里更常见的标准说法是:装饰器,英文叫 decorator。
一句话理解:
装饰器就是:在不直接修改原函数代码的情况下,给原函数额外增加功能。
它经常写在函数上面,例如:
@some_decorator
def hello():
print("Hello")
这里的 @some_decorator 就是在“装饰”下面的 hello() 函数。
1. 装饰器是干什么的?
假设你已经写好了一个函数:
def say_hello():
print("你好")
现在你希望在这个函数执行前后额外做一些事情,比如:
函数开始执行
你好
函数执行结束
最直接的方法是改原函数:
def say_hello():
print("函数开始执行")
print("你好")
print("函数执行结束")
但是这样有一个问题:
如果你有很多函数都想加“开始执行”和“执行结束”,难道每个函数都进去改一遍吗?
这时装饰器就有用了。
它的作用就是:
把一个函数包起来,在它原本功能的前后增加一些额外操作。
2. 先理解一个前置概念:函数也可以当作变量传递
在 Python 里,函数本身也可以像变量一样传来传去。
例如:
def say_hello():
print("你好")
func = say_hello
func()
运行结果:
你好
这里要注意:
func = say_hello
不是在调用函数,而是把函数本身交给变量 func。
如果写成:
func = say_hello()
这才是调用函数。
这是理解装饰器的关键。
3. 装饰器的基本语法
最基础的装饰器结构是这样的:
def 装饰器函数(原函数):
def 包装函数():
# 原函数执行前做的事
原函数()
# 原函数执行后做的事
return 包装函数
然后使用时:
@装饰器函数
def 被装饰的函数():
函数原本要做的事
这里有三个角色:
| 名称 | 作用 |
|---|---|
| 装饰器函数 | 负责接收原函数,并返回一个新函数 |
| 原函数 | 被装饰的函数 |
| 包装函数 | 在里面控制“原函数执行前后要做什么” |
4. 最小可运行例子
先看完整代码:
def my_decorator(func):
def wrapper():
print("函数开始执行")
func()
print("函数执行结束")
return wrapper
@my_decorator
def say_hello():
print("你好")
say_hello()
运行结果:
函数开始执行
你好
函数执行结束
5. 这段代码到底怎么执行?
重点看这一段:
@my_decorator
def say_hello():
print("你好")
它其实等价于下面这种写法:
def say_hello():
print("你好")
say_hello = my_decorator(say_hello)
这句话非常关键。
也就是说:
@my_decorator
并不是简单地“标记一下函数”,而是把下面的函数交给 my_decorator 处理,然后把返回的新函数重新赋值给原来的函数名。
所以:
say_hello = my_decorator(say_hello)
执行之后,say_hello 已经不再直接指向原来的函数,而是指向 wrapper 这个包装函数。
因此当你调用:
say_hello()
真正执行的是:
wrapper()
而 wrapper() 里面又会调用原来的 func()。
执行顺序是:
调用 say_hello()
实际上调用 wrapper()
先打印:函数开始执行
再调用原函数 func()
原函数打印:你好
最后打印:函数执行结束
6. 为什么装饰器要返回 wrapper?
看这段代码:
def my_decorator(func):
def wrapper():
print("函数开始执行")
func()
print("函数执行结束")
return wrapper
这里 return wrapper 的意思是:
把包装好的新函数交出去。
注意不是:
return wrapper()
而是:
return wrapper
区别很重要。
wrapper 表示函数本身。
wrapper() 表示现在立刻调用这个函数。
装饰器不应该马上执行函数,而是应该把包装后的函数返回出去,等以后用户真正调用 say_hello() 时再执行。
7. 从思路到代码:怎么自己写一个装饰器?
假设需求是:
给一个函数增加提示信息:执行前打印“准备开始”,执行后打印“已经结束”。
第一步,先写原函数:
def study():
print("正在学习 Python")
第二步,想清楚我们要增加什么功能:
执行 study() 之前:打印 准备开始
执行 study() 本身:打印 正在学习 Python
执行 study() 之后:打印 已经结束
第三步,把这个逻辑写进包装函数:
def add_message(func):
def wrapper():
print("准备开始")
func()
print("已经结束")
return wrapper
第四步,使用装饰器:
@add_message
def study():
print("正在学习 Python")
study()
完整代码:
def add_message(func):
def wrapper():
print("准备开始")
func()
print("已经结束")
return wrapper
@add_message
def study():
print("正在学习 Python")
study()
运行结果:
准备开始
正在学习 Python
已经结束
写装饰器时,可以先不要急着写 @,先在脑子里想清楚:
我要把哪个函数包起来?
我要在原函数执行前做什么?
我要在原函数执行后做什么?
最后是不是要返回包装函数?
8. 带参数的函数怎么装饰?
前面的例子里,原函数没有参数:
def say_hello():
print("你好")
但是实际函数经常有参数,例如:
def greet(name):
print("你好," + name)
如果装饰器还是这样写:
def wrapper():
func()
就会出问题,因为 greet(name) 需要一个参数。
所以包装函数也要接收参数:
def my_decorator(func):
def wrapper(name):
print("函数开始执行")
func(name)
print("函数执行结束")
return wrapper
@my_decorator
def greet(name):
print("你好," + name)
greet("小明")
运行结果:
函数开始执行
你好,小明
函数执行结束
这里的执行逻辑是:
greet("小明")
实际上执行 wrapper("小明")
wrapper 里面再执行 func("小明")
func 指向原来的 greet 函数
9. 更通用的写法:*args 和 **kwargs
如果你不知道原函数有几个参数,可以使用更通用的写法:
def my_decorator(func):
def wrapper(*args, **kwargs):
print("函数开始执行")
result = func(*args, **kwargs)
print("函数执行结束")
return result
return wrapper
这段对初学者来说稍微抽象一点,可以先这样理解:
| 写法 | 含义 |
|---|---|
*args | 接收任意数量的位置参数 |
**kwargs | 接收任意数量的关键字参数 |
result = func(...) | 保存原函数的返回值 |
return result | 把原函数的结果继续返回出去 |
例如:
def my_decorator(func):
def wrapper(*args, **kwargs):
print("开始计算")
result = func(*args, **kwargs)
print("计算结束")
return result
return wrapper
@my_decorator
def add(a, b):
return a + b
answer = add(3, 5)
print(answer)
运行结果:
开始计算
计算结束
8
为什么要写:
return result
因为原来的 add(3, 5) 本来应该返回 8。
如果装饰器没有把结果返回出去,那么外面就拿不到 8 了。
10. 常见错误与易混点
错误 1:把 return wrapper 写成 return wrapper()
错误写法:
def my_decorator(func):
def wrapper():
print("开始")
func()
print("结束")
return wrapper()
问题是:
wrapper()
会立刻执行包装函数,而不是把包装函数返回出去。
正确写法是:
return wrapper
错误 2:忘记在 wrapper 里面调用原函数
错误写法:
def my_decorator(func):
def wrapper():
print("开始")
print("结束")
return wrapper
这样原函数根本没有被执行。
应该写成:
def my_decorator(func):
def wrapper():
print("开始")
func()
print("结束")
return wrapper
错误 3:原函数有参数,但 wrapper 没有接收参数
错误写法:
def my_decorator(func):
def wrapper():
print("开始")
func()
print("结束")
return wrapper
@my_decorator
def greet(name):
print("你好," + name)
greet("小明")
这里 greet("小明") 实际是在调用 wrapper("小明")。
但是 wrapper() 没有参数,所以会报错。
可以改成:
def my_decorator(func):
def wrapper(name):
print("开始")
func(name)
print("结束")
return wrapper
或者更通用地写:
def my_decorator(func):
def wrapper(*args, **kwargs):
print("开始")
result = func(*args, **kwargs)
print("结束")
return result
return wrapper
错误 4:原函数有返回值,但装饰器没有返回结果
错误写法:
def my_decorator(func):
def wrapper(a, b):
print("开始计算")
func(a, b)
print("计算结束")
return wrapper
@my_decorator
def add(a, b):
return a + b
result = add(3, 5)
print(result)
输出可能是:
开始计算
计算结束
None
原因是:
func(a, b)
虽然算出了结果,但是没有保存,也没有返回。
正确写法:
def my_decorator(func):
def wrapper(a, b):
print("开始计算")
result = func(a, b)
print("计算结束")
return result
return wrapper
11. 初学者应该怎么理解装饰器?
你可以先把装饰器理解成一句话:
装饰器就是一个“函数加工厂”。
原来的函数进去:
原函数
装饰器加工后出来:
增强后的函数
比如:
普通函数:只负责说你好
装饰器加工后:先提示开始,再说你好,再提示结束
所以:
@my_decorator
def say_hello():
print("你好")
可以理解成:
请用 my_decorator 加工 say_hello 这个函数
以后调用 say_hello 时,调用的是加工后的版本
12. 本节小结
装饰器的核心不是 @ 这个符号本身,而是它背后的函数替换逻辑。
这段代码:
@my_decorator
def say_hello():
print("你好")
本质上等价于:
def say_hello():
print("你好")
say_hello = my_decorator(say_hello)
所以装饰器的重点有三个:
| 关键点 | 解释 |
|---|---|
| 装饰器接收一个函数 | func 就是原函数 |
| 装饰器内部定义一个新函数 | wrapper 负责增加额外功能 |
| 装饰器返回新函数 | 以后调用原函数名时,实际执行包装函数 |
最基础的装饰器模板可以先记成:
def my_decorator(func):
def wrapper(*args, **kwargs):
# 原函数执行前
result = func(*args, **kwargs)
# 原函数执行后
return result
return wrapper
13. 练习
请你写一个装饰器 show_message,要求如下:
题目:
定义一个函数:
def eat(food):
print("我正在吃" + food)
然后写一个装饰器,让它执行时变成:
准备吃饭
我正在吃苹果
吃饭结束
提示:
- 装饰器函数要接收一个参数
func。 - 装饰器里面要定义一个
wrapper(food)。 - 在
wrapper(food)里面,先打印"准备吃饭"。 - 再调用原函数:
func(food)。 - 最后打印
"吃饭结束"。 - 使用
@show_message放在eat函数上面。

1. 题目考点
这道题主要考:
- 字符串切片
- 列表处理
- 排序
sorted() - 函数作为参数传递
- 装饰器
decorator
这题表面上是“手机号格式化”,但它真正想考的是:用装饰器在原函数执行前,先把手机号统一格式化。
2. 审题
题目给你 N 个手机号。每个手机号可能长得不一样,例如:
07895462130
919875641230
9195969878
+919875641230
但是它们真正有效的手机号部分,都是最后 10 位。
题目要求输出统一格式:
+91 xxxxx xxxxx
也就是:
+91 前5位 后5位
例如:
07895462130
最后 10 位是:
7895462130
分成前 5 位和后 5 位:
78954 62130
所以输出:
+91 78954 62130
3. 思路提示
先不要急着写装饰器,可以先想清楚“一个手机号怎么处理”。
对于任意一个输入手机号 number,不管它前面有没有 +91、91、0,真正的手机号都是最后 10 位。
所以核心操作是:
number[-10:]
例如:
"07895462130"[-10:] # "7895462130"
"919875641230"[-10:] # "9875641230"
"9195969878"[-10:] # "9195969878"
拿到最后 10 位之后,再切成两段:
last_10[:5]
last_10[5:]
然后拼成:
"+91 " + 前5位 + " " + 后5位
这就是格式化手机号的核心逻辑。
4. 完整设计思路
这道题要求使用装饰器,所以我们要分成两层来看。
第一层是原函数 sort_phone()。它的任务很简单:接收一个手机号列表,然后排序并打印。
第二层是装饰器 wrapper()。它的任务是在 sort_phone() 执行之前,先把原始手机号列表转换成标准格式。
也就是说,程序整体流程是:
读取 N
读取 N 个手机号,放进列表
调用 sort_phone(手机号列表)
但是因为 sort_phone 被装饰器包裹了:
先进入装饰器内部函数 fun()
fun() 负责把手机号标准化
然后再调用原来的 sort_phone()
sort_phone() 排序并打印
所以可以理解为:
装饰器负责:清洗数据、统一格式
原函数负责:排序、输出
5. 为什么用 number[-10:]
这是这道题最关键的地方。
因为题目说手机号可能有这些前缀:
+91
91
0
没有前缀
但不管前面有什么,真正的手机号都在最后 10 位。
例如:
| 原始输入 | 最后 10 位 |
|---|---|
07895462130 | 7895462130 |
919875641230 | 9875641230 |
+919875641230 | 9875641230 |
9195969878 | 9195969878 |
所以最稳的方法不是判断前缀是什么,而是直接取最后 10 位:
number = number[-10:]
这一步就自动去掉了前面的 +91、91、0 等前缀。
6. 代码实现
def wrapper(func):
def fun(phone_numbers):
formatted_numbers = []
for number in phone_numbers:
number = number[-10:] # 取最后 10 位真实手机号
formatted_number = "+91 " + number[:5] + " " + number[5:]
formatted_numbers.append(formatted_number)
func(formatted_numbers)
return fun
@wrapper
def sort_phone(phone_numbers):
for number in sorted(phone_numbers):
print(number)
if __name__ == '__main__':
n = int(input())
phone_numbers = []
for _ in range(n):
phone_numbers.append(input())
sort_phone(phone_numbers)
7. 代码逐段解释
先看装饰器部分:
def wrapper(func):
这里的 func 代表被装饰的函数。因为下面写了:
@wrapper
def sort_phone(phone_numbers):
所以这里的 func 实际上就是原来的 sort_phone 函数。
然后定义内部函数:
def fun(phone_numbers):
这个 fun() 会代替原来的 sort_phone() 被调用。
也就是说,当你写:
sort_phone(phone_numbers)
实际执行的是:
fun(phone_numbers)
接着遍历手机号列表:
for number in phone_numbers:
number = number[-10:]
每次拿到一个手机号,取最后 10 位。
例如:
number = "07895462130"
number[-10:] # "7895462130"
然后格式化:
formatted_number = "+91 " + number[:5] + " " + number[5:]
如果:
number = "7895462130"
那么:
number[:5] # "78954"
number[5:] # "62130"
拼接之后就是:
+91 78954 62130
最后:
func(formatted_numbers)
这一步是调用原来的 sort_phone(),把已经格式化好的手机号列表传进去。
原函数负责排序和打印:
@wrapper
def sort_phone(phone_numbers):
for number in sorted(phone_numbers):
print(number)
8. 运行演示
输入:
3
07895462130
919875641230
9195969878
程序先读取到列表:
[
"07895462130",
"919875641230",
"9195969878"
]
装饰器开始处理。
第一个:
07895462130
取最后 10 位:
7895462130
格式化:
+91 78954 62130
第二个:
919875641230
取最后 10 位:
9875641230
格式化:
+91 98756 41230
第三个:
9195969878
取最后 10 位:
9195969878
格式化:
+91 91959 69878
格式化后的列表是:
[
"+91 78954 62130",
"+91 98756 41230",
"+91 91959 69878"
]
排序后输出:
+91 78954 62130
+91 91959 69878
+91 98756 41230
9. 这题的核心理解
这道题不要把重点放在“手机号有多少种前缀”上。
正确思路是:
无论前面是什么,只取最后 10 位。
然后再统一变成:
+91 前5位 后5位
装饰器的作用是:
在原函数执行前,先加工传入的数据。
所以这题的结构可以记成:
wrapper:负责格式化手机号
sort_phone:负责排序和打印
10. 同类型小练习
请你尝试写一个装饰器,处理学生成绩列表。
要求:
输入若干个成绩字符串,例如:
["85", " 90 ", "078", "100"]
装饰器先把每个成绩处理成整数:
[85, 90, 78, 100]
然后交给原函数排序并打印。
提示:
score = int(score.strip())
其中:
strip()
可以去掉字符串首尾空格。



