Понимание дескрипторов get и set и Python
Я попытка чтобы понять, что такое дескрипторы 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__). Так что мой вопрос в том, какова цель из третьего параметра здесь?как бы я назвал / использовать этот пример?
5 ответов:
дескриптор, как в Python тип. Дескриптор просто реализует
__get__,__set__и т. д. и затем добавляется к другому классу в его определении (как вы сделали выше с классом температуры). Например:temp=Temperature() temp.celsius #calls celsius.__get__доступ к свойству, которому вы назначили дескриптор (
celsiusв приведенном выше примере) вызывает соответствующий метод дескриптора.
instanceна__get__является экземпляром класса (так выше,__get__будет получитьtemp, аowner- это класс с дескриптором (так было быTemperature).вы должны использовать класс дескриптора для инкапсуляции логики, которая приводит его в действие. Таким образом, если дескриптор используется для кэширования какой-либо дорогостоящей операции (например), он может хранить значение на себе, а не на своем классе.
статью о дескрипторах можно найти здесь.
EDIT: как указал jchl в комментариях, если вы просто попробуете
Temperature.celsius,instanceбудетNone.
зачем мне нужен класс дескриптора? Пожалуйста, объясните, используя этот пример или тот, который вы считаете лучше.
это дает вам дополнительный контроль над тем, как атрибуты работы. если вы привыкли к геттерам и сеттерам в java, например, то это способ python сделать это. одним из преимуществ является то, что он выглядит для пользователей так же, как атрибут (нет никаких изменений в синтаксисе). поэтому вы можете начать с обычного атрибута, а затем, когда вам нужно сделать что-то необычное, переключитесь на дескриптор.
атрибут - это просто изменяемое значение. дескриптор позволяет выполнять произвольный код при чтении или установке (или удалении) значения. таким образом, вы можете представить себе его использование для сопоставления атрибута с полем в базе данных, например - своего рода ORM.
другое использование может отказываться принимать новое значение, бросая исключение в
__set__- эффективно делать атрибут "только для чтения".что такое экземпляр и владелец здесь? (в
__get__). Итак, мой вопрос: какова цель третьего параметра здесь?это довольно тонко (и причина, по которой я пишу новый ответ здесь - я нашел этот вопрос, задаваясь тем же вопросом, и не нашел существующего ответа, который велик).
дескриптор определяется в классе, но обычно вызывается из экземпляра. когда он вызывается из экземпляра как
instanceиownerустанавливаются (и вы можете работатьownerотinstanceтак что это кажется бессмысленным). но когда вызывается из класса, толькоownerустановлен - вот почему он там.это необходимо только для
__get__потому что это единственный, который может быть вызван на класс. если вы зададите значение класса, вы зададите сам дескриптор. аналогично для удаления. вот почемуownerтам не нужно.как бы я назвал / использовать этот пример?
Ну, вот классный трюк с использованием подобных классы:
class Celsius: def __get__(self, instance, owner): return 5 * (instance.fahrenheit - 32) / 9 def __set__(self, instance, value): instance.fahrenheit = 32 + 9 * value / 5 class Temperature: celsius = Celsius() def __init__(self, initial_f): self.fahrenheit = initial_f t = Temperature(212) print(t.celsius) t.celsius = 0 print(t.fahrenheit)(я использую python 3; для python 2 вам нужно убедиться, что эти подразделения
/ 5.0и/ 9.0). что дает:100.0 32.0теперь есть другие, возможно, лучшие способы достижения того же эффекта в python (например, если бы celsius был свойством, которое является тем же основным механизмом, но помещает весь источник внутри температурного класса), но это показывает, что можно сделать...
я пытаюсь понять, что такое дескрипторы Python и для чего они могут быть полезны.
дескрипторы-это атрибуты класса (например, свойства или методы) с любым из следующих специальных методов:
__get__(метод дескриптора без данных, например, для метода / функции)__set__(метод дескриптора данных, например на экземпляре свойства)__delete__(дескриптор данных метод)эти объекты дескриптора могут использоваться в качестве атрибутов в других определениях классов объектов. (То есть, они живут в
__dict__объекта класса.)объекты дескриптора могут использоваться для программного управления результатами точечного поиска (например,
foo.descriptor) в обычном выражении, присваивании и даже удалении.функции / методы, связанные методы,
property,classmethodиstaticmethodвсе используют эти специальные методы для управления как они доступны через точечный поиск.A дескриптор данных, как
property, может разрешить ленивую оценку атрибутов на основе более простого состояния объекта, позволяя экземплярам использовать меньше памяти, чем если бы вы предварительно вычисляли каждый возможный атрибут.другой дескриптор данных, a
member_descriptor, создано__slots__, позволяет экономить память, позволяя классу хранить данные в изменяемой кортеж-подобной структуре данных вместо более гибкий, но занимающий много места__dict__.неданные дескрипторы, обычно экземпляр, класс и статические методы, получают свои неявные первые аргументы (обычно называемые
clsиself, соответственно) из их метода дескриптора без данных,__get__.большинство пользователей Python должны изучить только простое использование, и нет необходимости изучать или понимать реализацию дескрипторов дальше.
В Глубину: Что Такое Дескрипторы?
дескриптор-это объект с любым из следующих методов (
__get__,__set__или__delete__), предназначенный для использования через точечный поиск, как если бы это был типичный атрибут экземпляра. Для объекта-владельца,obj_instanceС ,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__в противном случае ваши дескрипторы игнорируют класс владельца и экземпляры владельца, вместо этого сохраняя состояние в дескрипторе. Вы можете так же легко поделиться состоянием во всех экземплярах с помощью простого атрибута класса (если вы всегда устанавливаете его как плавающее значение для класса и никогда не удаляете его или удобно для пользователей вашего кода):
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иownerздесь? (в__get__). Итак, мой вопрос: какова цель третьего параметра здесь?
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__и вы не можете назначить переменную, которая не может быть преобразована в a поплавок:
>>> 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вывод
мы рассмотрели атрибуты, определяющие дескрипторы, разницу между дескрипторами данных и не - данных, встроенные объекты, которые их используют, и конкретные вопросы об использовании.
Итак, еще раз, как бы вы использовали пример вопроса? Я надеюсь, что вы начнете с моего первого предложения (простой атрибут класса) и перейдете ко второму предложению (декоратор недвижимости) если вы чувствуете, что это необходимо.
зачем мне нужен класс дескриптора? Пожалуйста, объясните, используя этот пример или тот, который вы считаете лучше.
вдохновленный Свободно Python by Buciano Ramalho
Imaging у вас есть такой класс
class LineItem: price = 10.9 weight = 2.1 def __init__(self, name, price, weight): self.name = name self.price = price self.weight = weight item = LineItem("apple", 2.9, 2.1) item.price = -0.9 # it's price is negative, you need to refund to your customer even you delivered the apple :( item.weight = -0.8 # negative weight, it doesn't make senseмы должны проверить вес и цену во избежание присвоить им отрицательное число, мы можем написать меньше кода, если мы используем дескриптор в качестве прокси, как это
class Quantity(object): __index = 0 def __init__(self): self.__index = self.__class__.__index self._storage_name = "quantity#{}".format(self.__index) self.__class__.__index += 1 def __set__(self, instance, value): if value > 0: setattr(instance, self._storage_name, value) else: raise ValueError('value should >0') def __get__(self, instance, owner): return getattr(instance, self._storage_name)затем определить класс LineItem, как это:
class LineItem(object): weight = Quantity() price = Quantity() def __init__(self, name, weight, price): self.name = name self.weight = weight self.price = priceи мы можем расширить класс количества, чтобы сделать более общую проверку
я попробовал (с незначительными изменениями как было предложено) код ответа Эндрю Кук. (Я запускаю python 2.7).
код:
#!/usr/bin/env python class Celsius: def __get__(self, instance, owner): return 9 * (instance.fahrenheit + 32) / 5.0 def __set__(self, instance, value): instance.fahrenheit = 32 + 5 * value / 9.0 class Temperature: def __init__(self, initial_f): self.fahrenheit = initial_f celsius = Celsius() if __name__ == "__main__": t = Temperature(212) print(t.celsius) t.celsius = 0 print(t.fahrenheit)результат:
C:\Users\gkuhn\Desktop>python test2.py <__main__.Celsius instance at 0x02E95A80> 212С Python до 3, убедитесь, что вы подкласс из объекта, который сделает дескриптор работать правильно, как get магия не работает для классов старого стиля.
Comments