python描述器

基本概念

  1. 实例(instance)的属性查找

    对于一个类的实例a,当访问a.x属性时,查找顺序是:首先查看x是否是实例a的属性(a.dict[‘x’])–>查找实例a对应的类的属性(type(a).dict[‘x’])–>继续查找类继承的基类的属性除了(metaclasses)

  2. 描述器协议
    用到3个魔术方法: get()、set()、delete()
    方法使用格式:
    obj.get(self, instance, owner)
    obj.set(self, instance, value)
    obj.delete(self, instance)
    self: 指当前类的实例本身
    instance: 指owner的实例
    owner: 指当前实例作为属性的所属类
    当任何一个类实现了这三个魔术放发的其中一个就叫做描述器
    典型的描述器分类:
    a) data-descriptor : both get and set 都被定义
    当一个entry被定义为描述器,此时对于a.m ==a.get(m)//a.m=dd == a.set(dd).

    b) non-data-descriptor : only __get__被定义
    此时以__get__函数是最低优先级

  3. 例子
    data_descriptor

class A:
    def __init__(self):
        self.a1='a1'
        self.data=None
        print("2--A init")
    def __set__(self,instance,value):
        print('3--set',self,'-',instance,'-',value)
        self.data=value

    def __get__(self,instance,owner):
        print('4--get',self,'-',instance,'-',owner)
        return self.data
class B:
    x=A()                    #将X作为一个描述器,此时x属于CLASS的属性
    y='b.b'
    def __init__(self):
        print('1--B')
        self.x='b.x'      #此时将调用__set__函数,x属于CLASS 的属性,不属于实例的属性
        self.y='b.y'
        self.z='b.z'

m=B()
print(m.y)
print(m.x)#此时将调用__get__函数
print(m.__dict__)

output:based on python 3.7
2--A init
1--B
3--set <__main__.A object at 0x0000025634C8DDA0> - <__main__.B object at 0x0000025634C7E5F8> - b.x
b.y
4--get <__main__.A object at 0x0000025634C8DDA0> - <__main__.B object at 0x0000025634C7E5F8> - <class '__main__.B'>
b.x
{'y': 'b.y', 'z': 'b.z'}

non-descriptor

class A:
    def __init__(self):
        self.a1='a1'
        self.data=None
        print("2--A init")
    """
    def __set__(self,instance,value):
        print('3--set',self,'-',instance,'-',value)
        self.data=value
    """

    def __get__(self,instance,owner):
        print('4--get',self,'-',instance,'-',owner)
        return self.data
class B:
    x=A()     #将X作为一个描述器,此时x属于CLASS的属性
    y='b.b'
    #x='b.m'
    def __init__(self):
        print('1--B')
        self.x='b.x'      #此时x属于实例的属性---因为此时描述器是个non-data,描述器的优先级最低
        self.y='b.y'
        self.z='b.z'

m=B()
print(m.y)
print(m.x)       #打印 b.x 按照正常实例属性来搜索,如果找不到则调用__get__
print(m.__dict__)

output:
2--A init
1--B
b.y
b.x
{'x': 'b.x', 'y': 'b.y', 'z': 'b.z'}
class A:
    def __init__(self):
        self.a1='a1'
        self.data=None
        print("2--A init")
    """
    def __set__(self,instance,value):
        print('3--set',self,'-',instance,'-',value)
        self.data=value
    """

    def __get__(self,instance,owner):
        print('4--get',self,'-',instance,'-',owner)
        return self.data

class B():
    x=A()
    y='b.b'
    #x='b.m'
    def __init__(self):
        print('1--B')
        #self.x='b.x'
        self.y='b.y'
        self.z='b.z'

m=B()
print(m.y)
print(m.x)
print(m.__dict__)

output:
2--A init
1--B
b.y
4--get <__main__.A object at 0x000002218019ED30> - <__main__.B object at 0x000002218018E588> - <class '__main__.B'>
None
{'y': 'b.y', 'z': 'b.z'}
  1. 使用property 创建的是data-descriptor 描述器
  • [property 用法1 ]
    通过@property装饰器来构建描述器,即使用@property 将函数XXXX设置为__get__,通过XXXX.setter设置__set__
    a) 函数名字和需要设置为描述器的实例属性相同,此时属性名字需要使用私有,即添加_/__
    b) 描述器的函数也变为属性,此时当描述器方法的名字和需要使用描述器的属性名字一样时(即使用self.score)运行程序会出现无限循环造成栈溢出。
    通过这种方式,对于python的解释器看到的其实两个属性:self._score 和self.score,但是当你调用self._score时,其实是在直接调用实例的属性(_score是实例属性,不是描述器),当调用a.score(score是描述器方法)时,其实调用的是函数。
    通过这种封装,使用者需要记住的是score (因为在score 函数中是对_score 的操作)
class A(object):
    def __init__(self,name,score):
        self.name=name
        self._score=score
    @property
    def score(self):                                   #相当于为__get__
        print("getting score here")
        return self._score
    
    @score.setter
    def score(self,value):                       #相当于__set__
        print("setting score here")
        self._score =value
    #score=property(getscore,setscore)
    
b=100
a=A('smith',100)
print(a.name)
print(a.score)  #调用score描述器 函数
a.score=90  #调用score 描述器函数
print(a._score)#调用实例的属性,因为_score不是描述器
print(a.score)#调用score描述器函数
output:

smith
getting score here
100
setting score here
90
getting score here
90
  • [ property 用法2]

    property(fget=None, fset=None, fdel=None, doc=None) -> property attribute
    a)此时结果和1相同,只是写法不一样。

class A(object):
    def __init__(self,name,score):
        self.name=name
        self._score=score
    #@property
    def getscore(self):
        print("getting score here")
        return self._score
    
    #@score.setter
    def setscore(self,value):
        print("setting score here")
        self._score =value
    score=property(getscore,setscore)
    
b=100
a=A('smith',100)
print(a.name)
print(a.score)
#print(a._score)
#print(a.__dict__)
#a.name='bob'
a.score=90
print(a._score)
print(a.score)

output:
smith
getting score here
100
setting score here
90
getting score here
90
  1. 当使用property创建data-descriptor时,只有__get__被定义(即对于1–@property,对于2.x=property(get)),此时对于描述器属性是只读的,不能改变值。可以使用魔术方式构建同样的只读的描述器(定义__get__和__set__,但是在__set__中不修改值,使用raise AttributeError(“unreadable attribute”)异常处理方法)
  2. 当然你可以使用@property将某个类内的方法转化为属性(当访问此属性时是通过某个公式或者逻辑计算而得)
import math
class Power:
    def __init__(self,r=0):
        self.r=r
    @property
    def r_power(self):
        return self.r**2
    @property
    def r_sqrt(self):
        return self.r**0.5
d=Power(4)
print(d.__dict__)
print(d.r_power)                   #此时两个类内函数r_power/r_sqrt 变成了属性,不是函数或者方法,调用的时候不能用 d.r_power()
print(d.r_sqrt)
print(d.__dict__)
print(Power.__dict__)

output:
{‘r’: 4}
16
2.0
{‘r’: 4}
{‘module’: ‘main’, ‘init’: <function Power.init at 0x00000213104ABE18>, ‘r_power’: <property object at 0x00000213104575E8>, ‘r_sqrt’: <property object at 0x00000213104A1958>, ‘dict’: <attribute ‘dict’ of ‘Power’ objects>, ‘weakref’: <attribute ‘weakref’ of ‘Power’ objects>, ‘doc’: None}
以上都可以参考python doc

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值