Я пытаюсь понять, что такое дескрипторы Python и для чего они могут быть полезны.
Дескрипторы - это атрибуты класса (например, свойства или методы) с помощью любого из следующих специальных методов:
__get__
(метод дескриптора, не относящийся к данным, например, для метода / функции) __set__
(метод дескриптора данных, например, для экземпляра свойства) __delete__
(метод дескриптора данных)
Эти объекты дескриптора могут использоваться в качестве атрибутов в других определениях классов объектов.(То есть они живут в __dict__
объекта класса.)
Объекты дескриптора могут использоваться для программного управления результатами точечного поиска (например, foo.descriptor
) в нормальном выражении, присваиваниии даже удаление.
Функции / методы, связанные методы, property
, classmethod
и staticmethod
- все используют эти специальные методы для управления доступом к ним через пунктирный поиск.
A дескриптор данных , как и property
, может позволить ленивую оценку атрибутов на основе более простого состояния объекта, позволяя экземплярам использовать меньше памяти, чем если бы вы предварительно вычисляли каждый возможный атрибут.
Другой дескриптор данных, member_descriptor
, созданный __slots__
, позволяет экономить память, позволяя классу хранить данные в изменяемой структуре типа кортежа вместо более гибкой, нозанимающий пространство __dict__
.
Дескрипторы, не являющиеся данными, обычно экземпляры, классы и статические методы, получают свои неявные первые аргументы (обычно называемые cls
и self
соответственно) из своих данных.метод дескриптора, __get__
.
Большинству пользователей Python необходимо изучить только простое использование, и им не нужно больше изучать или понимать реализацию дескрипторов.
In Depth: What IsДескрипторы?
Дескриптор - это объект с любым из следующих методов (__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))
A Дескриптор данных имеет __set__
и / или __delete__
.
A Дескриптор без данных не имеет ни __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
__dict__
, затем - в конечном итоге возвращается к Non-Data-Descriptor.
Следствием этого порядка поиска является то, что не-дескрипторы данных, такие как функции / методы, могут быть переопределены экземплярами .
Повтор и следующие шаги
Мы имеемузнал, что дескрипторы - это объекты с любым из __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__
В противном случае ваши дескрипторы игнорируют класс владельца и экземпляры владельца, вместо этого сохраняя состояние в дескрипторе.Вы также можете легко обмениваться состоянием во всех экземплярах с помощью простого атрибута класса (при условии, что вы всегда устанавливаете его как класс с плавающей точкой и никогда не удаляете его, или если это удобно пользователям вашего кода):
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__
И вы не можете назначить переменную, которая не может быть преобразована в число с плавающей точкой:
>>> 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
Заключение
Мырассмотрены атрибуты, которые определяют дескрипторы, разницу между дескрипторами данных и не-данных, встроенные объекты, которые их используют, и конкретные вопросы об использовании.
Итак, еще раз, как бы вы использовали пример вопроса?Я надеюсь, что вы не будете.Я надеюсь, что вы начнете с моего первого предложения (простой атрибут класса) и перейдете ко второму предложению (декоратор свойств), если считаете, что это необходимо.