Понимание дескрипторов 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 здесь? (в __get__). Так что мой вопрос в том, какова цель из третьего параметра здесь?


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


855   5  

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
  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__

в противном случае ваши дескрипторы игнорируют класс владельца и экземпляры владельца, вместо этого сохраняя состояние в дескрипторе. Вы можете так же легко поделиться состоянием во всех экземплярах с помощью простого атрибута класса (если вы всегда устанавливаете его как плавающее значение для класса и никогда не удаляете его или удобно для пользователей вашего кода):

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 здесь? (в __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__

и вы не можете назначить переменную, которая не может быть преобразована в 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

    Ничего не найдено.