这章主要构建了一个教学 Vector 类,对 Python 的数据模型做了更深入的探讨。每一节的内容都比较相关,因此我不再按章节写笔记,而是总的写在一起。
Slicing
def __getitem__(self, index):
cls = type(self)
if isinstance(index, slice):
return cls(self._components[index])
elif isinstance(index, numbers.Integral):
return self._components[index]
else:
msg = '{cls.__name__} indices must be integers'
raise TypeError(msg.format(cls=cls))
调用 v1[0]
时传入的是整数,调用 v1[0:3]
这种传入的是 slice
对象。方括号中的 0:3
可以说是一种 slice literal。
S.indices(len) -> (start, stop, stride)
这个函数非常实用,它可以把 negative index 转成正的,方便你做一些操作:
>>> slice(None, 10, 2).indices(5)
(0, 5, 2)
>>> slice(-3, None, None).indices(5)
(2, 5, 1)
Dynamic Attribute Access
Python 的 instance attribute 存在类的 __dict__
中,class attributes 存在类的 __class__
中。当你访问某个 attributes 时,Python 会先从这两个地方找变量,如果没找到会调用 __getitem__
函数。
所以要实现 Dynamic Attribute Access,你要实现 __getitem__
函数,同时最好实现配套的 __setitem__
函数。
Hashing
如果你想把一个类实例作为 set
dict
的 key,你需要让他不可变
,同时保证它的 hash()
值在整个生命周期保持不变。
一般的实现方式是,将类中的 attributes 先 hash 再异或,对于多个 attributes 的情况,map reduce 很适合这种场景:
def __hash__(self):
hashes = map(hash, self._components)
return functools.reduce(operator.xor, hashes, 0)
Python 3 vs Python 2
Python 3 把很多内置函数从生成一个 list,变成返回一个 iterator,比如 map
zip
等。这对内存和性能都有很大帮助。
The Awesome zip
zip
内置函数可以把多个 iterable 像拉链一样重组:
>>> list(zip(range(3), 'ABC'))
[(0, 'A'), (1, 'B'), (2, 'C')]
但是它在最短的 iterable 消耗完就停止了。你可以用 itertools.zip_longest
提供默认值,使最长的 iterable 也可以被 zip:
>>> list(zip(range(3), 'ABCD'))
[(0, 'A'), (1, 'B'), (2, 'C')]
>>> from itertools import zip_longest
>>> list(zip_longest(range(3), 'ABCD', fillvalue=-1))
[(0, 'A'), (1, 'B'), (2, 'C'), (-1, 'D')]
The Search for a Pythonic Sum
Future Reading 里面有一个值得一看的故事,讲述大家在讨论 Pythonic way to sum n-th list element? 的故事。当时 Python 并没有 sum
内置函数,于是一些有 OOP 经验的人使用了 functools.reduce
和 lambda
来写:
>>> my_list = [[1, 2, 3], [40, 50, 60], [9, 8, 7]]
>>> import functools
>>> functools.reduce(lambda a, b: a+b, [sub[1] for sub in my_list])
60
但是这实在是太丑了,即有 lambda
又有 reduce
,还有 list comprehension。于是大家写出了优化版本:
>>> functools.reduce(lambda a, b: a + b[1], my_list, 0)
60
>>> functools.reduce(operator.add, [sub[1] for sub in my_list], 0)
60
但是 reduce
会使得求和 (sum) 的意图不明。有人觉得这样写还不如直接:
>>> t = 0
>>> for sub in my_list:
... total += sub[1]
>>> t
60
对此有两段评论说得好:
I like Evan Simpson’s code but I also like David Eppstein’s comment on it:If you want the sum of a list of items, you should write it in a way that looks like “the sum of a list of items”, not in a way that looks like “loop over these items, maintain another variable t, perform a sequence of additions”. Why do we have high level languages if not to express our intentions at a higher level and let the language worry about what low-level operations are needed to implement it?Then Alex Martelli comes back to suggest:“The sum” is so frequently needed that I wouldn’t mind at all if Python singled it out as a built-in. But “reduce(operator.add, …” just isn’t a great way to express it, in my opinion (and yet as an old APL’er, and FP-liker, I should like it—but I don’t).
最终导致 3 个月后 Python 添加了内置的 sum
函数。于是可以:
>>> sum([sub[1] for sub in my_list])
60
这也说明了为何 Python 3 把 map
reduce
放到 functools
里面而不放在内置函数中。它们缺乏表达能力。
Protocol v.s. Interface
Python 里面的 dunder 函数,只能算做一种 protocol,而非 interface。protocol 像是一种不太强的约定,而 interface 是一种强的约定。比如我实现了 __getitem__
来取下标,但是如果对于我的类,slicing 并没有意义,那我可以不实现对 slice 的处理。而 interface 表示你必须实现它约定的一系列函数。