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 содержит обширные тестовые сценарии, которые проверят вашу реализацию на соответствие различным вариантам использования, паттернам наследования и граничным случаям. Вам не нужно изменять этот файл, но его изучение поможет вам понять ожидаемое поведение.

Шпаргалка

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

Базовый синтаксис декоратора класса:

def decorator_name(cls):
    # Modify the class
    return cls

@decorator_name
class MyClass:
    pass

Пример — добавление метода в класс:

def add_farewell(cls):
    def farewell(self):
        return f"Goodbye from {self.name}"
    
    cls.farewell = farewell  # Add the method to the class
    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())     # Hello, my name is Alice
print(person.farewell())  # Goodbye from Alice

Пример — оборачивание существующего метода (сохранение оригинала, затем замена):

def loud_greet(cls):
    original_greet = cls.greet  # Store the original method

    def new_greet(self):
        result = original_greet(self)  # Call the original
        return result.upper()          # Enhance the result

    cls.greet = new_greet  # Replace with the wrapped version
    return cls

@loud_greet
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())  # HELLO, MY NAME IS ALICE

Ключевые шаги для оборачивания существующего метода:

original = cls.method_name — сохраните оригинальный метод
def new_method(self): original(self) — вызовите его внутри обертки
cls.method_name = new_method — замените метод в классе

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

Попробуйте сами

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