2

我试图了解描述符在 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。

4

1 回答 1

2

装饰器是在对象的类型中定义的,例如在您的 object 示例中jane,所引用的描述符name必须在janeclass 的主体上定义为 ie 的类型Person。因此,在类的情况下,需要在类的元类中定义描述符(因为类是其元类的实例),以便为类对象上的给定属性查找调用描述符。

在您的示例中,jane.name并且jane.name = 'Janny'作为所引用的描述符name在类的主体中定义,Person即它存在于Person.__dict__(这是一个mappingproxy对象并且是只读的)上。接下来,Person.name在 上找到该属性Person.__dict__并看到它具有该__get__属性,因此是一个描述符,因此称为__get__检索值。现在,当您设置 时Person.name = 'Jack',它会将属性设置为通过name引用字符串对象,删除之前对描述符的引用。所以现在所有对eg 的引用,如果你做/ ,你会得到字符串。JackPerson.__setattr__Dnamejane.namePerson.nameJack


以下是如何通过在元类中应用描述符来实现所需行为的示例:

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
于 2019-11-09T14:03:36.220 回答