Рубрики
Без рубрики

Декоратор свойств Python

Автор оригинала: Scott Robinson.

Часто считается лучшей практикой создавать геттеры и сеттеры для открытых свойств класса. Многие языки позволяют реализовать это по-разному, либо с помощью функции (например, person.getName () ), либо с помощью специфичной для языка конструкции get или set . В Python это делается с помощью @property .

В этой статье я опишу декоратор свойств Python, который, возможно, использовался с синтаксисом @decorator :

class Person(object):
    def __init__(self, first_name, last_name):
        self.first_name = first_name
        self.last_name = last_name

    @property
    def full_name(self):
        return self.first_name + ' ' + self.last_name

    @full_name.setter
    def full_name(self, value):
        first_name, last_name = value.split(' ')
        self.first_name = first_name
        self.last_name = last_name

    @full_name.deleter
    def full_name(self):
        del self.first_name
        del self.last_name

Это способ Python создавать геттеры, сеттеры и delete (или мутаторные методы ) для свойства в классе.

В этом случае @property decorator делает так, что вы вызываете метод full_name(self) , как будто это просто обычное свойство, тогда как на самом деле это метод, содержащий код, который будет выполняться при установке свойства.

Использование геттера/сеттера/делетера, подобного этому, дает нам довольно много преимуществ, некоторые из которых я перечислил здесь:

  • Проверка: Перед установкой внутреннего свойства вы можете проверить, что предоставленное значение соответствует некоторым критериям, и заставить его выдать ошибку, если это не так.
  • Ленивая загрузка: Ресурсы могут лениво загружаться откладывать работу до тех пор, пока она действительно не понадобится, экономя время и ресурсы
  • Абстракция: Геттеры и сеттеры позволяют абстрагировать внутреннее представление данных. Как и в нашем примере выше, например, имя и фамилия хранятся отдельно, но геттеры и сеттеры содержат логику, которая использует имя и фамилию для создания полного имени.
  • Отладка: Поскольку методы мутатора могут инкапсулировать любой код, он становится отличным местом для перехвата при отладке (или протоколировании) вашего кода. Например, вы можете регистрировать или проверять каждый раз, когда изменяется значение свойства.

Python достигает этой функциональности с помощью декораторов, которые являются специальными методами, используемыми для изменения поведения другой функции или класса. Чтобы описать, как работает декоратор @property , давайте рассмотрим более простой декоратор и то, как он работает внутри.

Декоратор-это просто функция, которая принимает другую функцию в качестве аргумента и добавляет к ее поведению, обернув ее. Вот простой пример:

# decorator.py

def some_func():
    print 'Hey, you guys'

def my_decorator(func):
    def inner():
        print 'Before func!'
        func()
        print 'After func!'

    return inner

print 'some_func():'
some_func()

print ''

some_func_decorated = my_decorator(some_func)

print 'some_func() with decorator:'
some_func_decorated()

Запуск этого кода дает вам:

$ python decorator.py
some_func():
Hey, you guys

some_func() with decorator:
Before func!
Hey, you guys
After func!

Как вы можете видеть, функция my_decorator() динамически создает новую функцию для возврата с помощью функции ввода, добавляя код, который будет выполняться до и после запуска исходной функции.

свойство decorator реализовано с помощью шаблона, аналогичного функции my_decorator . Используя синтаксис Python @decorator , он получает украшенную функцию в качестве аргумента, как и в моем примере: some_func_decorated(some_func) .

Итак, возвращаясь к моему первому примеру, этот код:

@property
def full_name_getter(self):
    return self.first_name + ' ' + self.last_name

Примерно эквивалентно этому:

def full_name_getter(self):
    return self.first_name + ' ' + self.last_name

full_name = property(full_name_getter)

Обратите внимание, что я изменил некоторые имена функций для ясности.

Затем, позже, когда вы захотите использовать @full_name.setter как мы делаем в этом примере, то, что вы действительно вызываете, это:

def full_name_setter(self, value):
    first_name, last_name = value.split(' ')
    self.first_name = first_name
    self.last_name = last_name

full_name = property(full_name_getter)
full_name = full_name.setter(full_name_setter)

Теперь этот новый объект full_name (экземпляр объекта property ) имеет как методы getter, так и setter.

Чтобы использовать их с нашим классом, Person , объект property действует как дескриптор, что означает, что он имеет свой собственный __get__() , __set__() и __delete__() методы. Методы __get__() и __set__() срабатывают на объекте при извлечении или установке свойства, а __delete__() срабатывают при удалении свойства с помощью del .

Таким образом, person.full_name запускает метод __set__ () , который был унаследован от object . Это подводит нас к важному моменту – ваш класс должен наследовать от object для того, чтобы это работало . Таким образом, такой класс не сможет использовать свойства сеттера, так как он не наследуется от объекта :

class Person:
    pass

Благодаря property эти методы теперь соответствуют нашим full_name_getter и full_name_setter методам сверху:

full_name.fget is full_name_getter    # True
full_name.fset is full_name_setter    # True

fget и fset теперь обернуты .__get__() и .__set__ () соответственно.

И, наконец, к этим объектам дескриптора можно получить доступ, передав ссылку на наш класс, Person :

>>> person = Person('Billy', 'Bob')
>>> 
>>> full_name.__get__(person)
Billy Bob
>>>
>>> full_name.__set__(person, 'Timmy Thomas')
>>>
>>> person.first_name
Timmy
>>> person.last_name
Thomas

Это, по сути, то, как свойства работают под поверхностью.