Понимание __get__ и __set__ и дескрипторов Python


310

Я пытаюсь понять, что такое дескрипторы 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. Как бы я позвонил / использовал этот пример?

Ответы:


147

Дескриптор - это то, propertyкак реализован тип Python . Дескриптор просто орудие __get__, __set__и т.д. , и затем добавляет к другому классу в его определении (как вы делали выше с классом температуры). Например:

temp=Temperature()
temp.celsius #calls celsius.__get__

Доступ к свойству, которому вы присвоили дескриптор ( celsiusв приведенном выше примере), вызывает соответствующий метод дескриптора.

instancein __get__является экземпляром класса (так что выше, __get__получит temp, в то время ownerкак класс с дескриптором (так будет Temperature).

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

Статью о дескрипторах можно найти здесь .

РЕДАКТИРОВАТЬ: Как jchl указал в комментариях, если вы просто попытаетесь Temperature.celsius, instanceбудет None.


6
Какая разница между selfа instance?
Лемма Призма

2
'instance' может быть экземпляром любого класса, self будет экземпляром того же класса.
TheBeginner

3
@LemmaPrism self- это экземпляр дескриптора, instanceэто экземпляр класса (если он создан), в котором находится дескриптор ( instance.__class__ is owner).
Tcll

Temperature.celsiusдает значение в 0.0соответствии с кодом celsius = Celsius(). Дескриптор Celsius вызывается, поэтому его экземпляр имеет значение init, 0.0присвоенное атрибуту класса Temperature, celsius.
Анхель Салазар

109

Зачем мне нужен класс дескриптора?

Это дает вам дополнительный контроль над тем, как работают атрибуты. Например, если вы привыкли к геттерам и сеттерам в Java, то это способ Python. Одним из преимуществ является то, что он выглядит для пользователей как атрибут (нет изменений в синтаксисе). Таким образом, вы можете начать с обычного атрибута, а затем, когда вам нужно что-то сделать, переключиться на дескриптор.

Атрибут является просто изменяемым значением. Дескриптор позволяет вам выполнить произвольный код при чтении или установке (или удалении) значения. Таким образом, вы можете использовать его для сопоставления атрибута с полем в базе данных, например, своего рода ORM.

Другое использование может быть отказом принять новое значение, вызвав исключение __set__- фактически делая «атрибут» доступным только для чтения.

Что instanceи ownerздесь? (в __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 (например, если цельсий - это свойство, которое является тем же базовым механизмом, но помещает весь источник внутри класса Temperature), но это показывает, что можно сделать ...


2
Преобразования неверны: они должны быть C = 5 (F − 32) / 9, F = 32 + 9C / 5.
Musiphil

1
Убедитесь, что у вас есть один объект температуры. Выполнение следующих действий портит материал. t1 = температура (190) print t1.celsius t1.celsius = 100 print t1.fahrenheit Теперь, когда вы проверяете t.celcius и t.fahrenheit, они тоже модифицируются. t.celcius - 115, а t.fahrenheit - 32. что явно неверно. @Eric
Ишан Бхатт

1
@IshanBhatt: Я думаю, что это из-за ошибки, указанной мусифилом выше. Кроме того, не это не мой ответ
Эрик

69

Я пытаюсь понять, что такое дескрипторы Python и для чего они могут быть полезны.

Дескрипторы - это атрибуты класса (например, свойства или методы) с любым из следующих специальных методов:

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

Чтобы определить это с помощью кода, объект является дескриптором, если набор его атрибутов пересекается с любым из обязательных атрибутов:

def has_descriptor_attrs(obj):
    return set(['__get__', '__set__', '__delete__']).intersection(dir(obj))

def is_descriptor(obj):
    """obj can be instance of descriptor or the descriptor class"""
    return bool(has_descriptor_attrs(obj))

Data Descriptor имеет __set__и / или __delete__. Non-Data-Descriptor не имеет ни ни .
__set____delete__

def has_data_descriptor_attrs(obj):
    return set(['__set__', '__delete__']) & set(dir(obj))

def is_data_descriptor(obj):
    return bool(has_data_descriptor_attrs(obj))

Примеры объектов встроенного дескриптора:

  • classmethod
  • staticmethod
  • property
  • функции в целом

Дескрипторы без данных

Мы можем видеть это classmethodи staticmethodне-дескрипторы:

>>> is_descriptor(classmethod), is_data_descriptor(classmethod)
(True, False)
>>> is_descriptor(staticmethod), is_data_descriptor(staticmethod)
(True, False)

У обоих есть только __get__метод:

>>> has_descriptor_attrs(classmethod), has_descriptor_attrs(staticmethod)
(set(['__get__']), set(['__get__']))

Обратите внимание, что все функции также не-дескрипторы:

>>> def foo(): pass
... 
>>> is_descriptor(foo), is_data_descriptor(foo)
(True, False)

Дескриптор данных, property

Тем не менее, propertyэто дескриптор данных:

>>> is_data_descriptor(property)
True
>>> has_descriptor_attrs(property)
set(['__set__', '__get__', '__delete__'])

Пунктирный порядок поиска

Это важные различия , поскольку они влияют на порядок поиска для точечного поиска.

obj_instance.attribute
  1. Сначала вышеприведенное выглядит, чтобы увидеть, является ли атрибут Data-Descriptor в классе экземпляра,
  2. Если нет, то это выглядит , чтобы увидеть , если этот атрибут в obj_instance«х __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это экземпляр владельца, который вызывает дескриптор. Владелец - это класс, в котором объект дескриптора используется для управления доступом к точке данных. См. Описания специальных методов, которые определяют дескрипторы рядом с первым абзацем этого ответа, для более наглядных имен переменных.

  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декоратор, который использует те же дескрипторы под капотом, но переносит поведение в реализацию класса владельца (опять же, как определено выше):

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

Вывод

Мы рассмотрели атрибуты, которые определяют дескрипторы, разницу между дескрипторами данных и не-данных, встроенные объекты, которые их используют, и конкретные вопросы об использовании.

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


1
Хорошо, я узнал больше всего из этого ответа (конечно, узнал и от других). Вопрос об этом утверждении «Ожидаемый способ, которым большинство опытных программистов на Python достигли этого результата ...». Класс Temeperature, который вы определяете до и после оператора, идентичен. Я скучал по тому, что ты здесь делаешь?
Йоло Во

1
@YoloVoe нет, все верно, я добавил несколько слов в скобках, чтобы подчеркнуть, что это повторение вышеизложенного.
Аарон Холл

1
Это удивительный ответ. Мне нужно будет прочитать его еще несколько раз, но я чувствую, что мое понимание Python только что поднялось на несколько ступеней
Лукас Янг

20

Прежде чем углубляться в детали дескрипторов, может быть важно узнать, как работает поиск атрибутов в Python. Это предполагает, что у класса нет метакласса, и что он использует реализацию по умолчанию __getattribute__(обе могут использоваться для «настройки» поведения).

Лучшая иллюстрация поиска атрибутов (в Python 3.x или для классов нового стиля в Python 2.x) в этом случае - из Понимания метаклассов Python (кодовый журнал ionel) . Изображение используется в :качестве замены для «ненастраиваемого поиска атрибутов».

Это означает поиск атрибута foobarна instanceиз Class:

введите описание изображения здесь

Здесь важны два условия:

  • Если в классе instanceесть запись для имени атрибута, и он имеет __get__и __set__.
  • Если instanceнет нет записи для имени атрибута , но класс имеет один и у него есть __get__.

Вот где дескрипторы входят в это:

  • Дескрипторы данных, которые имеют оба __get__и __set__.
  • Дескрипторы без данных, которые только имеют __get__.

В обоих случаях возвращаемое значение вызывается __get__с использованием экземпляра в качестве первого аргумента и класса в качестве второго аргумента.

Поиск еще более сложен для поиска атрибутов класса (см., Например, поиск атрибутов класса (в вышеупомянутом блоге) ).

Давайте перейдем к вашим конкретным вопросам:

Зачем мне нужен класс дескриптора?

В большинстве случаев вам не нужно писать дескрипторные классы! Однако вы, вероятно, очень обычный конечный пользователь. Например функции. Функции являются дескрипторами, поэтому функции могут использоваться как методы с selfнеявно передаваемым в качестве первого аргумента.

def test_function(self):
    return self

class TestClass(object):
    def test_method(self):
        ...

Если вы посмотрите test_methodна экземпляр, вы получите «связанный метод»:

>>> instance = TestClass()
>>> instance.test_method
<bound method TestClass.test_method of <__main__.TestClass object at ...>>

Точно так же вы могли бы также связать функцию, вызывая ее __get__метод вручную (не очень рекомендуется, только для иллюстративных целей):

>>> test_function.__get__(instance, TestClass)
<bound method test_function of <__main__.TestClass object at ...>>

Вы даже можете назвать этот «самосвязанный метод»:

>>> test_function.__get__(instance, TestClass)()
<__main__.TestClass at ...>

Обратите внимание, что я не предоставил никаких аргументов, и функция вернула экземпляр, который я связал!

Функции являются дескрипторами без данных !

Было бы несколько встроенных примеров дескриптора данных property. Пренебрегая getter, setterи дескриптор (от Descriptor HowTo Guide "Свойства" ):deleterproperty

class Property(object):
    def __init__(self, fget=None, fset=None, fdel=None, doc=None):
        self.fget = fget
        self.fset = fset
        self.fdel = fdel
        if doc is None and fget is not None:
            doc = fget.__doc__
        self.__doc__ = doc

    def __get__(self, obj, objtype=None):
        if obj is None:
            return self
        if self.fget is None:
            raise AttributeError("unreadable attribute")
        return self.fget(obj)

    def __set__(self, obj, value):
        if self.fset is None:
            raise AttributeError("can't set attribute")
        self.fset(obj, value)

    def __delete__(self, obj):
        if self.fdel is None:
            raise AttributeError("can't delete attribute")
        self.fdel(obj)

Поскольку это данные дескриптора он вызывается всякий раз , когда вы смотрите на «имя» propertyи он просто делегирует функцию украшенной @property, @name.setterи @name.deleter(если имеется).

Есть несколько других дескрипторов в стандартной библиотеке, например staticmethod, classmethod.

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

Другим примером будет свойство класса .

Один забавный пример (с использованием __set_name__Python 3.6) также может быть свойством, которое допускает только определенный тип:

class TypedProperty(object):
    __slots__ = ('_name', '_type')
    def __init__(self, typ):
        self._type = typ

    def __get__(self, instance, klass=None):
        if instance is None:
            return self
        return instance.__dict__[self._name]

    def __set__(self, instance, value):
        if not isinstance(value, self._type):
            raise TypeError(f"Expected class {self._type}, got {type(value)}")
        instance.__dict__[self._name] = value

    def __delete__(self, instance):
        del instance.__dict__[self._name]

    def __set_name__(self, klass, name):
        self._name = name

Затем вы можете использовать дескриптор в классе:

class Test(object):
    int_prop = TypedProperty(int)

И немного поиграть с этим:

>>> t = Test()
>>> t.int_prop = 10
>>> t.int_prop
10

>>> t.int_prop = 20.0
TypeError: Expected class <class 'int'>, got <class 'float'>

Или "ленивая собственность"

class LazyProperty(object):
    __slots__ = ('_fget', '_name')
    def __init__(self, fget):
        self._fget = fget

    def __get__(self, instance, klass=None):
        if instance is None:
            return self
        try:
            return instance.__dict__[self._name]
        except KeyError:
            value = self._fget(instance)
            instance.__dict__[self._name] = value
            return value

    def __set_name__(self, klass, name):
        self._name = name

class Test(object):
    @LazyProperty
    def lazy(self):
        print('calculating')
        return 10

>>> t = Test()
>>> t.lazy
calculating
10
>>> t.lazy
10

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

Что instanceи ownerздесь? (в __get__). Какова цель этих параметров?

Это зависит от того, как вы смотрите атрибут. Если вы посмотрите на атрибут в экземпляре, то:

  • второй аргумент - это экземпляр, в котором вы ищите атрибут
  • третий аргумент - это класс экземпляра

Если вы ищите атрибут в классе (при условии, что дескриптор определен в классе):

  • Второй аргумент None
  • третий аргумент - это класс, в котором вы ищите атрибут

Так что в принципе третий аргумент необходим, если вы хотите настроить поведение при поиске на уровне класса (потому что он instanceесть None).

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

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

>>> t1 = Temperature()
>>> t2 = Temperature()

>>> t1.celsius = 20   # setting it on one instance
>>> t2.celsius        # looking it up on another instance
20.0

>>> Temperature.celsius  # looking it up on the class
20.0

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


не уверен, что прозрачный фон графического изображения, действительно страдающего в темном режиме, следует сообщать как ошибку в stackoverflow.
Tshirtman

@ Tshirtman Я думаю, что это проблема самого изображения. Это не полностью прозрачно ... Я взял это из сообщения в блоге и не знаю, как воссоздать это с надлежащим прозрачным фоном.
Жаль, что

9

Зачем мне нужен класс дескриптора?

Вдохновленный Свободным Питоном 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

и мы можем расширить класс количества, чтобы сделать более общую проверку


1
Интересный вариант использования, так как он показывает, как использовать дескриптор для взаимодействия с несколькими экземплярами пользователей. Первоначально я не понимал важный момент: атрибут с дескриптором должен быть создан в пространстве имен класса (например weight = Quantity(), но значения должны быть установлены в пространстве имен экземпляра только с использованием self(например self.weight = 4), в противном случае атрибут будет возвращен к новому значению и дескриптор будет отброшен Nice.!
минут

Я не могу понять одну вещь. Вы определяетеweight = Quantity() как переменную класса и его__get__ и __set__работаете над переменной экземпляра. Как?
Технократ

0

Я попробовал (с незначительными изменениями, как предложено) код из ответа Эндрю Кука. (Я использую 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 magic не работает для классов старого стиля.


1
Дескрипторы работают только с новыми классами стилей. Для Python 2.x это означает получение вашего класса из «объекта», который используется по умолчанию в Python 3.
Ivo van der Wijk

0

Вы увидите https://docs.python.org/3/howto/descriptor.html#properties

class Property(object):
    "Emulate PyProperty_Type() in Objects/descrobject.c"

    def __init__(self, fget=None, fset=None, fdel=None, doc=None):
        self.fget = fget
        self.fset = fset
        self.fdel = fdel
        if doc is None and fget is not None:
            doc = fget.__doc__
        self.__doc__ = doc

    def __get__(self, obj, objtype=None):
        if obj is None:
            return self
        if self.fget is None:
            raise AttributeError("unreadable attribute")
        return self.fget(obj)

    def __set__(self, obj, value):
        if self.fset is None:
            raise AttributeError("can't set attribute")
        self.fset(obj, value)

    def __delete__(self, obj):
        if self.fdel is None:
            raise AttributeError("can't delete attribute")
        self.fdel(obj)

    def getter(self, fget):
        return type(self)(fget, self.fset, self.fdel, self.__doc__)

    def setter(self, fset):
        return type(self)(self.fget, fset, self.fdel, self.__doc__)

    def deleter(self, fdel):
        return type(self)(self.fget, self.fset, fdel, self.__doc__)

1
Это не отвечает на вопрос и не предоставляет полезной информации.
Себастьян Нильсен
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.