На этом шаге мы рассмотрим решение этой задачи.
Вы пишете классы, где постоянно вынуждены повторять определение методов свойств, которые выполняют обычные задачи, такие как проверка типов.
Вы хотели бы упростить код и убрать из него повторения.
Рассмотрите простой класс, в котором атрибуты обернуты методами-свойствами:
class Person: def __init__(self, name ,age): self.name = name self.age = age @property def name(self): return self._name @name.setter def name(self, value): if not isinstance(value, str): raise TypeError('name must be a string') self._name = value @property def age(self): return self._age @age.setter def age(self, value): if not isinstance(value, int): raise TypeError('age must be an int') self._age = value
Как вы можете видеть, куча кода написана просто для того, чтобы внедрить принудительную проверку типов значений атрибутов. Всякий раз, когда вы видите такой код, вы должны поискать пути упрощения. Возможный подход - создать функцию, которая определяет свойство и возвращает его. Например:
def typed_property(name, expected_type): storage_name = '_' + name @property def prop(self): return getattr(self, storage_name) @prop.setter def prop(self, value): if not isinstance(value, expected_type): raise TypeError('{} must be a {}'.format(name, expected_type)) setattr(self, storage_name, value) return prop # Пример class Person: name = typed_property('name', str) age = typed_property('age', int) def __init__(self, name, age): self.name = name self.age = age
Этот рецепт иллюстрирует важную возможность вложенных функций (замыканий) - с точки зрения работы они во многом похожи на макросы. Функция typed_property() в этом примере может показаться странной, но она просто генерирует для вас код свойства и возвращает получившийся объект свойства. Так что при использовании в классе она работает точно так же, как если бы код, появляющийся внутри typed_property(), был помещен в само определение класса. Хотя методы свойства геттер и сеттер обращаются к локальным переменным, таким как name, expected_type и storage_name, это нормально - эти значения хранятся в замыкании.
Данный рецепт можно интересным образом "улучшить", используя функцию functools.partial(). Например, вы можете сделать так:
from functools import partial String = partial(typed_property, expected_type=str) Integer = partial(typed_property, expected_type=int) # Пример class Person: name = String('name') age = Integer('age') def __init__(self, name, age): self.name = name self.age = age
Тут код начинает походить на дескриптор системы типов, показанный в 142 шаге.
На следующем шаге мы рассмотрим легкий способ определения менеджеров контекста.