装饰器和装饰器模式

装饰,实际上是对函数、对象行为的改变,或是扩展、增强、甚至替换。它接收一个个体,最终输出一个装饰后的个体。

这里,装饰器是 Python 中的概念,而装饰器模式来源于设计模式。把它们放在一起,是因为它们都具备有”装饰”的特性,但也有一些不同。

装饰器

Python 中的装饰器,本质上是一个可调用对象(函数或者任何实现了 __call__ 方法的对象)。

装饰器使用

我们定义一个装饰器函数,它接收一个函数或者类作为参数,并返回一个函数。

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
27
28
29
30
31
32
33
34
print("1. 开始")

# 函数装饰器
def log_decorator(func):
print(f"\n2. 装饰器被调用")
print(f"2.1 接收到的 func 类型: {type(func)}") # 应该是 function
print(f"2.2 接收到的 func 名称: {func.__name__}")

def wrapper(*args, **kwargs):
print(f"\n4. wrapper 被调用")
print(f"4.1 wrapper 类型: {type(wrapper)}") # 应该是 function
print(f"4.2 调用原始函数")
result = func(*args, **kwargs)
print(f"4.3 原始函数调用完成")
return result

print(f"3. 准备返回 wrapper")
print(f"3.1 返回的 wrapper 类型: {type(wrapper)}") # 应该是 function
return wrapper


@log_decorator
def greeting(name):
print(f"5. greeting 函数被调用,参数: {name}")
return f"Hello, {name}!"


print("\n--- 装饰器执行完毕 ---")
print(f"6. 最终 greeting 类型: {type(greeting)}") # 现在应该是 wrapper 函数
print(f"6.1 最终 greeting 名称: {greeting.__name__}")

print("\n--- 调用函数 ---")
result = greeting("Alice")
print(f"\n7. 调用结果: {result}")

这里,其实我们能够看到,greeting 函数被 log_decorator 装饰器装饰后,变成了 log_decorator 内部的 wrapper 函数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# 类装饰器
def singleton(cls):
print(f"修饰前 Database 类型: {type(cls)}") # <class 'type'>

def get_instance():
print(f"get_instance 函数类型: {type(get_instance)}") # <class 'function'>
return cls()

print(f"修饰后返回函数类型: {type(get_instance)}") # <class 'function'>
return get_instance

@singleton
class Database:
pass

print(f"最终 Database 类型: {type(Database)}") # <class 'function'>
db = Database() # 实际上是调用返回的 get_instance 函数
print(f"db 对象类型: {type(db)}") # <class 'Database'>

使用了装饰器之后,Database 类在导入时,装饰器函数被自动执行,Database 类变成了 singleton 内部的 get_instance 函数。

综上,装饰器函数在本质上,其实是在被装饰个体(函数或者类)被导入时,将个体名字(标识符)指向到了装饰器函数内部的函数上。

名字是一个字符串键(key),这个键存在于某个命名空间(namespace)的字典中,这个键对应的值(value)就是对象的引用。

我们还可以创建装饰器工厂,从而实现装饰器参数化。

1
2
3
4
5
6
7
8
9
10
11
12
13
def repeat(n):
def decorator(func):
def wrapper(*args, **kwargs):
for _ in range(n):
func(*args, **kwargs)
return wrapper
return decorator

@repeat(3) # 让 say_hi() 运行 3 次
def say_hi():
print("Hi!")

say_hi()

闭包

闭包,就是延伸了变量作用域的函数。

以上面的 repeat 装饰器为例,repeat 装饰器内部的 decorator 函数,就是闭包,decorator 函数能够访问 repeat 装饰器内部的变量 n。这样,即使 repeat 装饰器执行完毕,decorator 函数仍然能够访问 repeat 装饰器内部的变量 n

1
2
print(say_hi.__closure__)  # 输出闭包元组
print(say_hi.__closure__[0].cell_contents) # 访问闭包变量 n 的值

当然,我们也可以定义一个类来实现类似的功能,但是类需要实例化,而闭包不需要。并且,闭包的性能比类高。

在变量搜索过程中,其实还涉及了 LEGB 规则,即:

  • L: Local 局部作用域
  • E: Enclosing 闭包函数外的函数中
  • G: Global 全局作用域
  • B: Built-in 内置作用域

尽管,Python 和 JS 中闭包本质上是相同的,但在作用域规则上也存在一些区别。

特性 Python(LEGB 规则) JavaScript(作用域链)
作用域规则 LEGB(Local → Enclosing → Global → Built-in) 作用域链(Scope Chain)
变量修改 默认不能修改外部变量,需要 nonlocal 可以直接修改外部变量
变量查找顺序 从内到外,依次查找 从内到外,依次查找

装饰器模式

装饰器模式,是一种常见的设计模式,目的是额外的给对象添加功能,这一点是和装饰器是相同的。

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
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
// 基础组件接口
interface Coffee {
String getDescription();
double getCost();
}

// 具体组件
class SimpleCoffee implements Coffee {
public String getDescription() { return "Simple Coffee"; }
public double getCost() { return 5.0; }
}

// 装饰器基类
abstract class CoffeeDecorator implements Coffee {
protected Coffee coffee;

public CoffeeDecorator(Coffee coffee) {
this.coffee = coffee;
}

public String getDescription() { return coffee.getDescription(); }
public double getCost() { return coffee.getCost(); }
}

// 具体装饰器
class MilkDecorator extends CoffeeDecorator {
public MilkDecorator(Coffee coffee) { super(coffee); }

public String getDescription() {
return coffee.getDescription() + ", with Milk";
}

public double getCost() {
return coffee.getCost() + 2.0;
}
}

Coffee coffee = new SimpleCoffee(); // 简单咖啡
Coffee milkCoffee = new MilkDecorator(coffee); // 加牛奶

System.out.println(milkCoffee.getDescription()); // 输出:Simple Coffee, with Milk
System.out.println(milkCoffee.getCost()); // 输出:7.0

以上,我们可以看到:

  • 每个装饰器都持有一个被装饰对象的引用。
  • 装饰器可以动态地添加或删除功能。
  • 装饰器和被装饰者实现相同的接口
  • 装饰器可以嵌套使用。

常见的,以 Java 中的 IO 流为例:

1
2
3
4
5
6
7
8
9
10
11
// 一个普通的 FileInputStream 只能按字节读取
FileInputStream fileIn = new FileInputStream("data.bin");
// 只能这样读:
int byte1 = fileIn.read(); // 读一个字节

// 使用 DataInputStream 装饰后
DataInputStream dataIn = new DataInputStream(fileIn);
// 可以直接读取基本类型:
int intValue = dataIn.readInt(); // 读取 4 字节并转成 int
double doubleValue = dataIn.readDouble(); // 读取 8 字节并转成 double
String str = dataIn.readUTF(); // 读取 UTF 编码的字符串

它体现了良好的单一职责原则,动态、灵活地扩展了 FileInputStream 的功能。

就动态扩展功能这一点,其实代理模式也可以实现。

1
2
3
4
5
6
7
8
9
10
// 代理:注重控制对象访问
public class ServiceProxy implements Service {
private Service service;

public void operation() {
if (hasPermission()) { // 控制访问
service.operation(); // 只是单纯调用原方法
}
}
}

代理类和装饰器类:

  • 通过持有被代理对象和被装饰对象的引用,实现对原对象的控制。
  • 都实现了和被代理对象和被装饰对象相同的接口。

但是,

  • 使用意图上,代理模式注重控制对象访问,而装饰器模式注重扩展对象功能。
  • 代理模式通常只有一个代理类,但装饰器模式可以有多个装饰器类。

装饰器和装饰器模式的区别

  • 从实现方式上,Python 中的装饰器体现了函数式编程(高阶函数)的思想,而装饰器模式体现了面向对象编程的思想,使用继承和组合。
  • 从定义时机上,装饰器在被编译时实现,而装饰器模式在运行时实现。
  • 从组合方式上,多个装饰器从里到外依次装饰,而装饰器模式可以嵌套使用。

但是,

  • 都是对目标进行功能扩展。
  • 都遵循开闭原则。都体现了单一职责原则。
  • 都是关注分离点。即功能和业务逻辑的分离。

© 2025 YueGS