
装饰,实际上是对函数、对象行为的改变,或是扩展、增强、甚至替换。它接收一个个体,最终输出一个装饰后的个体。
这里,装饰器是 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)}") print(f"2.2 接收到的 func 名称: {func.__name__}")
def wrapper(*args, **kwargs): print(f"\n4. wrapper 被调用") print(f"4.1 wrapper 类型: {type(wrapper)}") 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)}") return wrapper
@log_decorator def greeting(name): print(f"5. greeting 函数被调用,参数: {name}") return f"Hello, {name}!"
print("\n--- 装饰器执行完毕 ---") print(f"6. 最终 greeting 类型: {type(greeting)}") 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)}") def get_instance(): print(f"get_instance 函数类型: {type(get_instance)}") return cls() print(f"修饰后返回函数类型: {type(get_instance)}") return get_instance
@singleton class Database: pass
print(f"最终 Database 类型: {type(Database)}") db = Database() print(f"db 对象类型: {type(db)}")
|
使用了装饰器之后,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) 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)
|
当然,我们也可以定义一个类来实现类似的功能,但是类需要实例化,而闭包不需要。并且,闭包的性能比类高。
在变量搜索过程中,其实还涉及了 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()); System.out.println(milkCoffee.getCost());
|
以上,我们可以看到:
- 每个装饰器都持有一个被装饰对象的引用。
- 装饰器可以动态地添加或删除功能。
- 装饰器和被装饰者实现相同的接口
- 装饰器可以嵌套使用。
常见的,以 Java 中的 IO 流为例:
1 2 3 4 5 6 7 8 9 10 11
| FileInputStream fileIn = new FileInputStream("data.bin");
int byte1 = fileIn.read();
DataInputStream dataIn = new DataInputStream(fileIn);
int intValue = dataIn.readInt(); double doubleValue = dataIn.readDouble(); String str = dataIn.readUTF();
|
它体现了良好的单一职责原则,动态、灵活地扩展了 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 中的装饰器体现了函数式编程(高阶函数)的思想,而装饰器模式体现了面向对象编程的思想,使用继承和组合。
- 从定义时机上,装饰器在被编译时实现,而装饰器模式在运行时实现。
- 从组合方式上,多个装饰器从里到外依次装饰,而装饰器模式可以嵌套使用。
但是,
- 都是对目标进行功能扩展。
- 都遵循开闭原则。都体现了单一职责原则。
- 都是关注分离点。即功能和业务逻辑的分离。