Menu
Coddy logo textTech

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

Часть раздела Object Oriented Programming путешествия по Python на Coddy. Урок 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
# Hello, my name is 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 операций сложения
    for i in range(1000):
        calc.add(i, i+1)
    
    # Выполнение 500 операций вычитания
    for i in range(500):
        calc.subtract(i+10, i)
    
    print(calc.call_counts)  # Должно вывести {'add': 1000, 'subtract': 500}
quiz iconПроверьте себя

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

Все уроки раздела Object Oriented Programming

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