python 描述符参考文档_技术图文:什么是Python的描述符?

背景

今天在B站上学习“零基础入门学习Python”这门课程的第46讲“魔法方法:描述符”,这也是我们组织的 Python基础刻意练习活动 的学习任务,其中有这样的一个题目。

练习要求:先定义一个温度类,然后定义两个描述符类用于描述摄氏度和华氏度两个属性。

要求两个属性会自动进行转换,也就是说你可以给摄氏度这个属性赋值,然后打印的华氏度属性是自动转化后的结果。

华氏度与摄氏度的转换关系:1 Fahrenheit = 1 Celsius*1.8 + 32

技术分析

为了解决这个问题,我们首先回顾__dict__属性,以及__get__,__set__,__delete__魔法方法,然后总结描述符这个 Python 语言特有的语法结构,最后写代码完成要求的任务。

1. __dict__ 属性class Test(object):

cls_val = 1

def __init__(self):

self.ins_val = 10

t = Test()

print(Test.__dict__)

# {'__module__': '__main__', 'cls_val': 1, '__init__': , '__dict__': , '__weakref__': , '__doc__': None}

print(t.__dict__)

# {'ins_val': 10}

根据 Python 的语法结构,t为实例对象,Test为类对象。其对应的属性ins_val和cls_val称为实例属性和类属性。实例t的属性并不包含cls_val,cls_val是属于类Test的。t.cls_val = 20

print(Test.__dict__)

# {'__module__': '__main__', 'cls_val': 1, '__init__': , '__dict__': , '__weakref__': , '__doc__': None}

print(t.__dict__)

# {'ins_val': 10, 'cls_val': 20}

可见,更改实例t的属性cls_val,只是新增了该属性,并不影响类Test的属性cls_val。Test.cls_val = 30

print(Test.__dict__)

# {'__module__': '__main__', 'cls_val': 30, '__init__': , '__dict__': , '__weakref__': , '__doc__': None}

print(t.__dict__)

# {'ins_val': 10, 'cls_val': 20}

可见,更改了类Test的属性cls_val的值,由于事先增加了实例t的cls_val属性,因此不会改变实例的cls_val值。

2. __get__(),__set__(),__delete__() 魔法方法get(self, instance, owner)

set(self, instance, value)

del(self, instance)class Desc(object):

def __get__(self, instance, owner):

print("__get__...")

print("self:", self)

print("instance: ", instance)

print("owner: ", owner)

def __set__(self, instance, value):

print('__set__...')

print("self:", self)

print("instance:", instance)

print("value:", value)

class TestDesc(object):

x = Desc()

t = TestDesc()

t.x

# __get__...

# self: <__main__.desc>

# instance:  <__main__.testdesc>

# owner:  

可以看到,实例化类TestDesc后,调用对象t访问其属性x,会自动调用类Desc的__get__方法,由输出信息可以看出:self: Desc的实例对象,其实就是TestDesc的属性x

instance: TestDesc的实例对象,其实就是t

owner: 即谁拥有这些东西,当然是 TestDesc这个类,它是最高统治者,其他的一些都是包含在它的内部或者由它生出来的

3. 描述符的定义

某个类,只要是内部定义了方法__get__,__set__,__delete__ 中的一个或多个,就可以称为描述符。Desc类就是一个描述符(描述符是一个类)。问题1. 为什么访问t.x的时候,会直接去调用描述符的get()方法呢?

t为实例对象,访问t.x时,根据常规顺序。

首先,访问Owner的__getattribute__()方法(其实就是 TestDesc.__getattribute__()),访问实例属性,发现没有,然后去访问父类!

其次,判断属性x为一个描述符,此时,它就会做一些变动了,将TestDesc.x转化为TestDesc.__dict__['x'].__get__(None, TestDesc)来访问。

最后,进入类Desc的__get__()方法,进行相应的操作。问题2. 从上面代码我们看到了,描述符的对象x其实是类TestDesc  的类属性,那么可不可以把它变成实例属性呢?class Desc(object):

def __init__(self, name):

self.name = name

def __get__(self, instance, owner):

print("__get__...")

print('name = ', self.name)

class TestDesc(object):

x = Desc('x')

def __init__(self):

self.y = Desc('y')

t = TestDesc()

t.x

t.y

# __get__...

# name =  x

咦,为啥没打印 t.y 的信息呢?

因为调用 t.y 时刻,首先会去调用TestDesc(即Owner)的 __getattribute__() 方法,该方法将 t.y 转化为TestDesc.__dict__['y'].__get__(t, TestDesc),但是呢,实际上 TestDesc并没有y这个属性,y是属于实例对象的,所以,只能忽略了。问题3. 如果 类属性的描述符对象 和 实例属性描述符的对象 同名时,咋整?class Desc(object):

def __init__(self, name):

self.name = name

print("__init__(): name = ", self.name)

def __get__(self, instance, owner):

print("__get__() ...")

return self.name

def __set__(self, instance, value):

self.value = value

class TestDesc(object):

_x = Desc('x')

def __init__(self, x):

self._x = x

t = TestDesc(10)

t._x

# __init__(): name =  x

# __get__() ...

不对啊,按照惯例,t._x 会去调用 __getattribute__() 方法,然后找到了 实例t的 _x 属性就结束了,为啥还去调用了描述符的 __get__()方法呢?

这就牵扯到了一个查找顺序问题:当 Python 解释器发现实例对象的字典中,有与描述符同名的属性时,描述符优先,会覆盖掉实例属性。

我们再将代码改进一下, 删除 __set__() 方法试试看会发生什么情况?class Desc(object):

def __init__(self, name):

self.name = name

print("__init__(): name = ", self.name)

def __get__(self, instance, owner):

print("__get__() ...")

return self.name

class TestDesc(object):

_x = Desc('x')

def __init__(self, x):

self._x = x

t = TestDesc(10)

print(t._x)

# __init__(): name =  x

# 10

可见,一个类,如果只定义了 __get__() 方法,而没有定义 __set__(), __delete__()方法,则认为是非数据描述符;反之,则成为数据描述符。非数据描述符,优先级低于实例属性。问题4. 天天提属性查询优先级,就不能总结一下吗?

① __getattribute__(), 无条件调用

② 数据描述符

③ 实例对象的字典

④ 类的字典

⑤ 非数据描述符

⑥ 父类的字典

⑦ __getattr__()方法

代码实现class Celsius:

def __init__(self, value=26.6):

self.value = value

def __get__(self, instance, owner):

return self.value

def __set__(self, instance, value):

self.value = float(value)

class Fahrenheit:

def __get__(self, instance, owner):

return instance.cel * 1.8 + 32

def __set__(self, instance, value):

instance.cel = (float(value) - 32) / 1.8

class Temperature:

cel = Celsius()

fah = Fahrenheit()

temp = Temperature()

print(temp.cel)  # 26.6

print(temp.fah)  # 79.88

temp.cel = 30

print(temp.cel)  # 30

print(temp.fah)  # 86.0

temp.fah = 79.88

print(temp.cel)  # 26.599999999999998

print(temp.fah)  # 79.88

总结

通过以上的介绍我们了解了 Python 中描述符的定义,以及属性调用的优先级。由于Python魔法方法非常复杂需要下很大的功夫才能把这块搞明白。今天就到这里吧,See you!

参考文献https://www.runoob.com/python3/python3-tutorial.html

https://www.bilibili.com/video/av4050443

http://c.biancheng.net/view/2371.html

https://www.cnblogs.com/seablog/p/7173107.html

https://www.cnblogs.com/Jimmy1988/p/6808237.html

相关图文:

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值