Я намагаюся зрозуміти, що таке дескриптори Python і для чого вони можуть бути корисними.
Дескриптори - це атрибути класу (наприклад, властивості або методи) за допомогою будь-якого з наступних спеціальних методів:
__get__
(метод дескриптора даних, наприклад, про метод / функцію)
__set__
(метод дескриптора даних, наприклад, в екземплярі властивості)
__delete__
(метод дескриптора даних)
Ці об'єкти дескриптора можуть використовуватися як атрибути для інших визначень класів об'єктів. (Тобто вони живуть в __dict__
об'єкті класу.)
Об'єкти дескриптора можуть використовуватися для програмного управління результатами пунктирного пошуку (наприклад foo.descriptor
) у звичайному виразі, призначенні та навіть видаленні.
Функції / методи, пов'язані методи, property
, classmethod
і staticmethod
все використовує ці спеціальні методи контролю , як вони доступні через пунктирний пошук.
Дескриптор даних , як property
, може дозволити ледачі обчислення атрибутів , заснованих на більш простому стані об'єкта, що дозволяє екземплярів використовувати менше пам'яті , ніж якщо ви попередньо обчислені кожен можливий атрибут.
Інший дескриптор даних member_descriptor
, створений компанією __slots__
, дозволяє економити пам'ять, дозволяючи класу зберігати дані в змінній структурі даних, схожих на кортеж, а не в більш гнучку, але затратну на багато місця __dict__
.
Дескриптори Non-дані, як правило , наприклад, клас, і статичні методи, отримати їх неявні перші аргументи (зазвичай з ім'ям cls
і self
, відповідно) від способу їх дескрипторів без даних __get__
.
Більшість користувачів Python повинні навчитися лише простому використанню, і не потрібно далі вивчати чи розуміти реалізацію дескрипторів.
В глибині: що таке дескриптори?
Дескриптор - це об'єкт із будь-яким із наведених нижче методів ( __get__
, __set__
або __delete__
), призначених для використання за допомогою пунктирного пошуку, як би типовим атрибутом екземпляра. Для власника-об'єкта obj_instance
, з descriptor
об’єктом:
obj_instance.descriptor
викликає
descriptor.__get__(self, obj_instance, owner_class)
повернення. value
Ось як працюють усі методи та get
властивості.
obj_instance.descriptor = value
викликає
descriptor.__set__(self, obj_instance, value)
повернення None
Ось як setter
працює властивість.
del obj_instance.descriptor
викликає
descriptor.__delete__(self, obj_instance)
повернення None
Ось як deleter
працює властивість.
obj_instance
- це екземпляр, клас якого містить екземпляр об'єкта дескриптора. self
- це екземпляр дескриптора (можливо, лише один для класу obj_instance
)
Для визначення цього коду об'єкт є дескриптором, якщо набір його атрибутів перетинається з будь-яким із необхідних атрибутів:
def has_descriptor_attrs(obj):
return set(['__get__', '__set__', '__delete__']).intersection(dir(obj))
def is_descriptor(obj):
"""obj can be instance of descriptor or the descriptor class"""
return bool(has_descriptor_attrs(obj))
Data Descriptor має __set__
і / або __delete__
. Non-Data-Descriptor не має ні ні .
__set__
__delete__
def has_data_descriptor_attrs(obj):
return set(['__set__', '__delete__']) & set(dir(obj))
def is_data_descriptor(obj):
return bool(has_data_descriptor_attrs(obj))
Приклади об’єктів вбудованого дескриптора:
classmethod
staticmethod
property
- функції в цілому
Дескриптори даних, що не належать до даних
Ми можемо це бачити classmethod
і staticmethod
є Дескрипторами Неданих:
>>> is_descriptor(classmethod), is_data_descriptor(classmethod)
(True, False)
>>> is_descriptor(staticmethod), is_data_descriptor(staticmethod)
(True, False)
В обох є лише __get__
метод:
>>> has_descriptor_attrs(classmethod), has_descriptor_attrs(staticmethod)
(set(['__get__']), set(['__get__']))
Зауважте, що всі функції також є Дескрипторами даних:
>>> def foo(): pass
...
>>> is_descriptor(foo), is_data_descriptor(foo)
(True, False)
Дескриптор даних, property
Однак, property
це Дескриптор даних:
>>> is_data_descriptor(property)
True
>>> has_descriptor_attrs(property)
set(['__set__', '__get__', '__delete__'])
Порядок пошуку пунктирною
Це важливі відмінності , оскільки вони впливають на порядок пошуку пунктирного пошуку.
obj_instance.attribute
- По-перше, вище виглядає, щоб означити, чи атрибут є Дескриптором даних для класу екземпляра
- Якщо ні, то виглядає, чи є атрибут у
obj_instance
's __dict__
, то
- остаточно повертається до Дескриптора без даних.
Наслідком цього порядку пошуку є те, що не-дані-дескриптори, як функції / методи, можуть бути замінені примірниками .
Резюме та наступні кроки
Ми дізналися , що дескриптори об'єктів з будь-якої з __get__
, __set__
або __delete__
. Ці об'єкти дескриптора можуть використовуватися як атрибути для інших визначень класів об'єктів. Тепер ми розглянемо, як вони використовуються, використовуючи ваш приклад як приклад.
Аналіз кодексу з питання
Ось ваш код, а потім ваші запитання та відповіді до кожного:
class Celsius(object):
def __init__(self, value=0.0):
self.value = float(value)
def __get__(self, instance, owner):
return self.value
def __set__(self, instance, value):
self.value = float(value)
class Temperature(object):
celsius = Celsius()
- Навіщо мені потрібен клас дескриптора?
Ваш дескриптор гарантує, що ви завжди маєте float для цього атрибута класу Temperature
, і що ви не можете використовувати del
для видалення атрибута:
>>> t1 = Temperature()
>>> del t1.celsius
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: __delete__
В іншому випадку ваші дескриптори ігнорують клас власника та екземпляри власника, натомість зберігають стан у дескрипторі. Ви можете так само легко ділитися станом у всіх примірниках за допомогою простого атрибута класу (до тих пір, поки ви завжди встановлюєте його як float до класу і ніколи не видаляєте його, або вам зручно користувачі вашого коду):
class Temperature(object):
celsius = 0.0
Це дає вам точно таку поведінку, як ваш приклад (див. Відповідь на запитання 3 нижче), але використовує вбудований Pythons ( property
) і вважатиметься більш ідіоматичним:
class Temperature(object):
_celsius = 0.0
@property
def celsius(self):
return type(self)._celsius
@celsius.setter
def celsius(self, value):
type(self)._celsius = float(value)
- Що тут примірник і власник? (в отримати ). Яке призначення цих параметрів?
instance
- це примірник власника, який викликає дескриптор. Власник - клас, в якому об'єкт дескриптора використовується для управління доступом до точки даних. Див. Описи спеціальних методів, які визначають дескриптори поруч із першим абзацом цієї відповіді для отримання більш описових імен змінних.
- Як я б назвав / використав цей приклад?
Ось демонстрація:
>>> t1 = Temperature()
>>> t1.celsius
0.0
>>> t1.celsius = 1
>>>
>>> t1.celsius
1.0
>>> t2 = Temperature()
>>> t2.celsius
1.0
Ви не можете видалити атрибут:
>>> del t2.celsius
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: __delete__
І ви не можете призначити змінну, яку неможливо перетворити на float:
>>> t1.celsius = '0x02'
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 7, in __set__
ValueError: invalid literal for float(): 0x02
В іншому випадку те, що у вас є, є глобальним станом для всіх примірників, яким керується шляхом присвоєння будь-якому екземпляру.
Очікуваний спосіб, коли найбільш досвідчені програмісти Python досягнуть цього результату, буде використовувати property
декоратор, який використовує ті самі дескриптори під кришкою, але вводить поведінку у реалізацію класу власника (знову, як визначено вище):
class Temperature(object):
_celsius = 0.0
@property
def celsius(self):
return type(self)._celsius
@celsius.setter
def celsius(self, value):
type(self)._celsius = float(value)
Яка така ж очікувана поведінка оригінального коду:
>>> t1 = Temperature()
>>> t2 = Temperature()
>>> t1.celsius
0.0
>>> t1.celsius = 1.0
>>> t2.celsius
1.0
>>> del t1.celsius
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: can't delete attribute
>>> t1.celsius = '0x02'
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 8, in celsius
ValueError: invalid literal for float(): 0x02
Висновок
Ми розглянули атрибути, що визначають дескриптори, різницю між дескрипторами даних і не-даних, вбудовані об'єкти, які ними користуються, та конкретні питання щодо використання.
Отже, знову ж таки, як би ви використали приклад запитання? Сподіваюсь, ви цього не зробили. Сподіваюся, ви почнете з моєї першої пропозиції (простий атрибут класу) і перейдете до другої пропозиції (декоратора властивостей), якщо вважаєте, що це необхідно.
self
іinstance
?