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

Часто считается лучшей практикой создавать геттеры и сеттеры для общедоступных свойств класса. Многие языки позволяют реализовать это по-разному, либо с помощью функции (например, person.getName ()), либо с помощью специфичной для языка конструкции get или set. В Python это делается с помощью @property. В этой статье я опишу декоратор свойств Python, который вы, возможно, видели, использованный с синтаксисом @decorator: class Person (object): def __init __ (self, first_name, l

Часто считается лучшей практикой создавать геттеры и сеттеры для общедоступных свойств класса. Многие языки позволяют реализовать это по-разному, либо с помощью функции (например, 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 

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

Licensed under CC BY-NC-SA 4.0
comments powered by Disqus