流畅的 Python 学习笔记
-
1
magic/dunder method
为了能求得
my_collection[key]
的值,解释器实际上会调用my_collection.__getitem__(key)
。一摞Python分格的纸牌
import collections Card = collections.namedtuple('Card', ['rank', 'suit']) class FrenchDeck: ranks = [str(n) for n in range(2, 11)] + list('JQKA') suits = 'spades diamonds clubs hearts'.split() def __init__(self): self._cards = [Card(rank, suit) for suit in self.suits for rank in self.ranks] def __len__(self): return len(self._cards) def __getitem__(self, position): return self._cards[position]
通过实现特殊方法来利用 Python 数据模型的两个好处:
- 作为类的用户,不必去记住标准操作的各式名称(
.size()
/.length()
) - 可以更加方便地利用 Python 的标准库,如
random.choice()
等
而且,因为
__getitem__
方法把[]
操作交给了self._cards
列表,所以我们的 deck 类支持切片、迭代
- 作为类的用户,不必去记住标准操作的各式名称(
-
实现二维向量类
from math import hypot class Vector: def __init__(self, x=0, y=0): self.x = x self.y = y def __repr__(self): return 'Vector(%r, %r)' % (self.x, self.y) def __abs__(self): return hypot(self.x, self.y) def __bool__(self): # return bool(abs(self)) return bool(self.x of self.y) def __add__(self, other): x = self.x + other.x y = self.y + other.y return Vector(x, y) def __mul__(self, scaler): return Vector(self.x * scaler, self.y * scaler)
- 内置函数
__repr__
:把一个对象用字符串的形式表达出来。 - 通过
__add__
和__mul__
,为向量类带来了 + 和 * 两个算术运算符,中缀运算符的基本原则就是不改变操作对象,而是产出一个新的值。 __bool__
Python 会调用该函数判断值为 True 还是 False,自定义的类实例默认总为 True
- 内置函数
-
len()
对 Python 内置类型操作时 CPython 会直接从 C 结构体中读取对象的长度,这种处理方式在保持内置类型的效率和保证语言的一致性之间找到了一个平衡点
-
2 序列构成的数组
- 容器序列:list,tuple,collections.deque 存放包含对象的引用
- 扁平序列:str,bytes 存放值
可变序列(MutableSequence)继承与不可变序列(Sequence)
-
列表推导:生成列表
codes = [x for x in xs if x > 100]
,列表推导不会有变量泄露(类似局部作用域)笛卡尔积
self._cards = [Card(rank, suit) for suit in self.suits for rank in self.ranks]
两个循环的嵌套关系和for循环顺序相同,即先花色再点数
-
生成器表达式:生成元组、数组或其他数据类型
tuple(ord(symbol) for symbol in symbols) array.array('I', (ord(symbol) for symbol in symbols))
- 生成器表达式背后遵守了迭代器协议,可以逐个地产出元素,而不是先建立一个完整的列表,然后再把这个列表传递到某个构造函数里。节省内存。
- 如果生成器表达式是一个函数调用过程中的唯一参数,则不需要额外括号
for tshirt in ('%s %s' % (c, s) for c in colors for s in sizes):
-
元组
作为记录的数据类型
元组拆包
=
,%
,for
b, a = a, b
不确定数量的参数a, b, *args, c, d = range(10)
,args
为list
**嵌套元组拆包 **for name, cc, pop, (lat, lng) in metro_areas:
具名元组namedtuple>>> from collections import namedtuple >>> City = namedtuple('City', 'name country population coordinates') >>> tokyo = City('Tokyo', 'JP', 36.933, (35.689722, 139.691667)) >>> tokyo City(name='Tokyo', country='JP', population=36.933, coordinates=(35.689722, 139.691667)) >>> tokyo.population 36.933 >>> tokyo.coordinates (35.689722, 139.691667) >>> tokyo[1] 'JP'
- 创建一个具名元组需要两个参数,一个是类名,另一个是类的各个字段的名字。后者可以是由数个字符串组成的可迭代对象,或者是由空格分隔开的字段名组成的字符串。
作为不可变列表
除了增减元素相关的方法之外,元组支 持列表的其他所有方法
-
切片
在切片和区间操作里不包含区间范围的最后一个元素是 Python 的风格,这个习惯符合 Python、C 和其他语言里以 0 作为起始下标的传统。 这样做带来的好处如下。
- 当只有最后一个位置信息时,我们也可以快速看出切片和区间里有 几个元素:
range(3)
和my_list[:3]
都返回 3 个元素。当起止位置信息都可见时,我们可以快速计算出切片和区间的长度,用后一个数减去第一个下标(stop - start)即可。 - 这样做也让我们可以利用任意一个下标来把序列分割成不重叠的两部分,只要写成 my_list[:x] 和 my_list[x:]即可
给切片赋值
l[2:5] = [20, 30]
可以对序列进行嫁接、切除或就地修改操作
**对序列使用*
**序列重复n遍
注意如果序列的元素是其他可变对象的引用,复制后仍指向同一个对象
['_'] * 3
√
[['_'] * 3] * 3
×序列的增量赋值
- 当只有最后一个位置信息时,我们也可以快速看出切片和区间里有 几个元素:
-
序列的增量赋值
相当于
extend()
,需要实现__iadd__()
方法list.sort 方法会就地排序列表,也就是说不会把原列表复制一份。这 也是这个方法的返回值是 None 的原因,提醒你本方法不会新建一个列 表。在这种情况下返回 None 其实是 Python 的一个惯例:如果一个函数 或者方法对对象进行的是就地改动,那它就应该返回 None,好让调用 者知道传入的参数发生了变动,而且并未产生新的对象
目前所提到的内容都不仅仅是对列表或者元组有效,还可以应用于几乎 所有的序列类型上。有时候因为列表实在是太方便了,所以 Python 程 序员可能会过度使用它,反正我知道我犯过这个毛病。而如果你只需要 处理数字列表的话,数组可能是个更好的选择。下面就来讨论一些可以 替换列表的数据结构。
-
数组 如果我们需要一个只包含数字的列表,那么 array.array 比 list 更 高效。数组支持所有跟可变序列有关的操作,包括 .pop、.insert 和 .extend。另外,数组还提供从文件读取和存入文件的更快的方法,如 .frombytes 和 .tofile。
-
ndarray
-
-
dict 和 set。容器序列可以嵌套着使 用,因为容器里的引用可以针对包括自身类型在内的任何类型。 与此相反,扁平序列因为只能包含原子数据类型,比如整数、浮点 数或字符,所以不能嵌套使用。
Python 入门教材往往会强调列表是可以同时容纳不同类型的元素 的,但是实际上这样做并没有什么特别的好处。我们之所以用列表 来存放东西,是期待在稍后使用它的时候,其中的元素有一些通用 的特性(比如,列表里存的是一类可以“呱呱”叫的动物,那么所有 的元素都应该会发出这种叫声,即便其中一部分元素类型并不是鸭 子)。在 Python 3 中,如果列表里的东西不能比较大小,那么我们 就不能对列表进行排序:
>>> l = [28, 14, '28', 5, '9', '1', 0, 6, '23', 19] >>> sorted(l) Traceback (most recent call last): File "", line 1, in TypeError: unorderable types: str() < int()
元组则恰恰相反,它经常用来存放不同类型的的元素。这也符合它 的本质,元组就是用作存放彼此之间没有关系的数据的记录。
-
3. 字典和集合
可散列
__hash__()
字典推导
dict、defaultdict 和 OrderedDict
DIAL_CODES = [ (86, 'China'), (91, 'India'), ... ] country_code = {country: code for code, country in DIAL_CODES} {code: country.upper() for country, code in country_code.items()
用setdefault处理找不到的键
dict.set_default
index.setdefault(word, []).append(location)
纯地查找取值(而不是通过查找来插入新值)的时候
defaultdict:处理找不到的键的一个选择
调用 default factory 工厂函数
__missing__
return self[str(key)] 把非字符串的值转换为字符串
collections.OrderedDict 添加键时保持顺序
collections.ChainMap 查找多个映射对象
collections.Counter 计数器
colllections.UserDict 让用户继承写子类
-
5.一等函数:函数视作对象
- 在运行时创建
- 能赋值给变量或数据结构中的元素
- 能作为参数传给函数
- 能作为函数的返回结果
函数视作对象
高阶函数:接受函数为参数,或者把函数作为结果返回的函数
函数式语言通常会提供 map、filter 和 reduce 三个高阶函数
在 Python 3 中,map 和 filter 还是内置函数,但 是由于引入了列表推导和生成器表达式,它们变得没那么重要了。列表 推导或生成器表达式具有 map 和 filter 两个函数的功能,而且更易于 阅读。内置的 sum