| |
|
开发:
C++知识库
Java知识库
JavaScript
Python
PHP知识库
人工智能
区块链
大数据
移动开发
嵌入式
开发工具
数据结构与算法
开发测试
游戏开发
网络协议
系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程 数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁 |
-> Python知识库 -> 流畅的python笔记(十)序列的修改、散列和切片 -> 正文阅读 |
|
[Python知识库]流畅的python笔记(十)序列的修改、散列和切片 |
目录 前言本章定义表示多维向量的Vector类,其中的元素是浮点数,将支持以下功能:
此外,还将通过__getattr__方法实现属性的动态存取,虽然序列类型通常不会这么做。 一、Vector类:用户定义的序列类型二、Vector类第一版:与Vector2d兼容这里故意不让Vector的构造方法和Vector2d的构造方法兼容,一个用可迭代对象,一个是直接传入各个分量。 ????????为了编写Vector(3,4)和Vector(3,4,5)这样代码,可以让构造函数__init__接受任意个参数(通过*args),但 是所有内置序列类型的做法都是让构造函数接收可迭代对象为参数。如下实例化方式所示: 如果Vector实例的分量超过6个,repr()生成的字符串就会用 ... 省略一部分,因为对象的字符串表示形式都是用于调试的,不能在控制台一次输出几千行内容,使用reprlib模块可以生成长度有限的表示形式。 ? ? ? ? Vector类第一版代码如下:
三、协议和鸭子类型在python中创建功能完善的序列类型无需使用继承,只需实现符合序列协议的方法,协议是指? ? ? ? ? 在面向对象编程中,协议是非正式的接口,只在文档中定义,在代码中不定义。python中的序列协议只需要__len__和__getitem__两个方法,任何类,只要实现了这两个方法,就可以用在任何期待序列的地方,即此类可以归类为序列类型。 ???????? 像这个指派类,因为实现了__len__和__getitem__方法,我们就说它是序列,因为它的行为像序列,因此它就是序列。 ? ? ? ? 这就是所谓的鸭子类型,行为像鸭子,就是鸭子。 ? ? ? ? 协议是非正式的,没有强制力,在具体的使用场景,通常只需要实现一个协议的部分。例如为了支持迭代,只需要实现__getitem__方法,不必实现__len__方法。 四、Vector类第2版:可切片的序列把类序列协议的实现委托给其属性如果对象中有属性是序列,可以把实现类的__len__和__getitem__方法委托给这个序列属性。如下,我们将Vector类的序列协议实现委托给了属性self._components,而该属性本身是序列类型。 ?但上边例子有缺点,即Vector实例切片的结果时一个数组对象,而不是Vector对象。? ? 切片原理__getitem__和slice看如下示例,了解__getitem__和切片的行为:
slice类的属性如下:
注意slice的indices方法,其可以返回一个元组(start, stop, stride),用法是: slice(s, t, st).indices(len) -> (start, stop, stride) 作用是对切片对象进行整顿,即优雅地处理缺失索引和负数索引,以及长度超过目标序列的切片。该方法把start,stop和stride都变成非负数,并且都落在指定长度序列的边界内。如下例子所示: Vector类中能处理切片的__getitem__方法因为上边实现的__getitem__方法中,切片返回的是数组而不是Vector对象,因此需要改进。
五、Vector类第三版:动态存取属性Vector2d中只有两个分量,因此可以用v.x和v.y来分别访问两个分量,但是多维向量类Vector不能这样干了,如果想要用四个变量x、y、z、t来访问向量对象中的前四个分量,可以用特殊方法__getattr__来实现。具体属性查找过程大致如下:
如下图是一个__getattr__简单实现:
如上边__getattr__的实现,相当于增加了几个虚拟属性 ‘xyzt’,但是如果只实现__getattr__而没有实现__setattr__的话,可能会导致跟我们设想不同的行为。如下:
上边现象出现的原因是,只有当对象查找不到某个属性时,才会调用__getattr__方法。标号1处用v.x访问时,x还是虚拟属性,而这里标号2处给v.x赋值时,会创建一个实例属性x出来,不再是虚拟属性,且给该属性赋值为10,而并没有改变原_components数组的值。为了避免这种行为,我们应该实现__setattr__,即设置改变属性时候的行为。 ?为了下一小节讲散列,这里我们需要设置为Vector是只读的。
虽然在类中声明__slots__属性可以防止设置新实例属性,但是不建议这么做,只有在为了节省内存时才应该使用__slots__。 六、Vector第四版:散列和快速等值测试__hash__把各个分量的散列值异或起来,构成整个向量对象的散列值。 这里要用到一个函数,规约函数functools.reduce,其作用是将可迭代对象中前两个元素送入一个二参数函数中计算,然后得到的结果与第三个元素一起再送入二参数函数计算......以此类推。用法为:?
其中func是用来计算的二参数函数,比如加法,乘法等,iter是可迭代对象,initializer是当可迭代对象为空时候返回的初始值。 ? ? ? ? 我们已知operator模块以函数的形式提供了python的全部中缀运算符,因此可以导入operator模块方便代码编写。 ? ? ? ? 下边是用三种方式计算0~5的累计异或值,第一种使用for循环,后两种使用reduce函数。 可以看到使用operator模块的话就不用写匿名函数了,直接调用xor。 ? ? ? ? 因此Vector类中的__hash__方法如下: ?标号4处特地创建了一个生成器表达式。 ? ? ? ? 上例中__hash__函数是一种映射规约计算,即映射过程计算各个分量散列值,规约过程则使用xor运算符聚合所有散列值。
可以把生成器表达式替换成map方法,映射过程更明显: __eq____hash__和__eq__方法需要同时实现。 ? ? ? ? 上边实现的__eq__方法,需要完成复制两个参数,生成两个元组,然后利用tuple的__eq__方法来判断,如果Vector实例有几千个分量的话,这种做法效率就非常低了。可以使用zip函数来重新实现。如下:
还可以用all函数来替换标号2处的for循环,all中是一个生成器表达式,只有生成的元素都是True的时候才返回True,否则返回False: zip函数zip函数用于并行迭代两个或更多的可迭代对象,返回一个zip对象(生成器),可以拆包。
zip有一个奇怪的特性:当一个可迭代对象耗尽后,它不发出警告就停止,解决这个问题可以用itertools.zip_longest函数,其可以使用可选的默认值来填充缺失的值,然后继续产出直到最长的可迭代对象耗尽。 七、Vector类第五版:格式化略。 ? ? ? ? |
|
|
上一篇文章 下一篇文章 查看所有文章 |
|
开发:
C++知识库
Java知识库
JavaScript
Python
PHP知识库
人工智能
区块链
大数据
移动开发
嵌入式
开发工具
数据结构与算法
开发测试
游戏开发
网络协议
系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程 数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁 |
360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 | -2024/11/15 21:06:35- |
|
网站联系: qq:121756557 email:121756557@qq.com IT数码 |