Python描述器

描述器Descriptors

描述器的表现

  • 用到三个方法 : __ get__(),__ set__(), __ delete__()

  • 方法签名如下:
    object.get(self,instance,owner)
    object.set(self,instance,value)
    object.delete(self,instance)

  • self指代当前实例,调用者

  • instance 是owner的实例

  • owner 是属性的所属的类

## 下面执行流程
class A:
    def __init__(self):
        self.a1 = "a1"
        print("A.init")
        
class B:
    x = A()
    def __init__(self):
        print("B.init")
        
print(B.x.a1)

b = B()
print(b.x.a1)

out:#A.init
#a1
#B.init
#a1
  • 可以看出类加载的时候,类变量需要先形成,而类B的x属性是类A的实例,所以类A先初始化,所以打印A.init
    然后至执行到打印B.x.a1.
  • 然后实例化并初始化B的实例b.
  • 打印b.x.a1,会查找类属性b.x,指向A的实例,所以返回A实例的属性a1的值
    接下来我们在类A中实现__get__方法.看看变化
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
        
class B:
    x = A()
    def __init__(self):
        print("B.init")
        
b = B()

print(b.x)
#A.init
#B.init
#A.__get__<__main__.A object at 0x0000020E86371748>,<__main__.B object at 0x0000020E86371780>,<class '__main__.B'>
#<__main__.A object at 0x0000020E86371748>

类访问

  • 因为定义了__ get__方法,类A就是一个描述器,对类B或者类B的实例的x属性读取,成为对类A的实例的访问就会调用__ get__方法

  • 总结:
    self对应是都A的实例
    owner对应都是B的类
    instance (None表示不是B类的实例,对应调用B.x,
    <main.B object at 0x0000000000B84F28>表示是B的实例,对应调用B().x)

    使用返回值解决。返回self,就是A的实例,该实例有a1属性,返回正常。

那么类B的实例属性也可以这样吗?

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 # 解决返回None的问题
class B:
    # x = A()
    def __init__(self):
        print('B.init')
        self.b = A() # 实例属性也指向一个A的实例

print('-'*20)
# print(B.x)
print('='*20)
b = B()
print(b.b)
print(b.b.a1)
#====================
#B.init
#A.init
#<__main__.A object at 0x00000132E7949320>
#a1

从运行结果看,只有类属性类的实例才能触发协议

描述器定义

Python中,一个类实现了__ get__,__set__,__delete__三个方法中的任何一个方法,就是描述器.实现这三个方法中的某些方法,就支持了描述器协议

  • 仅实现了__get__,就是非数据描述符 non-data descriptor
  • 实现了__get__、__set__就是数据描述符 data descriptor

如果一个类的类属性设置为描述器实例,那么它被称为owner属主

属性的访问顺序

为上例中的类B增加实例属性x

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
class B:
    x = A()
    def __init__(self):
        print('B.init')
        self.x = "b.x"

print('-'*20)
print(B.x)
print(B.x.a1)
print('='*20)
b = B()
print(b.x)
print(b.x.a1) # AttributeError: 'str' object has no attribute 'a1'

类A只实现了__get__()方法,b.x访问不到描述器,而是访问实例的属性
继续修改代码,为类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"

print('-'*20)
print(B.x)
print(B.x.a1)
print('='*20)
b = B()
print(b.x)
print(b.x.a1) 

所有的b.x就会访问描述器的__get__()方法,代码中返回的self就是描述器实例,它的实例字典中就保存着a1和data属性,可以打印b.x.__dict__就可以看到这些属性。

属性查找顺序

  • 实例的__dict__优先于非数据描述器
  • 数据描述器优先于实例的__dict__
  • __delete__方法有同样的效果,有了这个方法,也是数据描述器。

尝试着增加下面的2行代码,看看字典的变化
b.x = 500
B.x = 600

  • b.x = 500 这是调用数据描述器的__set__方法,或调用非数据描述器的实例覆盖。
  • B.x = 600,赋值即定义,这是覆盖类属性。把描述器给替换了。

python中的描述器

描述器在Python中应用非常广泛

  • Python的方法(包括staticmethod()和classmethod())都实现为非数据描述器。因此,实例可以重新定义和覆盖方法。这允许单个实例获取与同一类的其他实例不同的行为因为在get方法中,instance可以附加其他行为。描述器多用于对属性功能的增强
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值