Применение декораторов через наследование
На этапе 2 мы создали декоратор класса, который упрощает наш код. Декоратор класса - это особый тип функции, которая принимает класс в качестве аргумента и возвращает модифицированный класс. Это полезный инструмент в Python для добавления функциональности классам без изменения их исходного кода. Однако мы по-прежнему должны явно применять декоратор @validate_attributes
к каждому классу. Это означает, что каждый раз, когда мы создаем новый класс, который нуждается в валидации, мы должны помнить добавить этот декоратор, что может быть немного утомительно.
Мы можем улучшить это, автоматически применяя декоратор через наследование. Наследование - это фундаментальное понятие в объектно-ориентированном программировании, при котором подкласс может наследовать атрибуты и методы от родительского класса. Метод __init_subclass__
в Python был введен в версии 3.6, чтобы позволить родительским классам настраивать инициализацию подклассов. Это означает, что когда создается подкласс, родительский класс может выполнить некоторые действия над ним. Мы можем использовать эту возможность для автоматического применения нашего декоратора к любому классу, который наследует от Structure
.
Давайте реализуем это:
- Откройте файл
structure.py
:
code ~/project/structure.py
Здесь мы используем команду code
для открытия файла structure.py
в редакторе кода. Этот файл содержит определение класса Structure
, и мы собираемся изменить его, чтобы использовать метод __init_subclass__
.
- Добавьте метод
__init_subclass__
в класс Structure
:
class Structure:
_fields = ()
_types = ()
def __init__(self, *args):
if len(args) != len(self._fields):
raise TypeError(f'Expected {len(self._fields)} arguments')
for name, val in zip(self._fields, args):
setattr(self, name, val)
def __repr__(self):
values = ', '.join(repr(getattr(self, name)) for name in self._fields)
return f'{type(self).__name__}({values})'
@classmethod
def create_init(cls):
'''
Create an __init__ method from _fields
'''
body = 'def __init__(self, %s):\n' % ', '.join(cls._fields)
for name in cls._fields:
body += f' self.{name} = {name}\n'
## Execute the function creation code
namespace = {}
exec(body, namespace)
setattr(cls, '__init__', namespace['__init__'])
@classmethod
def __init_subclass__(cls):
validate_attributes(cls)
Метод __init_subclass__
- это метод класса, то есть его можно вызывать на самом классе, а не на экземпляре класса. Когда создается подкласс Structure
, этот метод будет автоматически вызван. Внутри этого метода мы вызываем декоратор validate_attributes
на подклассе cls
. Таким образом, каждый подкласс Structure
автоматически получит поведение валидации.
- Сохраните файл.
После внесения изменений в файл structure.py
мы должны сохранить его, чтобы изменения вступили в силу.
- Теперь давайте обновим файл
stock.py
, чтобы воспользоваться этой новой возможностью:
code ~/project/stock.py
Мы открываем файл stock.py
, чтобы изменить его. Этот файл содержит определение класса Stock
, и мы собираемся сделать его наследником класса Structure
, чтобы использовать автоматическое применение декоратора.
- Измените файл
stock.py
, чтобы удалить явный декоратор:
## stock.py
from structure import Structure
from validate import String, PositiveInteger, PositiveFloat
class Stock(Structure):
name = String()
shares = PositiveInteger()
price = PositiveFloat()
@property
def cost(self):
return self.shares * self.price
def sell(self, nshares):
self.shares -= nshares
Обратите внимание, что мы:
- Удалили импорт
validate_attributes
, так как нам больше не нужно его явно импортировать, так как декоратор применяется автоматически через наследование.
- Удалили декоратор
@validate_attributes
, так как метод __init_subclass__
в классе Structure
позаботится о его применении.
- Теперь код полностью зависит от наследования от
Structure
для получения поведения валидации.
- Запустите тесты еще раз, чтобы убедиться, что все по-прежнему работает:
cd ~/project
python3 teststock.py
Запуск тестов важен, чтобы убедиться, что наши изменения не сломали ничего. Если все тесты проходят, это означает, что автоматическое применение декоратора через наследование работает правильно.
Вы должны увидеть, что все тесты проходят:
.........
----------------------------------------------------------------------
Ran 9 tests in 0.001s
OK
Давайте протестируем наш класс Stock
еще раз, чтобы убедиться, что он работает как ожидается:
cd ~/project
python3 -c "from stock import Stock; s = Stock('GOOG', 100, 490.1); print(s); print(f'Cost: {s.cost}')"
Эта команда создает экземпляр класса Stock
и выводит его представление и стоимость. Если вывод соответствует ожиданиям, это означает, что класс Stock
работает правильно с автоматическим применением декоратора.
Вывод:
Stock('GOOG', 100, 490.1)
Cost: 49010.0
Эта реализация еще более чистая! Используя __init_subclass__
, мы избавились от необходимости явно применять декораторы. Любой класс, который наследует от Structure
, автоматически получает поведение валидации.