背景

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

练习要求:

  • 先定义一个温度类,然后定义两个描述符类用于描述摄氏度和华氏度两个属性。
  • 要求两个属性会自动进行转换,也就是说你可以给摄氏度这个属性赋值,然后打印的华氏度属性是自动转化后的结果。
  • 华氏度与摄氏度的转换关系:1 Fahrenheit = 1 Celsius*1.8 + 32

技术分析

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

1. __dict__ 属性

class Test(object):cls_val = 1def __init__(self):self.ins_val = 10t = Test()
print(Test.__dict__)
# {'__module__': '__main__', 'cls_val': 1, '__init__': <function Test.__init__ at 0x000000EBCB65F598>, '__dict__': <attribute '__dict__' of 'Test' objects>, '__weakref__': <attribute '__weakref__' of 'Test' objects>, '__doc__': None}
print(t.__dict__)
# {'ins_val': 10}

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

t.cls_val = 20
print(Test.__dict__)
# {'__module__': '__main__', 'cls_val': 1, '__init__': <function Test.__init__ at 0x000000CB7EB5F598>, '__dict__': <attribute '__dict__' of 'Test' objects>, '__weakref__': <attribute '__weakref__' of 'Test' objects>, '__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__': <function Test.__init__ at 0x000000DAB2BFC048>, '__dict__': <attribute '__dict__' of 'Test' objects>, '__weakref__': <attribute '__weakref__' of 'Test' objects>, '__doc__': None}
print(t.__dict__)
# {'ins_val': 10, 'cls_val': 20}

可见,更改了类Test的属性cls_val的值,由于事先增加了实例tcls_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 object at 0x0000009C9B980198>
# instance:  <__main__.TestDesc object at 0x0000009C9B9801D0>
# owner:  <class '__main__.TestDesc'>

可以看到,实例化类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 = namedef __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 = nameprint("__init__(): name = ", self.name)def __get__(self, instance, owner):print("__get__() ...")return self.namedef __set__(self, instance, value):self.value = valueclass TestDesc(object):_x = Desc('x')def __init__(self, x):self._x = xt = 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 = nameprint("__init__(): name = ", self.name)def __get__(self, instance, owner):print("__get__() ...")return self.nameclass TestDesc(object):_x = Desc('x')def __init__(self, x):self._x = xt = 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 = valuedef __get__(self, instance, owner):return self.valuedef __set__(self, instance, value):self.value = float(value)class Fahrenheit:def __get__(self, instance, owner):return instance.cel * 1.8 + 32def __set__(self, instance, value):instance.cel = (float(value) - 32) / 1.8class 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

相关图文

  • 资料分享:数学建模资料分享 – 图论部分
  • 资料分享:数学建模资料分享 – 神经网络部分
  • 如何利用 C# 实现 K 最邻近算法?
  • 如何利用 C# 实现 K-D Tree 结构?
  • 如何利用 C# + KDTree 实现 K 最邻近算法?
  • 如何利用 C# 对神经网络模型进行抽象?
  • 如何利用 C# 实现神经网络的感知器模型?
  • 如何利用 C# 实现 Delta 学习规则?
  • 如何利用 C# 实现 误差反向传播 学习规则?
  • 如何利用 C# 爬取带 Token 验证的网站数据?
  • 如何利用 C# 向 Access 数据库插入大量数据?
  • 如何利用 C# + Python 破解猫眼电影的反爬虫机制?

技术图文:Python描述符 (descriptor) 详解相关推荐

  1. python描述符详解_Python描述符 (descriptor) 详解

    1.什么是描述符? python描述符是一个"绑定行为"的对象属性,在描述符协议中,它可以通过方法重写属性的访问.这些方法有 __get__(), __set__(), 和__de ...

  2. python中@property以及描述符descriptor详解

    python一直以代码简洁优雅而著称,这篇文章介绍的小技巧,就是如何优雅地对一个类的属性进行赋值和取值.不过不仅仅如此,本文章还为类属性的查找顺序,以及装饰器在类方法的使用打下了基础. 文章目录 待解 ...

  3. javascript描述符descriptor详解

    一.描述符对象是个什么东西? javascript里,有时候不想让用户修改某个对象的属性,则可以把这个对象的属性设置为不可写的,这样用户就不能对该属性进行修改了. 实际操作为: 这样看来,用户修改属性 ...

  4. Python 占位符格式化详解

    Python 占位符格式化详解 占位符,顾名思义就是插在输出里站位的符号.占位符是绝大部分编程语言都存在的语法, 而且大部分都是相通的, 它是一种非常常用的字符串格式化的方式. 原文:https:// ...

  5. python描述符(descriptor)、属性(property)、函数(类)装饰器(decorator )原理实例详解

    2019独角兽企业重金招聘Python工程师标准>>> 1.前言 Python的描述符是接触到Python核心编程中一个比较难以理解的内容,自己在学习的过程中也遇到过很多的疑惑,通过 ...

  6. python 描述器 详解_Python描述器descriptor详解

    前面说了descriptor,这个东西其实和Java的setter,getter有点像.但这个descriptor和上文中我们开始提到的函数方法这些东西有什么关系呢? 所有的函数都可以是descrip ...

  7. python descriptor 详解(全干货)

    目录 descriptor简介 descriptor注意事项 descriptor应用场景 regerences 正文 descriptor简介 在python中,如果一个新式类定义了__get__, ...

  8. Python中self用法详解

    Python中self用法详解 https://blog.csdn.net/CLHugh/article/details/75000104 首页 博客 学院 下载 图文课 论坛 APP 问答 商城 V ...

  9. 菜鸟教程python正则表达式_python 正则表达式详解

    python 正则表达式详解 1. 正则表达式模式 模式描述 ^ 匹配字符串的开头 $ 匹配字符串的末尾. . 匹配任意字符,除了换行符,当re.DOTALL标记被指定时,则可以匹配包括换行符的任意字 ...

最新文章

  1. web前段学习day_01:HTML(学习如何搭建页面结构和内容):文本标签、列表标签、图片标签、超链接、表格、表单表单、分区标签、实体引用
  2. 用SAPI实现Speech Recognition(SR) - 命令控制模式
  3. vs2008 添加头文件路径
  4. 计算机应用基础计算配置教案,[定稿]计算机应用基础教案_Windows_XP版V8.1(全文完整版)...
  5. 电脑上怎么做pdf文件_PDF压缩文件怎么压缩最小?请收好这些PDF压缩方法
  6. 【C++的深度剖析教程20】类型转换函数上
  7. Mysql 启动报错解析:Starting MySQL.. ERROR! The server quit without updating PID file (/usr/local/mysql/dat
  8. c语言下面程序的功能是求圆的周长和面积.请改正程序中带*行中,2012年计算机等级考试二级C语言上机题(5)...
  9. 赚钱的方法地推拉新一定算一个
  10. pppoe拨号中的server name和service name
  11. HttpClient 学习整理【转】
  12. 【UE4】特效之 Particle System 详解(二)—— 特效池
  13. 揭开迷雾,来一顿美味的Capsule盛宴
  14. 记一次菜鸟网络电话面试
  15. H5中 iphoneX适配
  16. 轻松理解LTE网规网优FAQ基本概念
  17. 微信小程序添加音效createInnerAudioContext
  18. 程序员生存定律-六个程序员的故事(2) .
  19. 塑壳断路器用考虑启动电流么_塑壳断路器的选用
  20. OSChina 周三乱弹 ——新技能get 如何机智的关注大胸妹子。

热门文章

  1. leetcode--对称二叉树--python
  2. mongDB的常用操作总结
  3. Python 判断类型
  4. OSS.Core基于Dapper封装(表达式解析+Emit)仓储层的构思及实现
  5. 网络编程 -- RPC实现原理 -- RPC -- 迭代版本V3 -- 远程方法调用 整合 Spring
  6. 最优化:拉格朗日乘子法
  7. C# 获取图片的EXIF 信息
  8. WinForm容器内控件批量效验是否允许为空?设置是否只读?设置是否可用等方法分享...
  9. 如何在JSP页面中获取当前系统时间转
  10. 数学各个研究方向简介