Розуміння __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).

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

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

EDIT: Як зазначав 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(). Дескриптор Цельсія викликається, тому його екземпляр має значення init, 0.0присвоєне атрибуту класу Температура, Цельсій .
Ангел Салазар

109

Навіщо мені потрібен клас дескриптора?

Це дає додатковий контроль над тим, як працюють атрибути. Якщо, наприклад, ви звикли до того, щоб набирати Java та сеттерів на 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

Зараз існують інші, імовірно, кращі способи досягти такого ж ефекту в пітоні (наприклад, якщо цельсій був властивістю, який є тим самим основним механізмом, але розміщує все джерело всередині класу Температура), але це показує, що можна зробити ...


2
Конверсії помилкові: вони повинні бути C = 5 (F-32) / 9, F = 32 + 9C / 5.
musiphil

1
Переконайтеся, що у вас є один об’єкт температури. Виконуючи наступні помилки до речі. t1 = температура (190) друку t1.celsius t1.celsius = 100 друку t1.fahrenheit Тепер, коли ви перевіряєте t.celcius і t.fahrenheit, вони також змінюються. t.celcius - 115, а t.fahrenheit - 32. Це явно неправильно. @Eric
Ішан Бхатт

1
@IshanBhatt: Я думаю, це через помилку, яку вказав musiphil вище. Також не це не моя відповідь
Ерік

69

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

Дескриптори - це атрибути класу (наприклад, властивості або методи) за допомогою будь-якого з наступних спеціальних методів:

  • __get__ (метод дескриптора даних, наприклад, про метод / функцію)
  • __set__ (метод дескриптора даних, наприклад, в екземплярі властивості)
  • __delete__ (метод дескриптора даних)

Ці об'єкти дескриптора можуть використовуватися як атрибути для інших визначень класів об'єктів. (Тобто вони живуть в __dict__об'єкті класу.)

Об'єкти дескриптора можуть використовуватися для програмного управління результатами пунктирного пошуку (наприклад foo.descriptor) у звичайному виразі, призначенні та навіть видаленні.

Функції / методи, пов'язані методи, property, classmethodі staticmethodвсе використовує ці спеціальні методи контролю , як вони доступні через пунктирний пошук.

Дескриптор даних , як property, може дозволити ледачі обчислення атрибутів , заснованих на більш простому стані об'єкта, що дозволяє екземплярів використовувати менше пам'яті , ніж якщо ви попередньо обчислені кожен можливий атрибут.

Інший дескриптор даних member_descriptor, створений компанією __slots__, дозволяє економити пам'ять, дозволяючи класу зберігати дані в змінній структурі даних, схожих на кортеж, а не в більш гнучку, але затратну на багато місця __dict__.

Дескриптори Non-дані, як правило , наприклад, клас, і статичні методи, отримати їх неявні перші аргументи (зазвичай з ім'ям 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працює властивість.

  • del obj_instance.descriptorвикликає
    descriptor.__delete__(self, obj_instance)повернення None
    Ось як deleterпрацює властивість.

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. По-перше, вище виглядає, щоб означити, чи атрибут є Дескриптором даних для класу екземпляра
  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- це примірник власника, який викликає дескриптор. Власник - клас, в якому об'єкт дескриптора використовується для управління доступом до точки даних. Див. Описи спеціальних методів, які визначають дескриптори поруч із першим абзацом цієї відповіді для отримання більш описових імен змінних.

  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__

І ви не можете призначити змінну, яку неможливо перетворити на float:

>>> 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 досягнуть цього результату ...". Клас температури, який ви визначаєте до і після виразу, є однаковим. Хіба я пропустив те, що ти тут отримуєш?
Yolo Voe

1
@YoloVoe ні, це правда, я додав деякі думки в дужках, щоб підкреслити, що це повторення сказаного.
Аарон Холл

1
Це дивовижна відповідь. Мені потрібно прочитати ще кілька разів, але я відчуваю, що моє розуміння Python просто наткнулося на кілька висічок
Lucas Young

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 Я думаю, що це проблема із самим зображенням. Це не зовсім прозоро ... Я взяв його з допису в блозі і не знаю, як відтворити його з належним прозорим фоном. Дуже погано, що на темному тлі це виглядає так дивно :(
MSeifert

9

Навіщо мені потрібен клас дескриптора?

Натхненний Fluent Python від Buciano Ramalho

Уявляючи, що у вас такий клас

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), інакше атрибут буде відновлюватися до нового значення і дескриптор буде відкинутий. Приємно!
хв

Я не в змозі зрозуміти одне. Ви визначаєте 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, переконайтеся, що ви підклас з об'єкта, що змусить дескриптор працювати правильно, так як магія отримання не працює для класів старого стилю.


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.