装饰器函数

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)

然后写一个装饰器,让它执行时变成:

准备吃饭
我正在吃苹果
吃饭结束

提示:

  1. 装饰器函数要接收一个参数 func
  2. 装饰器里面要定义一个 wrapper(food)
  3. wrapper(food) 里面,先打印 "准备吃饭"
  4. 再调用原函数:func(food)
  5. 最后打印 "吃饭结束"
  6. 使用 @show_message 放在 eat 函数上面。

1. 题目考点

这道题主要考:

  1. 字符串切片
  2. 列表处理
  3. 排序 sorted()
  4. 函数作为参数传递
  5. 装饰器 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,不管它前面有没有 +91910,真正的手机号都是最后 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 位
078954621307895462130
9198756412309875641230
+9198756412309875641230
91959698789195969878

所以最稳的方法不是判断前缀是什么,而是直接取最后 10 位:

number = number[-10:]

这一步就自动去掉了前面的 +91910 等前缀。


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()

可以去掉字符串首尾空格。

文末附加内容
暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇