Magic Method
python中有些跟对象本身有关的方法, 以两个下划线开始,两个下划线结束, 一般称为魔法方法(magic method). 比如 obj[key] 的背后就是 __getitem__
方法,为了能求得my_collection[key]
的值,解释器实际上会调用my_collection.__getitem__(key)
。
首先明确一点,特殊方法的存在是为了被 Python 解释器调用的,你自己并不需要调用它们.
特殊几个方法:
__init__() 构造函数
__len__() 返回集合的Count
__getitem__() 根据传入的位置参数,取出集合内的对象
__repr__() 根据对象的属性等返回一个字符串表示
__repr__ 和 __str__ 的区别在于,后者是在 str() 函数被使用,或是在用 print 函数打印一个对象的时候才被调用的,并且它返回的字符串对终端用户更友好。
如果你只想实现这两个特殊方法中的一个,__repr__ 是更好的选择,因为如果一个对象没有 __str__ 函数,而 Python 又需要调用它的时候,解释器会用 __repr__ 作为替代
__add__ 和 __mul__ 分别实现了简单的+和*的运算符重载, 中缀运算符的基本原则就是不改变操作对象,而是产出一个新的值.
__bool__ bool(x) 的背后是调用 x.__bool__() 的结果;如果不存在 __bool__ 方法,那么 bool(x) 会尝试调用 x.__len__()。若返回 0,则 bool 会返回False;否则返回 True。
其余的一些魔法方法可以在Python文档中搜索Data Model查看.
len()为什么不是普通函数, 如果是普通函数据就直接从实例对象中取出该函数或者是对应的属性值就可以了, 但是如果是外在的实现len()就可以把len()适用于自定义函数, 并且具有同样的风格而不是有时候是count()有时候是len()
list like type
容器序列
- list、tuple 和 collections.deque 这些序列能存放不同类型的数据。
扁平序列
- str、bytes、bytearray、memoryview 和 array.array,这类序列只能容纳一种类型。
所以容器类型是存储的不同类型的对象的引用, 扁平类型是直接在一块连续内存中存储的值, 注意不是引用.但是里面只能放字符, 字节, 数值等这种基本类型.
可变序列
- list、bytearray、array.array、collections.deque 和 memoryview。
不可变序列
- tuple、str 和 bytes。
列表推导和生成器表达式
列表推导是构建列表(list)的快捷方式,而生成器表达式则可以用来创建其他任何类型的序列.
>>> symbols = '$¢£¥€¤'
>>> codes = []
>>> for symbol in symbols:
... codes.append(ord(symbol))
...
>>> codes
[36, 162, 163, 165, 8364, 164]
没使用列表推导
>>> symbols = '$¢£¥€¤'
>>> codes = [ord(symbol) for symbol in symbols]
>>> codes
[36, 162, 163, 165, 8364, 164]
使用列表推导, 一般来说不要超过两行
列表推导、生成器表达式,以及同它们很相似的集合(set)推导和字典(dict)推导,在 Python 3 中都有了自己的局部作用域,就像函数似的。表达式内部的变量和赋值只在局部起作用,表达式的上下文里的同名变量还可以被正常引用,局部变量并不会影响到它们。
>>> x = 'ABC'
>>> dummy = [ord(x) for x in x]
>>> x ➊
'ABC'
>>> dummy ➋
[65, 66, 67]
>>>
在python2.x中可能会出现x=C的情况, 即局部变量会覆盖外部变量, 没有自己的作用域, 在Python3中没有这种问题.
>>> symbols = '$¢£¥€¤'
>>> beyond_ascii = [ord(s) for s in symbols if ord(s) > 127]
>>> beyond_ascii
[162, 163, 165, 8364, 164]
>>> beyond_ascii = list(filter(lambda c: c > 127, map(ord, symbols)))
>>> beyond_ascii
[162, 163, 165, 8364, 164]
列表推导和filter和map的对比
列表推导一个很重要的使用场景就是生成笛卡尔集,
self._cards = [Card(rank, suit) for rank in self.ranks
for suit in self.suits]
如上, 这样的代码比较多, 要想改变生成的元组的构成, 只要改变两个for的顺序即可, 当然也是相当于两层循环.
列表推导的作用只有一个:生成列表。如果想生成其他类型的序列,生成器表达式就派上了用场。
>>> colors = ['black', 'white']
>>> sizes = ['S', 'M', 'L']
>>> for tshirt in ('%s %s' % (c, s) for c in colors for s in sizes): ➊
... print(tshirt)
...
black S
black M
black L
white S
white M
white L
和列表推导不同, 生成器表达式是在循环真正执行的时候才会每次生成一个对象出来, 而不会先生成一个列表, 然后往里面填充, 更节省内存.
tuple like
元组其实是对数据的记录:元组中的每个元素都存放了记录中一个字段的数据,外加这个字段的位置。正是这个位置信息给数据赋予了意义。
元组更类似一种数据输入输出的格式, 在对tuple做某些运算的时候可以保持聚合.
元组拆包可以应用到任何可迭代对象上,唯一的硬性要求是,被可迭代对象中的元素数量必须要跟接受这些元素的元组的空档数一致。否则则必须用*表示省略.
一般的拆包的例子如下:
>>> lax_coordinates = (33.9425, -118.408056)
>>> latitude, longitude = lax_coordinates # 元组拆包
>>> latitude
33.9425
>>> longitude
-118.408056
所以函数返回多个值其实也是返回一个tuple, 后面再进行拆包, 如果对不感兴趣的字段用"_"表示.
对于拆包中也可以有多个参数省略的做法, 使用*. 如:
>>> a, *body, c, d = range(5)
>>> a, body, c, d
(0, [1, 2], 3, 4)
>>> *head, b, c, d = range(5)
>>> head, b, c, d
([0, 1], 2, 3, 4)
注意这里的*必须放在一个变量前, 而变量也会变成一个list. 而且这里的拆包和结构还可以嵌套使用, 只要等式右边的结构是同构的.
collections.namedtuple()
相比tuple多了字段名, 但是对于一般的类实例, 也少了很多存储的内容, 本质上是把字段名作为类属性存储, 另外的值存储在tuple中.
创建一个具名元组需要两个参数,一个是类名,另一个是类的各个字段的名字。后者可以是由数个字符串组成的可迭代对象,或者是由空格分隔开的字段名组成的字符串.
>>> City._fields ➊
('name', 'country', 'population', 'coordinates')
>>> LatLong = namedtuple('LatLong', 'lat long')
>>> delhi_data = ('Delhi NCR', 'IN', 21.935, LatLong(28.613889, 77.208889))
>>> delhi = City._make(delhi_data) ➋
>>> delhi._asdict() ➌
OrderedDict([('name', 'Delhi NCR'), ('country', 'IN'), ('population',
21.935), ('coordinates', LatLong(lat=28.613889, long=77.208889))])
>>> for key, value in delhi._asdict().items():
print(key + ':', value)
name: Delhi NCR
country: IN
population: 21.935
coordinates: LatLong(lat=28.613889, long=77.208889)
>>>
❶ _fields 属性是一个包含这个类所有字段名称的元组。
❷ 用 _make() 通过接受一个可迭代对象来生成这个类的一个实例,它的作用跟
City(*delhi_data) 是一样的。
❸ _asdict() 把具名元组以 collections.OrderedDict 的形式返回,我们可以利用它
来把元组里的信息友好地呈现出来。
切片高级
切片和C语言中一样, 都是忽略最后一个元素, 并且使用0开头, 这样的话好处有二:
- 可以根据任何一个下标来进行对集合的分割
- 任意两个下标的差就是相差的元素数
切片可以支持步长, seq.__getitem__(slice(start, stop, step))
, 且步长可以为负.
切片也支持赋值和del, 如下:
>>> l = list(range(10))
>>> l
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
>>> l[2:5] = [20, 30]
>>> l
[0, 1, 20, 30, 5, 6, 7, 8, 9]
>>> del l[5:7]
>>> l
[0, 1, 20, 30, 5, 8, 9]
>>> l[3::2] = [11, 22]
>>> l
[0, 1, 20, 11, 5, 22, 9]
>>> l[2:5] = 100 ➊
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: can only assign an iterable
>>> l[2:5] = [100]
>>> l
[0, 1, 100, 22, 9]
这里要注意, 右侧的对象必须是一个可迭代的对象, 即使只有一个也要变成可迭代的.
对序列使用+和*, 都是不改变原序列, 重新建一个序列, 然后填充.
当序列中具有可变对象的引用时, 其实是一个对象的多个拷贝, 并不是新建的对象拷贝.
当对可变序列进行+=和*=操作时, 如果实现了_iadd_(), 那么就是原地增加, 否则则是寻找_add_(), 会重新构造一个序列然后拷贝过去.
当对不可变序列, 如tuple进行操作时, 就是直接拷贝增加了.
注意
- 不要把可变对象放在元组里面。
- 增量赋值不是一个原子操作。我们刚才也看到了,它虽然抛出了异常,但还是完成了操作。
- 查看 Python 的字节码并不难,而且它对我们了解代码背后的运行机制很有帮助。
序列的排序
对序列的排序有两种方法, list.sort()和sorted(). list.sort()会就地排序, 且返回一个None. 而sorted永远返回一个列表, 即使传入的是不可变对象.
两者都有两个参数:
reverse
如果被设定为 True,被排序的序列里的元素会以降序输出(也就是说把最大值当作最小值来排序)。这个参数的默认值是 False。
key
一个只有一个参数的函数,这个函数会被用在序列里的每一个元素上,所产生的结果将是排序算法依赖的对比关键字。比如说,在对一些字符串排序时,可以用key=str.lower 来实现忽略大小写的排序,或者是用 key=len 进行基于字符串长度的排序。这个参数的默认值是恒等函数(identity function),也就是默认用元素自己的值来排序。
>>> fruits = ['grape', 'raspberry', 'apple', 'banana']
>>> sorted(fruits)
>>> ['apple', 'banana', 'grape', 'raspberry']
>>> fruits
['grape', 'raspberry', 'apple', 'banana']
>>> sorted(fruits, reverse=True)
['raspberry', 'grape', 'banana', 'apple']
>>> sorted(fruits, key=len)
['grape', 'apple', 'banana', 'raspberry']
>>> sorted(fruits, key=len, reverse=True)
['raspberry', 'banana', 'grape', 'apple']
>>> fruits
['grape', 'raspberry', 'apple', 'banana']
>>> fruits.sort()
>>> fruits
['apple', 'banana', 'grape', 'raspberry']
bisect 模块包含两个主要函数,bisect 和 insort,两个函数都利用二分查找算法来在有序序列中查找或插入元素。
bisect负责在一个序列中查找对应的应该插入的index, 后面可以直接插入或者调用bisect.insort.
list的替换类型
array.array, set, deque等.
array.array就是C语言中数组的实现, 并且仅支持几种数值的实现. 内部存储的是字节信息, 可以方便的输入输出到文件, 占用内存和资源也较少.
memoryview
直接映射在内存上的数据结构, 可以使用cast方式改变内存读取解释的类型.
>>> numbers = array.array('h', [-2, -1, 0, 1, 2])
>>> memv = memoryview(numbers) ➊
>>> len(memv)
5
>>> memv[0] ➋
-2
>>> memv_oct = memv.cast('B') ➌
>>> memv_oct.tolist() ➍
[254, 255, 255, 255, 0, 0, 1, 0, 2, 0]
>>> memv_oct[5] = 4 ➎
>>> numbers
array('h', [-2, -1, 1024, 1, 2]) ➏
根据不同的内存解释方法解释内存中的字节, 'h'有符号整数, 'B'无符号整数.
所以把高字节改成4, 则数值从0变到1024.
队列
列表list本质上还是顺序排列的数据结构, 而不是类似链表的形式. 增加和删除都有可能复制数据, 所以速度会受影响.
队列的操作都是原子操作, 所以可以使用在多线程环境中.