这周正式开始阅读另一本Python的书籍《流畅的Python》,这周阅读的部分有Python数据模型、序列构成的数组和字典和集合。下面分享我的读书笔记。
首先书中用一个简单的代码展示了如何实现__getitem__和__len__特殊方法。
针对上述代码,我最大的启发就是Python特殊方法真的很有用。通过实现__getitem__和__len__特殊方法,我们直接可以用len()计算卡片的长度,而且这个函数也变成了可迭代的。
给扑克牌排序的函数。
二维向量类的定义
真的好美。
另外__repr__和__str__的区别在于,__str__是在str()函数被使用,或是在用print函数打印一个对象的时候才被调用,并且它返回的字符串对终端用户更友好。
使用列表推导式计算笛卡尔积
使用生成器表达式计算笛卡尔积
用元组用作记录
定义和使用具名元组
切片知识
s[a:b:c]——意味着对s在a和b之间以c为间隔取值。c的值可以为负,负值意味着反向取值。
特殊方法一览
跟运算符无关的特殊方法
字符串/字节序列表示形式
__repr__、__str__、__format__、__bytes__
数值转换
__abs__、__bool__、__complex__、__int__、__float__、__hash__ __index__
集合模拟
__len__、__getitem__、__setitem__、__delitem__、__contains__
迭代枚举
__iter__、__reversed__、__next__
可调用模拟
__call__
上下文管理
__enter__、__exit__
实例创建和销毁
__new__、__init__、__del__
属性管理
__getattr__、__getattribute__、__setattr__、__delattr__、__dir__
属性描述符
__get__、__set__、__delete__
跟类相关的服务
__prepare__、__instancecheck__、__subclasscheck__
跟运算符相关的特殊方法
类别
方法名和对应的
一元运算符
__neg__ -、__pos__ +、__abs__ abs( )
众多比较运算符
__lt__ 、__ge__ >=
算术运算符
__add__ +、__sub__ -、__mul__ *、__truediv__ /、__floordiv__ //、__mod__ %、__divmod__ divmod( )、__pow__ **或pow( )、__round__ round( )
反向算术运算符
__radd__、__rsub__、__rmul__、__rtruediv__、__rfloordiv__、__rmod__、__rdivmod__、__rpow__
增量赋值算术运算符
__iadd__、__isub__、__imul__、__itruediv__、__ifloordiv__、__imod__、__ipow__
对象模型的定义:计算机编程语言中对象的属性。
元对象定义:元对象所指的是那些对建构语言本身来讲很重要的对象,以此为前提,协议也可以看作接口。
容器序列存放的是它们所包含的任意类型对象的引用。扁平序列里存放的是值而不是引用,它是一段连续的内存空间,只能存放诸如字符、字节和数值这种基础类型。
可变序列:list、bytearray、array.array、collections.deque和memoryview。
不可变序列:tuple、str和bytes。
元组不仅仅是不可变的列表,它还可以用于没有字段名的记录。元组其实是对数据的记录:元组中的每个元素都存放了记录中一个字段的数据,外加这个字段的位置。正是这个位置信息给数据赋予了意义。
列表或元组的方法和属性
列表
元组
s.__add__(s2)
●
●
s + s2, 拼接
s.__iadd__(s2)
●
s += s2, 就地拼接
s.append(e)
●
在尾部添加一个新元素
s.clear( )
●
删除所有元素
s.__contains__(e)
●
●
s是否包含e
s.copy( )
●
列表的浅复制
s.count(e)
●
●
e在s中出现的次数
s.__delitem__(p)
●
把位于p的元素删除
s.extend(it)
●
把可迭代对象it追加给s
s.__getitem__(p)
●
●
s[p], 获取位置p的元素
s.__getnewargs__( )
●
在pickle中支持更加优化的序列化
s.index(e)
●
●
在s中找到元素e第一次出现的位置
s.insert(p, e)
●
在位置p之前插入元素e
s.__iter__( )
●
●
获取s的迭代器
s.__len__( )
●
●
len(s), 元素的数量
s.__mul__(n)
●
●
s * n, n个s 的重复拼接
s.__imul__(n)
●
s *= n, 就地重复拼接
s.__rml__(n)
●
●
n * s, 反向拼接 *
s.pop([p])
●
删除最后或者是(可选的)位于p的元素,并返回它的值
s.remove(e)
●
删除s中的第一次出现的e
s.reverse( )
●
就地把s的元素倒序排列
s.__reversed( )
●
返回s的倒叙迭代器
s.__setitem__(p, e)
●
s[p] = e, 把元素e放在位置p,替代已经在那个位置的元素
s.sort([key], [reverse])
●
就地对s中的元素进行排序,可选的参数有键(key)和是否倒叙(reverse)
sorted和list.sort背后的排序算法是Timsort,它是一种自适应算法,会根据原始数据的顺序特点交替使用插入排序和归并排序,以达到最佳效率。
在字典和集合这一章中,书中首先提出散列表是字典类型性能出众的根本原因。一般用户自定义的类型对象都是可散列的,散列值就是它们的id()函数的返回值。
字典推导的应用
散列表其实是一个稀疏数组(总是有空白元素的数组称为稀疏数组)。散列表里的单元叫做表元(bucket)。在dict的散列表当中,每个键值对都占用一个表元,每个表元都有两个部分,一个是对键的引用,另一个是对值的引用。因为所有表元的大小一致,所以可以通过偏移量来读取某个表元。
而如果要把一个对象放入散列表,首先要计算元素键的散列值。Python中可以通过hash( )方法来做这件事情。
有几个方面需要注意:
散列值和相等性
如果两个对象在比较的时候是相等的,那它们的散列值必须相等。为了让散列值能够胜任散列表索引这一角色,它们必须在索引空间中尽量分散开来。因此在最理想的情况下,越是相似但不相等的对象,它们的散列值的差别应该是越大。
散列表算法
为了获取mydict[search_key]背后的值 ,Python首先会调用hash(search_key)来计算search_key的散列值,把这个值最低的几位数字当作偏移量,在散列表里查找表元(具体取几位,得看当前散列表的大小)。
键必须是可散列的
一个可散列的对象必须满足以下要求:
支持hash()函数,并且通过__hash__()方法所得到的散列值是不变的。
支持通过__eq__()方法来检测相等性。
若 a == b为真,则hash(a) == hash(b) 也为真。
字典在内存上的开销巨大
由于字典使用了散列表,而散列表又必须是稀疏的,这导致它在空间上的效率低下。
键查询很快
键的次序取决于添加顺序
往字典里添加新键可能会改变已有键的顺序
不要对字典同时进行迭代和修改。