卡码笔记-最强八股文
首页
计算机基础
Python
C++
Java
Go
🔥大模型🔥
  • 大模型面经
  • Java面经
  • C++面经
大模型Github (opens new window)
代码随想录 (opens new window)
首页
计算机基础
Python
C++
Java
Go
🔥大模型🔥
  • 大模型面经
  • Java面经
  • C++面经
大模型Github (opens new window)
代码随想录 (opens new window)
  • 本栏必读

    • Python面试题专栏介绍
    • Python学习路线
    • Python零基础入门课
  • 基础语法

  • 函数、作用域与模块

  • 面向对象

  • 进阶语法与语言机制

    • 可迭代对象和迭代器有什么区别?
    • 生成器是什么?yield为什么能节省内存?
    • Python装饰器的原理是什么?
      • 简要回答
      • 详细回答
      • 知识拓展
    • Python上下文管理器是什么?with如何自动释放资源?
    • Python异常体系是什么?try、except、else和finally怎么执行?
    • Python浅拷贝和深拷贝有什么区别?
    • Python类型标注有什么用?常见类型应该怎么写?
    • dataclass、namedtuple和普通类有什么区别?
    • Python如何管理内存?引用计数和垃圾回收是什么?

# Python装饰器的原理是什么?

面试官问:"Python装饰器的原理是什么?"

很多人会答"装饰器就是用@修饰函数",但这只是语法表象。真正要讲清楚的是:装饰器是一个接收函数作为参数、返回新函数的高阶函数,通过闭包保存原函数并在调用前后添加额外逻辑,@符号只是语法糖。

# 简要回答

装饰器(Decorator) 的本质是函数包装器。它接收一个函数,返回一个新函数,新函数在调用原函数前后可以执行额外操作。

@语法糖等价于函数重新绑定:

@decorator
def func():
    pass

# 等价于
def func():
    pass
func = decorator(func)
1
2
3
4
5
6
7
8

装饰器的核心机制:

  1. 装饰器函数接收被装饰函数作为参数。
  2. 装饰器内部定义包装函数,在包装函数中调用原函数。
  3. 装饰器返回包装函数。
  4. 原函数名被重新绑定到包装函数。

装饰器依赖闭包保存原函数引用,让包装函数可以访问和调用原函数。

# 详细回答

# 最简单的装饰器

不使用@符号,手动包装函数:

def log_decorator(func):
    def wrapper():
        print("函数开始执行")
        func()
        print("函数执行完成")
    return wrapper

def say_hello():
    print("Hello")

say_hello = log_decorator(say_hello)
say_hello()
# 输出:
# 函数开始执行
# Hello
# 函数执行完成
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

log_decorator接收say_hello,返回wrapper。wrapper是闭包,保存了对原say_hello的引用。最后say_hello名字被重新绑定到wrapper。

# @语法糖的等价转换

使用@符号等价于上面的手动包装:

def log_decorator(func):
    def wrapper():
        print("函数开始执行")
        func()
        print("函数执行完成")
    return wrapper

@log_decorator
def say_hello():
    print("Hello")

say_hello()  # 实际调用的是 wrapper
1
2
3
4
5
6
7
8
9
10
11
12

@log_decorator在函数定义完成时立即执行say_hello = log_decorator(say_hello)。

装饰器包装过程

# 处理有参数的被装饰函数

装饰器的包装函数需要接收任意参数并传递给原函数:

def log_decorator(func):
    def wrapper(*args, **kwargs):
        print(f"调用 {func.__name__}")
        result = func(*args, **kwargs)
        print(f"返回 {result}")
        return result
    return wrapper

@log_decorator
def add(a, b):
    return a + b

print(add(3, 5))
# 输出:
# 调用 add
# 返回 8
# 8
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

wrapper使用*args和**kwargs接收所有参数,并透传给func。

# 带参数的装饰器

装饰器本身需要参数时,需要再嵌套一层:

def repeat(times):
    def decorator(func):
        def wrapper(*args, **kwargs):
            for _ in range(times):
                result = func(*args, **kwargs)
            return result
        return wrapper
    return decorator

@repeat(3)
def say_hello():
    print("Hello")

say_hello()
# 输出:
# Hello
# Hello
# Hello
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

@repeat(3)等价于:

say_hello = repeat(3)(say_hello)
1

repeat(3)先返回真正的装饰器函数decorator,然后decorator(say_hello)返回wrapper。

# 保留原函数元信息

装饰后,函数名和文档字符串会丢失:

def log_decorator(func):
    def wrapper(*args, **kwargs):
        return func(*args, **kwargs)
    return wrapper

@log_decorator
def add(a, b):
    """计算两数之和"""
    return a + b

print(add.__name__)  # wrapper
print(add.__doc__)   # None
1
2
3
4
5
6
7
8
9
10
11
12

使用functools.wraps保留元信息:

from functools import wraps

def log_decorator(func):
    @wraps(func)
    def wrapper(*args, **kwargs):
        return func(*args, **kwargs)
    return wrapper

@log_decorator
def add(a, b):
    """计算两数之和"""
    return a + b

print(add.__name__)  # add
print(add.__doc__)   # 计算两数之和
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

@wraps(func)会把原函数的__name__、__doc__等属性复制到wrapper上。

# 装饰器链

多个装饰器从下到上依次应用:

def bold(func):
    def wrapper():
        return f"<b>{func()}</b>"
    return wrapper

def italic(func):
    def wrapper():
        return f"<i>{func()}</i>"
    return wrapper

@bold
@italic
def say_hello():
    return "Hello"

print(say_hello())  # <b><i>Hello</i></b>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

等价于:

say_hello = bold(italic(say_hello))
1

先应用italic,再应用bold。调用时先执行bold的包装,再执行italic的包装。

# 类装饰器

类也可以作为装饰器,需要实现__call__方法:

class LogDecorator:
    def __init__(self, func):
        self.func = func
    
    def __call__(self, *args, **kwargs):
        print("函数开始执行")
        result = self.func(*args, **kwargs)
        print("函数执行完成")
        return result

@LogDecorator
def say_hello():
    print("Hello")

say_hello()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

@LogDecorator等价于say_hello = LogDecorator(say_hello)。调用say_hello()时触发__call__方法。

# 知识拓展

Q1: 装饰器和闭包有什么关系?

装饰器的包装函数通常是闭包。闭包保存了对原函数的引用,让包装函数可以在调用时访问原函数。没有闭包,装饰器无法在包装函数中调用原函数。

可以查看什么是闭包?闭包中的变量为什么会出现延迟绑定?了解闭包机制。

Q2: 装饰器的典型应用场景有哪些?

  • 日志记录:在函数调用前后打印日志。
  • 性能统计:计算函数执行时间。
  • 权限检查:在Web框架中检查用户权限。
  • 缓存结果:functools.lru_cache缓存函数返回值。
  • 参数校验:检查函数参数类型或范围。
  • 重试机制:失败后自动重试。

Q3: 可以装饰类方法吗?

可以。但要注意包装函数需要接收self参数:

def log_decorator(func):
    def wrapper(self, *args, **kwargs):
        print(f"调用 {func.__name__}")
        return func(self, *args, **kwargs)
    return wrapper

class Calculator:
    @log_decorator
    def add(self, a, b):
        return a + b
1
2
3
4
5
6
7
8
9
10

或者使用*args接收所有参数,self会作为第一个参数传入。

Q4: 装饰器会影响性能吗?

会。每次调用被装饰函数时,都会经过包装函数的额外调用层。但影响通常很小,除非在热点路径中使用复杂装饰器。装饰器带来的代码复用和可读性提升通常值得这点开销。

Q5: 可以在运行时动态添加或移除装饰器吗?

装饰器在函数定义时就已经应用,无法直接移除。但可以通过条件判断控制装饰器行为:

DEBUG = True

def log_decorator(func):
    def wrapper(*args, **kwargs):
        if DEBUG:
            print("执行日志")
        return func(*args, **kwargs)
    return wrapper
1
2
3
4
5
6
7
8

或者保存原函数引用,运行时选择调用装饰版本还是原版本。

Last Updated: 10/13/2026, 5:57:00 PM

← 生成器是什么?yield为什么能节省内存? Python上下文管理器是什么?with如何自动释放资源? →

评论

验证登录状态...

侧边栏 侧边栏
夜间模式 夜间
卡码简历 卡码简历
代码随想录 代码随想录
卡码投递表 卡码投递表🔥
2026实习校招群 2026群
添加客服微信 2026实习校招客服微信 PS:通过微信后,请发送姓名-学校-年级-2026实习/校招
支持卡码笔记 支持卡码笔记
鼓励/支持/赞赏Carl 卡码笔记赞赏码
1. 如果感觉本站对你很有帮助,也可以请Carl喝杯奶茶,金额大小不重要,心意已经收下
2. 希望大家都能梦想成真,有好的前程,加油💪