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