Часто считается лучшей практикой создавать геттеры и сеттеры для
общедоступных свойств класса. Многие языки позволяют реализовать это
по-разному, либо с помощью функции (например, person.getName()
), либо
с помощью специфичной для языка конструкции get
или set
. В Python
это делается с помощью @property
.
@decorator
декоратор свойств 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 для создания методов получения, установки и удаления (или методов мутатора ) для свойства в классе.
В этом случае @property
заставляет вас вызывать метод
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()
динамически создает новую функцию для
возврата с использованием функции ввода, добавляя код, который должен
выполняться до и после запуска исходной функции.
property
реализован по шаблону, аналогичному функции my_decorator
Используя @decorator
, он получает декорированную функцию в качестве
аргумента, как в моем примере:
some_func_decorated = my_decorator(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
) имеет методы
получения и установки.
Чтобы использовать их с нашим классом Person
, property
действует
как дескриптор, что означает, что у него есть собственные методы
__get __
() ,
__set __
() и
__delete __
().
__get__()
и __set__()
запускаются для объекта, когда свойство
извлекается или устанавливается, а __delete__()
запускается, когда
свойство удаляется с помощью del
.
Итак, person.full_name = 'Billy Bob'
запускает метод __set__()
,
унаследованный от object
. Это подводит нас к важному моменту - ваш
класс должен наследовать от 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
По сути, это то, как свойства работают под поверхностью.