Menu
CoddyTech

类装饰器

本课属于 Coddy Python 学习旅程的“面向对象编程”单元。第 40 课,共 64 课。

类装饰器可以通过用另一个函数包装类来修改或增强类。它们的工作方式与函数装饰器类似,但应用于整个类。

这是一个没有装饰的简单类:

class Person:
    def __init__(self, name):
        self.name = name
        
    def greet(self):
        return f"Hello, my name is {self.name}"

创建一个类装饰器,用来添加一个新方法:

def add_farewell(cls):
    def farewell(self):
        return f"Goodbye from {self.name}"
    
    cls.farewell = farewell  # 将方法添加到类中
    return cls

使用 @ 将装饰器应用于类:

@add_farewell
class EnhancedPerson:
    def __init__(self, name):
        self.name = name
        
    def greet(self):
        return f"Hello, my name is {self.name}"

现在这个类既有原始方法,也有新增的方法:

person = EnhancedPerson("Alice")
print(person.greet())     # 你好,我叫 Alice
print(person.farewell())  # Alice 向你道别

你也可以包装现有方法:保存原始方法,这样就可以在包装器中继续调用它。如果你想在类已有的方法周围添加行为(例如跟踪或日志记录),这会很有用:

def add_tracking(cls):
    original_greet = cls.greet  # 保存原来的方法

    def tracked_greet(self):
        print(f"greet was called")   # 先执行额外操作
        return original_greet(self)  # 调用原来的方法

    cls.greet = tracked_greet  # 替换类中的方法
    return cls

@add_tracking
class TrackedPerson:
    def __init__(self, name):
        self.name = name

    def greet(self):
        return f"Hello, my name is {self.name}"

现在调用 greet 时,包装函数会先运行,然后再调用原始函数:

person = TrackedPerson("Alice")
print(person.greet())
# 调用了 greet
# 你好,我叫 Alice

包装现有方法的关键步骤如下:
1. 保存原始方法:original = cls.method
2. 定义一个调用 original(self) 并添加其他逻辑的新函数。
3. 将新函数重新赋给类:cls.method = new_function
4. 返回修改后的类。

要点:类装饰器以类为输入,对其进行修改或增强,然后返回修改后的类。它们可以添加方法、修改属性,或包装现有功能。在类定义上方使用 @decorator_name 来应用类装饰器。这样无需继承,也能以简洁的方式扩展类的功能。

challenge icon

挑战

中等

在这个挑战中,你将实现一个类装饰器,为方法添加调用跟踪功能。

  • decorator.py:实现类装饰器 add_counter
  • calculator.py:包含将使用你的装饰器的类

driver.py 文件中包含大量测试场景,用于验证你的实现是否适用于各种用例、继承模式和边界情况。你不需要修改这个文件,但查看它有助于你了解预期行为。

亲手试试

from calculator import Calculator

# 综合测试用例处理程序
test_case = input()

if test_case == "basic_functionality":
    calc = Calculator()
    print(calc.add(5, 3))  # 应输出 8
    print(calc.add(2, 7))  # 应输出 9
    print(calc.subtract(10, 4))  # 应输出 6
    print(calc.call_counts)  # 应输出 {'add': 2, 'subtract': 1}

elif test_case == "multiple_instances":
    calc1 = Calculator()
    calc2 = Calculator()
    
    print(calc1.add(5, 3))  # 应输出 8
    print(calc2.add(2, 7))  # 应输出 9
    print(calc1.subtract(10, 4))  # 应输出 6
    
    # 两个实例应共享同一个 call_counts
    print(calc1.call_counts)  # 应输出 {'add': 2, 'subtract': 1}
    print(calc2.call_counts)  # 应输出 {'add': 2, 'subtract': 1}
    print(calc1.call_counts is calc2.call_counts)  # 应输出 True

elif test_case == "method_call_order":
    calc = Calculator()
    
    print(calc.add(1, 2))  # 应输出 3
    print(calc.subtract(5, 2))  # 应输出 3
    print(calc.add(10, 20))  # 应输出 30
    print(calc.add(7, 3))  # 应输出 10
    print(calc.call_counts)  # 应输出 {'add': 3, 'subtract': 1}

elif test_case == "large_values":
    calc = Calculator()
    
    print(calc.add(1000000, 2000000))  # 应输出 3000000
    print(calc.subtract(5000000, 2000000))  # 应输出 3000000
    print(calc.call_counts)  # 应输出 {'add': 1, 'subtract': 1}

elif test_case == "negative_values":
    calc = Calculator()
    
    print(calc.add(-5, -3))  # 应输出 -8
    print(calc.subtract(-10, -4))  # 应输出 -6
    print(calc.call_counts)  # 应输出 {'add': 1, 'subtract': 1}

elif test_case == "float_values":
    calc = Calculator()
    
    print(calc.add(5.5, 3.2))  # 应输出 8.7
    print(calc.subtract(10.5, 4.2))  # 应输出 6.3
    print(calc.call_counts)  # 应输出 {'add': 1, 'subtract': 1}

elif test_case == "zero_values":
    calc = Calculator()
    
    print(calc.add(0, 0))  # 应输出 0
    print(calc.subtract(0, 0))  # 应输出 0
    print(calc.call_counts)  # 应输出 {'add': 1, 'subtract': 1}

elif test_case == "counter_reset":
    calc = Calculator()
    
    print(calc.add(5, 3))  # 应输出 8
    print(calc.call_counts)  # 应输出 {'add': 1, 'subtract': 0}
    
    # 重置计数器
    calc.call_counts = {"add": 0, "subtract": 0}
    
    print(calc.add(2, 7))  # 应输出 9
    print(calc.call_counts)  # 应输出 {'add': 0, 'subtract': 0}

elif test_case == "counter_manipulation":
    calc = Calculator()
    
    print(calc.add(5, 3))  # 应输出 8
    print(calc.call_counts)  # 应输出 {'add': 1, 'subtract': 0}
    
    # 直接修改计数器
    calc.call_counts["add"] = 100
    
    print(calc.add(2, 7))  # 应输出 9
    print(calc.call_counts)  # 应输出 {'add': 101, 'subtract': 0}

elif test_case == "performance_test":
    calc = Calculator()
    
    # 执行 1000 次 add 操作
    for i in range(1000):
        calc.add(i, i+1)
    
    # 执行 500 次 subtract 操作
    for i in range(500):
        calc.subtract(i+10, i)
    
    print(calc.call_counts)  # 应输出 {'add': 1000, 'subtract': 500}
quiz icon自我测试

这节课包含一个小测验。开始学习即可作答,并记录你的进度。

面向对象编程中的所有课

自己练习: Python 在线编译器