Я пытаюсь понять, что такое дескрипторы Python и для чего они полезны. Я понимаю, как они работают, но вот мои сомнения. Рассмотрим следующий код:
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()
Зачем мне нужен класс дескриптора?
Что
instance
иowner
здесь? (в__get__
). Какова цель этих параметров?Как бы я позвонил / использовал этот пример?
источник
self
аinstance
?self
- это экземпляр дескриптора,instance
это экземпляр класса (если он создан), в котором находится дескриптор (instance.__class__ is owner
).Temperature.celsius
дает значение в0.0
соответствии с кодомcelsius = Celsius()
. Дескриптор Celsius вызывается, поэтому его экземпляр имеет значение init,0.0
присвоенное атрибуту класса Temperature, celsius.Это дает вам дополнительный контроль над тем, как работают атрибуты. Например, если вы привыкли к геттерам и сеттерам в Java, то это способ Python. Одним из преимуществ является то, что он выглядит для пользователей как атрибут (нет изменений в синтаксисе). Таким образом, вы можете начать с обычного атрибута, а затем, когда вам нужно что-то сделать, переключиться на дескриптор.
Атрибут является просто изменяемым значением. Дескриптор позволяет вам выполнить произвольный код при чтении или установке (или удалении) значения. Таким образом, вы можете использовать его для сопоставления атрибута с полем в базе данных, например, своего рода ORM.
Другое использование может быть отказом принять новое значение, вызвав исключение
__set__
- фактически делая «атрибут» доступным только для чтения.Это довольно тонко (и причина, по которой я пишу новый ответ здесь - я нашел этот вопрос, задаваясь вопросом о том же самом, и не нашел существующего ответа таким большим).
Дескриптор определен в классе, но обычно вызывается из экземпляра. При вызове из экземпляра как
instance
иowner
установлены (и вы можете работатьowner
сinstance
так что кажется , своего рода бессмысленно). Но когдаowner
вызывается из класса, устанавливается только - вот почему он там.Это нужно только
__get__
потому, что он единственный, который можно вызвать в классе. Если вы устанавливаете значение класса, вы устанавливаете сам дескриптор. Аналогично для удаления. Вот почему тамowner
не нужно.Ну, вот крутой трюк с использованием похожих классов:
(Я использую Python 3; для Python 2 вы должны убедиться, что эти подразделения
/ 5.0
и/ 9.0
). Это дает:Теперь есть и другие, возможно, лучшие способы достижения того же эффекта в python (например, если цельсий - это свойство, которое является тем же базовым механизмом, но помещает весь источник внутри класса Temperature), но это показывает, что можно сделать ...
источник
Дескрипторы - это атрибуты класса (например, свойства или методы) с любым из следующих специальных методов:
__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
)Чтобы определить это с помощью кода, объект является дескриптором, если набор его атрибутов пересекается с любым из обязательных атрибутов:
Data Descriptor имеет
__set__
и / или__delete__
. Non-Data-Descriptor не имеет ни ни .__set__
__delete__
Примеры объектов встроенного дескриптора:
classmethod
staticmethod
property
Дескрипторы без данных
Мы можем видеть это
classmethod
иstaticmethod
не-дескрипторы:У обоих есть только
__get__
метод:Обратите внимание, что все функции также не-дескрипторы:
Дескриптор данных,
property
Тем не менее,
property
это дескриптор данных:Пунктирный порядок поиска
Это важные различия , поскольку они влияют на порядок поиска для точечного поиска.
obj_instance
«х__dict__
, тоСледствием этого порядка поиска является то, что не-дескрипторы данных, такие как функции / методы, могут быть переопределены экземплярами .
Резюме и следующие шаги
Мы узнали , что дескрипторы объектов с любой из
__get__
,__set__
или__delete__
. Эти объекты дескриптора могут использоваться в качестве атрибутов в других определениях классов объектов. Теперь посмотрим, как они используются, используя ваш код в качестве примера.Анализ кода из вопроса
Вот ваш код, затем ваши вопросы и ответы на каждый из них:
Ваш дескриптор гарантирует, что у вас всегда есть float для этого атрибута класса
Temperature
, и что вы не можете использоватьdel
для удаления атрибута:В противном случае ваши дескрипторы игнорируют класс владельца и экземпляры владельца, вместо этого сохраняя состояние в дескрипторе. Вы также можете легко обмениваться состоянием во всех экземплярах с помощью простого атрибута класса (при условии, что вы всегда устанавливаете его как класс с плавающей точкой и никогда не удаляете его, или если это удобно для пользователей вашего кода):
Это приводит вас к тому же поведению, что и в вашем примере (см. Ответ на вопрос 3 ниже), но использует встроенную функцию Pythons (
property
) и будет рассматриваться как более идиоматическая:instance
это экземпляр владельца, который вызывает дескриптор. Владелец - это класс, в котором объект дескриптора используется для управления доступом к точке данных. См. Описания специальных методов, которые определяют дескрипторы рядом с первым абзацем этого ответа, для более наглядных имен переменных.Вот демонстрация:
Вы не можете удалить атрибут:
И вы не можете назначить переменную, которая не может быть преобразована в число с плавающей точкой:
В противном случае у вас есть глобальное состояние для всех экземпляров, которое управляется назначением любому экземпляру.
Ожидаемый способ, которым большинство опытных программистов на Python достигли этого результата, будет использовать
property
декоратор, который использует те же дескрипторы под капотом, но переносит поведение в реализацию класса владельца (опять же, как определено выше):Который имеет точно такое же ожидаемое поведение исходного куска кода:
Вывод
Мы рассмотрели атрибуты, которые определяют дескрипторы, разницу между дескрипторами данных и не-данных, встроенные объекты, которые их используют, и конкретные вопросы об использовании.
Итак, еще раз, как бы вы использовали пример вопроса? Я надеюсь, что вы не будете. Я надеюсь, что вы начнете с моего первого предложения (простой атрибут класса) и перейдете ко второму предложению (декоратор свойств), если считаете, что это необходимо.
источник
Прежде чем углубляться в детали дескрипторов, может быть важно узнать, как работает поиск атрибутов в Python. Это предполагает, что у класса нет метакласса, и что он использует реализацию по умолчанию
__getattribute__
(обе могут использоваться для «настройки» поведения).Лучшая иллюстрация поиска атрибутов (в Python 3.x или для классов нового стиля в Python 2.x) в этом случае - из Понимания метаклассов Python (кодовый журнал ionel) . Изображение используется в
:
качестве замены для «ненастраиваемого поиска атрибутов».Это означает поиск атрибута
foobar
наinstance
изClass
:Здесь важны два условия:
instance
есть запись для имени атрибута, и он имеет__get__
и__set__
.instance
нет нет записи для имени атрибута , но класс имеет один и у него есть__get__
.Вот где дескрипторы входят в это:
__get__
и__set__
.__get__
.В обоих случаях возвращаемое значение вызывается
__get__
с использованием экземпляра в качестве первого аргумента и класса в качестве второго аргумента.Поиск еще более сложен для поиска атрибутов класса (см., Например, поиск атрибутов класса (в вышеупомянутом блоге) ).
Давайте перейдем к вашим конкретным вопросам:
В большинстве случаев вам не нужно писать дескрипторные классы! Однако вы, вероятно, очень обычный конечный пользователь. Например функции. Функции являются дескрипторами, поэтому функции могут использоваться как методы с
self
неявно передаваемым в качестве первого аргумента.Если вы посмотрите
test_method
на экземпляр, вы получите «связанный метод»:Точно так же вы могли бы также связать функцию, вызывая ее
__get__
метод вручную (не очень рекомендуется, только для иллюстративных целей):Вы даже можете назвать этот «самосвязанный метод»:
Обратите внимание, что я не предоставил никаких аргументов, и функция вернула экземпляр, который я связал!
Функции являются дескрипторами без данных !
Было бы несколько встроенных примеров дескриптора данных
property
. Пренебрегаяgetter
,setter
и дескриптор (от Descriptor HowTo Guide "Свойства" ):deleter
property
Поскольку это данные дескриптора он вызывается всякий раз , когда вы смотрите на «имя»
property
и он просто делегирует функцию украшенной@property
,@name.setter
и@name.deleter
(если имеется).Есть несколько других дескрипторов в стандартной библиотеке, например
staticmethod
,classmethod
.Суть дескрипторов проста (хотя они вам редко нужны): абстрактный общий код для доступа к атрибутам.
property
является абстракцией для доступа к переменным экземпляра,function
предоставляет абстракцию для методов,staticmethod
обеспечивает абстракцию для методов, которым не требуется доступ к экземпляру, иclassmethod
предоставляет абстракцию для методов, которые требуют доступа к классу, а не доступа к экземпляру (это немного упрощено).Другим примером будет свойство класса .
Один забавный пример (с использованием
__set_name__
Python 3.6) также может быть свойством, которое допускает только определенный тип:Затем вы можете использовать дескриптор в классе:
И немного поиграть с этим:
Или "ленивая собственность"
Это случаи, когда перемещение логики в общий дескриптор может иметь смысл, однако можно также решить их (но, возможно, повторить некоторый код) другими способами.
Это зависит от того, как вы смотрите атрибут. Если вы посмотрите на атрибут в экземпляре, то:
Если вы ищите атрибут в классе (при условии, что дескриптор определен в классе):
None
Так что в принципе третий аргумент необходим, если вы хотите настроить поведение при поиске на уровне класса (потому что он
instance
естьNone
).В основном, ваш пример - это свойство, которое допускает только те значения, которые могут быть преобразованы
float
и которые совместно используются всеми экземплярами класса (и класса), хотя в классе можно использовать только «чтение», в противном случае вы бы заменили экземпляр дескриптора. ):Вот почему дескрипторы обычно используют второй аргумент (
instance
) для хранения значения, чтобы избежать его совместного использования. Однако в некоторых случаях может быть желательным разделение значения между экземплярами (хотя я не могу думать о сценарии в данный момент). Однако для градуса Цельсия практически нет смысла в температурном классе ... за исключением, может быть, чисто академического упражнения.источник
Вдохновленный Свободным Питоном Buciano Ramalho
Imaging у вас есть такой класс
Мы должны проверить вес и цену во избежание присвоения им отрицательного числа, мы можем написать меньше кода, если мы используем дескриптор в качестве прокси, как это
затем определите класс LineItem следующим образом:
и мы можем расширить класс количества, чтобы сделать более общую проверку
источник
weight = Quantity()
, но значения должны быть установлены в пространстве имен экземпляра только с использованиемself
(напримерself.weight = 4
), в противном случае атрибут будет возвращен к новому значению и дескриптор будет отброшен Nice.!weight = Quantity()
как переменную класса и его__get__
и__set__
работаете над переменной экземпляра. Как?Я попробовал (с незначительными изменениями, как предложено) код из ответа Эндрю Кука. (Я использую Python 2.7).
Код:
Результат:
С Python до 3, убедитесь, что вы подкласс от объекта, который заставит дескриптор работать правильно, так как get magic не работает для классов старого стиля.
источник
Вы увидите https://docs.python.org/3/howto/descriptor.html#properties
источник