Декораторы классов
Часть раздела 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 перед определением класса, чтобы применить их. Это обеспечивает чистый способ расширения функциональности класса без использования наследования.
Задание
СреднеВ этом испытании вы реализуете декоратор класса, который добавляет функциональность отслеживания вызовов методов.
decorator.py— Реализуйте декоратор классаadd_countercalculator.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}В этом уроке есть небольшой тест. Начните урок, чтобы ответить на вопросы и сохранить прогресс.
Все уроки раздела Object Oriented Programming
1Основы ООП
Внешние файлыВведение в ООПКлассы и объектыПараметр selfМетодыАтрибутыМетод-конструктор (__init__)Повторение — Простой калькулятор4Наследование
Базовое наследованиеФункция super()Переопределение методовМножественное наследованиеПорядок разрешения методовИтоги — Иерархия сотрудников7Специальные методы
Введение в магические методыПерегрузка операторовМагические методы контейнеровПовторение — Пользовательский список10Паттерны проектирования. Часть 1
Введение в паттерны проектированияПаттерн SingletonПаттерн FactoryПаттерн ObserverПаттерн Strategy5Полиморфизм
Снова о переопределении методовУтиная типизацияАбстрактные классы и методыПроектирование интерфейсовИтоги — Калькулятор фигур8Продвинутые концепции ООП
Композиция против наследованияМиксиныСтатические методы и методы классаДекораторы классовМенеджеры контекста11Паттерны проектирования. Часть 2
Паттерн КомандаПаттерн АдаптерПаттерн ДекораторПаттерн Шаблонный методПаттерн СостояниеПаттерн Компоновщик3Свойства классов
Переменные экземпляра и классаДекораторы свойствПриватные атрибутыИтоги — Менеджер банковского счета6Инкапсуляция
Public, Protected, Private члены классаМодификаторы доступаСокрытие данныхДекораторы property: продвинутый уровеньИтоги: Система учета студентов12Проект: Управление библиотекой
Обзор проектаКлассы Book и User