Python_描述器

作者: 青蛙兄 分类: 高阶 发布时间: 2019-11-10 15:33

描述器

class A:  
    def __init__(self):  
        self.a1 = 'a1'  
        print('A.init')  
  
  
class B:  
    x = A()  
  
    def __init__(self):  
        print('B.init')  
  
  
print('-' * 20)  
print(B.x.a1)  
  
print('=' * 20)  
b = B()  
print(b.x.a1) 

[v_blue]运行结果:[/v_blue]

A.init  
--------------------  
a1  
====================  
B.init  
a1  

[v_act]可以看出执行的先后顺序吧?
类加载的时候,类变量需要先生成,而类B的x属性是类A的实例,所以类A先初始化,所以先打印A,init
然后执行打印B.x.a1
然后实例化并初始化B的实例b
打印b.x.a1,会查找类属性b.x,指向A的实例,所以返回A实例的属性a1的值
看懂执行流程了,再看下面的程序,对类A做一些改造
如果在类A中实现__get__方法,看看变化[/v_act]

class A:  
    def __init__(self):  
        self.a1 = 'a1'  
        print('A.init')  
  
    def __get__(self, instance, owner):  
        print('A.__get__{}{}{}'.format(self, instance, owner))  
  
  
class B:  
    x = A()  
  
    def __init__(self):  
        print('B.init')  
  
  
print('-' * 20)  
print(B.x)  
# print(B.x.a1) #AttributeError: 'NoneType' object has no attribute 'a1'  
  
  
print('=' * 20)  
b = B()  
print(b.x)  
# print(b.x.a1) #AttributeError: 'NoneType' object has no attribute 'a1' 

[v_blue]运行结果:[/v_blue]

A.init  
--------------------  
A.__get__<__main__.A object at 0x000002ECFF818780>None<class '__main__.B'>  
None  
====================  
B.init  
A.__get__<__main__.A object at 0x000002ECFF818780><__main__.B object at 0x000002ECFF818F98><class '__main__.B'>  
None  

[v_act]因为定义了__get__方法,类A就是一个描述器,对类B或者类B的实例的x属性读取,成为对类A实例的访问,就会调用__get__方法
如何解决上例中访问报错的问题,问题应该来自__get__方法
通过return self#解决返回None的问题
从运行结果可以看出,只有类属性是类的实例才可以
描述器定义
Python中,一个类实现了__get__、__set__、__delete__三个方法中的任何一个方法,就是描述器
如果实现了__get__,就是非数据描述符 non-data descriptor
同时实现了__get__、__set__就是数据描述符data descriptor
如果一个类的类属性设置为描述器,那么它被称之为owner属主[/v_act]

继续修改上面示例的代码,为类A增加__set__方法

class A:  
    def __init__(self):  
        self.a1 = 'a1'  
        print('A.init')  
  
    def __get__(self, instance, owner):  
        print('A.__get__{}{}{}'.format(self, instance, owner))  
        return self  
  
    def __set__(self, instance, value):  
        print('A.__set__{}{}{}'.format(self, instance, value))  
        self.data = value  
  
  
class B:  
    x = A()  
  
    def __init__(self):  
        print('B.init')  
        self.x = 'b.x' #增加实例属性x  
  
  
print('-' * 20)  
print(B.x)  
print(B.x.a1)  
  
  
print('=' * 20)  
b = B()  
print(b.x)  
print(b.x.a1) #返回a1  

[v_blue]运行结果:[/v_blue]

A.init  
------------------------------------------------------------  
A.__get__<__main__.A object at 0x0000016CDCFFD5F8>None<class '__main__.B'>  
<__main__.A object at 0x0000016CDCFFD5F8>  
A.__get__<__main__.A object at 0x0000016CDCFFD5F8>None<class '__main__.B'>  
a1  
============================================================  
B.init  
A.__set__<__main__.A object at 0x0000016CDCFFD5F8><__main__.B object at 0x0000016CDCFFD860>b.x  
A.__get__<__main__.A object at 0x0000016CDCFFD5F8><__main__.B object at 0x0000016CDCFFD860><class '__main__.B'>  
<__main__.A object at 0x0000016CDCFFD5F8>  
A.__get__<__main__.A object at 0x0000016CDCFFD5F8><__main__.B object at 0x0000016CDCFFD860><class '__main__.B'>  
a1 

返回变成了a1,访问到了描述器的数据
属性查找顺序
官网和一些书籍是这样说的:
实例的__dict__ 优先于非数据描述器
数据描述器 优先于实例的 __dict__
__delete__方法有同样的效果,有了这个方法,就是数据描述器
尝试着增加下面的代码,看看字典的变化:
b.x = 500
B.x = 600 慎用,尽量必须使用类来操作类属性的方式
b.x = 500,这是调用数据描述器的__set__方法,或调用非数据描述器的实例覆盖

2条评论
  • 匿名

    2019-11-11 下午4:02

    怎么没有__delete__的方法示例

    1. 青蛙兄

      2019-11-11 下午4:03

      一般很少用到__delete__方法,所以就没有写

发表评论

电子邮件地址不会被公开。