Установка значений атрибута дескриптора в Python с использованием имени класса - PullRequest
1 голос
/ 09 ноября 2019

Я пытаюсь понять, как работает дескриптор в Python. У меня есть фрагмент кода ниже.

>>> class D(object):
    def __get__(*_):
        print('get called')
    def __set__(*_):
        print('set called')
>>> 
>>> class Person(object):
    name = D()

>>> 
>>> jane = Person()
>>> jane.name
get called
>>> jane.name = 'Janny'
set called
>>> Person.name
get called
>>> Person.name = 'Jack'
>>> 

При обращении к атрибуту дескриптора name с использованием имени класса, т.е. вызывается метод Person.name get. Однако, когда я пытаюсь установить значение таким же образом, т.е. Person.name = 'Jack' set не вызывается. Здесь я ожидал вызова набора.

Я не могу понять это поведение.

Я использую Python 3.8.

1 Ответ

1 голос
/ 09 ноября 2019

Декораторы определены для типа объекта, например, в вашем примере для объекта jane дескриптор, на который ссылается name, должен быть определен для типа jane, то есть в теле класса Person. Таким образом, в случае класса дескриптор должен быть определен в метаклассе класса (поскольку класс является экземпляром его метакласса), чтобы дескриптор вызывался для данного поиска атрибутов в объекте класса.

В вашем примере jane.name и jane.name = 'Janny' работали как дескриптор, на который ссылается name, определяется в теле класса Person, то есть он существует в Person.__dict__ (который является объектом mappingproxy и являетсятолько для чтения). Затем Person.name нашел атрибут в Person.__dict__ и увидел, что у него есть атрибут __get__, поэтому дескриптор, следовательно, называется __get__ для получения значения. Теперь, когда вы устанавливаете Person.name = 'Jack', он устанавливает атрибут name для ссылки на строковый объект Jack через Person.__setattr__, отбрасывая предыдущую ссылку на дескриптор D. Так что теперь все ссылки на name, например, если вы сделаете jane.name / Person.name, вы получите строку Jack.


Вот пример того, как вы можете достичь желаемого поведения с помощьюприменение дескриптора также и к метаклассу:

In [15]: class FooDec: 
    ...:     def __get__(self, instance, owner=None): 
    ...:         print(f'__get__ called with: instance: {instance}, owner: {owner}') 
    ...:     def __set__(self, instance, value): 
    ...:         print(f'__set__ called with: instance: {instance}, value: {value}')  
    ...:                                                                                                                                                                                                    

In [16]: class Meta(type): 
    ...:     foo = FooDec() 
    ...:                                                                                                                                                                                                    

In [17]: class People(metaclass=Meta): 
    ...:     foo = FooDec() 
    ...:                                                                                                                                                                                                    

In [18]: People.foo                                                                                                                                                                                         
__get__ called with: instance: <class '__main__.People'>, owner: <class '__main__.Meta'>

In [19]: People.foo = 100                                                                                                                                                                                   
__set__ called with: instance: <class '__main__.People'>, value: 100

In [20]: p = People()                                                                                                                                                                                       

In [21]: p.foo                                                                                                                                                                                              
__get__ called with: instance: <__main__.People object at 0x7faa0b2cb1c0>, owner: <class '__main__.People'>

In [22]: p.foo = 1                                                                                                                                                                                          
__set__ called with: instance: <__main__.People object at 0x7faa0b2cb1c0>, value: 1
...