Понимание дескрипторов 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
здесь? (in__get__
). Поэтому мой вопрос в том, какова цель третьего параметра здесь?как бы я позвонил / использовал этот пример?
5 ответов
дескриптор-это то, как Python тип. Дескриптор просто реализует __get__
, __set__
, etc. и затем добавляется к другому классу в его определении (как вы сделали выше с классом температуры). Например:
temp=Temperature()
temp.celsius #calls celsius.__get__
доступ к свойству, которому вы назначили дескриптор (celsius
в приведенном выше примере) вызывает соответствующий метод дескриптора.
instance
на __get__
является экземпляром класса (так выше,__get__
будет получить temp
, а owner
- это класс с дескриптором (так что это будет Temperature
).
вам нужно использовать класс дескриптора для инкапсуляции логики, которая его питает. Таким образом, если дескриптор используется для кэширования какой-либо дорогостоящей операции (например), он может хранить значение на себе, а не на своем классе.
статью о дескрипторах можно найти здесь.
EDIT: как указал jchl в комментариях, если вы просто попробуете Temperature.celsius
, instance
будет None
.
зачем мне нужен класс дескриптора? Пожалуйста, объясните, используя этот пример или тот, который вы считаете лучшим.
это дает вам дополнительный контроль над тем, как атрибуты работы. если вы привыкли к геттерам и сеттерам на java, например, то это способ python сделать это. одним из преимуществ является то, что он выглядит для пользователей так же, как атрибут (нет никаких изменений в синтаксисе). таким образом, вы можете начать с обычного атрибута, а затем, когда вам нужно сделать что-то необычное, переключитесь на дескриптор.
атрибут - это просто изменяемое значение. дескриптор позволяет выполнять произвольный код при чтении или установке (или удалении) значения. таким образом, вы можете представить себе, что используете его для сопоставления атрибута с полем в базе данных, например - своего рода ORM.
другое использование может отказываться принимать новое значение, бросая исключение в __set__
- эффективно делать атрибут "только для чтения".
что такое экземпляр и владелец здесь? (in
__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__
в противном случае ваши дескрипторы игнорируют класс владельца и экземпляры владельца, вместо этого сохраняя состояние в дескрипторе. Вы можете так же легко делиться состоянием во всех экземплярах с помощью простого атрибута класса (если вы всегда устанавливаете его как 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
иowner
здесь? (in__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__
и вы не можете назначить переменную, которая не может быть преобразована в поплавок:
>>> 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
декоратор, который использует те же дескрипторы под капотом, но вносит поведение в реализацию класса owner (опять же, как определено выше):
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 по Buciano Рамальо
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 магия не работает для классов старого стиля.