Понимание дескрипторов 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()
  1. зачем мне нужен класс дескриптора? Пожалуйста, объясните, используя этот пример или тот, который вы считаете лучшим.

  2. что это instance и owner здесь? (in __get__). Поэтому мой вопрос в том, какова цель третьего параметра здесь?

  3. как бы я позвонил / использовал этот пример?

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
  1. сначала выше выглядит, чтобы увидеть, является ли атрибут дескриптором данных в классе экземпляра,
  2. если нет, похоже, что атрибут находится в obj_instance ' s __dict__, тогда
  3. он, наконец, возвращается к Не-данных-дескриптором.

следствием этого порядка поиска является то, что не-дескрипторы данных, такие как функции/методы, могут быть переопределяется экземпляров.

резюме и следующие шаги

мы узнали, что дескрипторы являются объектами с любым из __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()
  1. зачем мне нужен класс дескриптора? Пожалуйста, объясните, используя этот пример или тот, который вы считаете лучшим.

ваш дескриптор гарантирует, что у вас всегда есть 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)
  1. что это instance и owner здесь? (in __get__). Итак, мой вопрос: какова цель третьего параметра здесь?

instance является экземпляром владельца, вызывающего дескриптор. Владелец-это класс, в котором объект дескриптора используется для управления доступом к данным точка. См. описания специальных методов, которые определяют дескрипторы рядом с первым пунктом этого ответа для более описательные имена переменных.

  1. как бы я назвал / использовал этот пример?

вот пример:

>>> 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 магия не работает для классов старого стиля.