Menu
Coddy logo textTech

Декораторы классов

Часть раздела Объектно-ориентированное программирование путешествия по Python на Coddy. Урок 40 из 64.

Декораторы классов позволяют изменять или расширять классы, оборачивая их в другую функцию. Они работают подобно декораторам функций, но применяются ко всему классу.

Вот простой class без декорации:

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

Создайте class-декоратор, который добавляет новый method:

def add_farewell(cls):
    def farewell(self):
        return f"Goodbye from {self.name}"
    
    cls.farewell = farewell  # Добавить метод в класс
    return cls

Примени decorator к классу, используя @:

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

Теперь у класса есть both original и добавленные methods:

person = EnhancedPerson("Alice")
print(person.greet())     # Привет, меня зовут Alice
print(person.farewell())  # До свидания от Alice

Вы также можете обернуть существующий method: сохранить original, чтобы по-прежнему вызывать его внутри обёртки. Это полезно, когда вы хотите добавить behaviour (например, отслеживание или ведение журнала) вокруг method, который уже есть в class:

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. Вернуть изменённый класс.

Ключевой момент: class-декораторы принимают class в качестве input, выполняют Modify или Enhance его и возвращают modified class. Они могут add methods, Modify атрибуты или оборачивать существующую functionality. Используйте @decorator_name above class definition, чтобы применить их. Это предоставляет чистый способ расширить functionality class без наследования.

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)  # Should print {'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)  # Should print {'add': 2, 'subtract': 1}
    print(calc2.call_counts)  # Should print {'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)  # Should print {'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)  # Should print {'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)  # Should print {'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)  # Should print {'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)  # Should print {'add': 1, 'subtract': 1}

elif test_case == "counter_reset":
    calc = Calculator()
    
    print(calc.add(5, 3))  # Должно вывести 8
    print(calc.call_counts)  # Should print {'add': 1, 'subtract': 0}
    
    # Сбросить счётчик
    calc.call_counts = {"add": 0, "subtract": 0}
    
    print(calc.add(2, 7))  # Должно вывести 9
    print(calc.call_counts)  # Should print {'add': 0, 'subtract': 0}

elif test_case == "counter_manipulation":
    calc = Calculator()
    
    print(calc.add(5, 3))  # Должно вывести 8
    print(calc.call_counts)  # Should print {'add': 1, 'subtract': 0}
    
    # Изменить счётчик напрямую
    calc.call_counts["add"] = 100
    
    print(calc.add(2, 7))  # Должно вывести 9
    print(calc.call_counts)  # Should print {'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)  # Should print {'add': 1000, 'subtract': 500}
quiz iconПроверьте себя

В этом уроке есть небольшой тест. Начните урок, чтобы ответить на вопросы и сохранить прогресс.

Все уроки раздела Объектно-ориентированное программирование

Потренируйтесь самостоятельно: Онлайн-компилятор Python