[关闭]
@spiritnotes 2016-06-14T16:43:54.000000Z 字数 28877 阅读 2469

《Python Cookbook》

Python 读书笔记 DOING


1 数据结构和算法

1.1 将序列分解为单独的变量采用赋值操作:

a, b, c = iterable
a, _, _ = iterable

1.2 从任意长度的可迭代对象中分解元素

a, b, c = itreable #变量可以为空
可以嵌套: a, _, (_, c) = iterable
* 在处理函数参数很有用

1.3 保存最后N个元素

collections.deque(maxlen=N)
q.append(v),q.appendleft(v),q.pop(),q.popleft()

1.4 找到最大或最小的N个元素

heapq.nlargest(N, numbers, key=)
heapq.nsmallest(N, numbers, key=)

heapq.heapify(list_), heapq.heappop(list_)

1.5 实现优先级队列

使用 heapq, 复杂度 O(logN)
heapq.heappush(q, (-priority, index, item)) #index对于相同优先级排位使用
heapq.heappop(q)[-1]

1.6 在字典中将键映射到多个值上

值用列表或者字典来表示
collections.defaultdict
dict_.setdefault(key, default_value).append(v) #每次调用都创建一个default_value

1.7 让字典保持有序

collections.OrderedDict,迭代时严格按照元素添加的次序
内部维护了一个双向链表,空间使用较多

1.8 与字典有关的计算问题

value_key = min/max/sorted(zip(dict_.values(),dict_.keys()))
注意 zip 创建了一个迭代器,只能消费一次
key_min = min(dict_)
key_min_value = min(dict_, key=lambda x:dict_[x])

1.9 在两个字典中寻找相同点

a.keys() & b.keys()
a.items() & b.items()
key-view对象支持常见的集合运算,& | -
item-view对象也支持
values()方法不支持,values可能不是唯一的

1.10 从序列中移除重复项并且保持元素间顺序不变

使用生成器+set实现,如果不支持hash,手动配置一个hash

  1. def get_unique_same_order(l):
  2. seen = set()
  3. for i in l:
  4. if i not in seen:
  5. yield i
  6. seen.add(i)

1.11 对切片命名

shares = slice(begin, end)
slice_.start, slice_.stop, slice_.step

1.12 找出序列中出现次数最多的元素

collections.Counter
counter_.most_common/update/['key']/['key']+=1
counter_1 +/- counter_2

1.13 通过公共键对字典列表排序

sorted(dicts, key=operator.itemgetter('key','key2')) #比lambda更快
sorted(dicts, key=lambda dict: dict['key'],dict['key2'])

1.14 对不原生支持比较操作的对象排序

sorted(objs, key=operator.attrgetter('attr1','attr2'))
sorted(objs, key=lambda obj_:obj_.attr1,obj.attr2)

1.15 根据字段将记录分组

先排序,然后使用 itertools.groupby()

  1. datas.sort(key=attrgetter('date'))
  2. for date, items in groupby(key=attrgetter('date')):
  3. for i in items:
  4. pass

使用一个defaultdict(list)进行记录,避免排序,更快

1.16 筛选序列中的元素

列表推导式 [k for k in l if xx(k)]
生成表达式 (k for k in l if xx(k))
filter(xx, l)产生一个迭代器
itertools.compress(iterable, bool_iterable)

1.17 从字典中提取子集

字典推导式 {key:val for key,val in dict_.items() if xx(y)}

1.18 将名称映射到序列的元素中

nametupel_name = collections.namedtuple(nametupel_name, ['key1','key2',...])
_ = nametupel_name(v1, v2)
_.key1, _.key2

namedtuple是不可变的,所需空间比字典少
_ = _._replace(key1 = v3)

如果实例对象修改多,则最好使用slots属性的类

1.19 同时对数据做转换和换算

需要调用换算reduction(sum、max、min、any)之前先要进行转换或筛选
生成器表达式 sum(f(i) for i in l if xx)
有些reduction函数支持 key 参数进行先转换

1.20 将多个映射合并成单个映射

collections.ChainMap
支持字典的那些操作,使用的是原始的字典,重复键显示前面的
修改映射的操作总是会作用在列出的第一个映射结构上

map_['x'] = 1
map_ = map_.new_child() 产生一个新map并放在前面
map_ = map_.parents 移动到后面的map

可以使用字典以及update方法实现,这样会创建一个新字典

2 字符串和文本

2.1 针对任意多的分隔符拆分字符串

使用正则表达式

  1. import re
  2. re.split(r'[;,\s]\s*', line)

如果使用了捕获组(capture group括号),则分隔符也会保留下来
注: "(?:,|;|\s)\s*"也是一种书写方式而不是捕获组

2.2 在字符串的开头或结尾处做文本匹配

使用 startswith(str/tuple)/endswith(str/tuple)
可以使用切片 [:-2] == x,该方法性能更快
使用re re.match(....)

2.3 利用Shell通配符做字符串匹配

  1. from fnmatch import fnmatch,fnmatchcase
  2. fnmatch('foo.txt','*.txt') #与底层的大小写规则相同
  3. fnmatch() #大小写相关

2.4 文本模式的匹配和查找

简单模式使用基本的字符串方法
赋值模式使用正则表达式
match方法总是在字符串的开头找匹配项
findall方法找到字符串中所有匹配项
将部分模式用括号包起来的方式引入捕获组
findall以列表的方式返回,finditer以迭代的方式返回
多次执行匹配和查找,应该先将模型编译然后重复使用

2.5 查找和替换文本

简单文本:使用 str_.replace()
复杂文本:使用 re.sub(r'(\d+)/(\d+)/(\d+), r'\3-\1-\2), txt_)
再复杂,使用回调函数 re.sub(r'..', fun_, txt_),回调函数的输入参数是一个匹配对象,由match或find返回,可以用group方法来获取匹配中特定的部分
使用 re.subn()除了替换外还返回替换的次数

2.6 以不区分大小写的方式对文本做查找和替换

使用re re.findall(..., flags=re.IGNORECASE)
如果替换需要的是同样结果,可能需要支撑函数

  1. def match(str_):
  2. def repalce(m):
  3. text = m.group()
  4. if text.isupper():
  5. return str_.upper()
  6. elif text.islower():
  7. return str_.lower()
  8. elif text[0].isupper()
  9. return str_.capitalize()
  10. else:
  11. return str_
  12. return replace
  13. re.sub('python',match('snake'),text,flags=re.IGNORECASE)

2.7 定义实现最短匹配的正则表达式

模式的*或者+号操作符后面加上?修饰符

2.8 编写多行模式的正则表达式

.号不匹配换行符,因此可以采用 '.|\n'
re.DOTALL,使得.号可以匹配所有字符

2.9 将unicode文本统一表示为规范形式

有的文本有多种表现形式
使用unicodedata.normalize('NFC', s1) ...
NFC:全组成
NFD:组合字符

2.10 用正则表达式处理Unicode字符

考虑使用第三方库

2.11 从字符串中去掉不需要的字符

strip lstrip rstrip,支持自定义多个字符
对于处于中间的,replace, re.sub('\s+',' ')

2.12 文本过滤和清理

translate,建立小型的转换表

2.13 对齐文本字符串

ljust rjust center,并可以可选填充字符
format(text, '>20s')
format(text, '
>10.2f')
'{:-^20s}'.format(text)

2.14 字符串连接及合并

字符串在序列或可迭代对象中 -- 使用join()方法,实际使用中最好考虑生成器
字符串较少 -- 使用 +
较复杂的格式 -- 使用format
源码中字符串合并 -- 直接连续写 'aaa''bbb'

2.15 给字符串中的变量名做插值处理

python本身不支持,使用var()

  1. name, n = 'a', 20
  2. '{name} has {n} ... '.format_map(var())
  3. # some class instance a
  4. a.name, a.n = 'b', 10
  5. '{name} has {n} ... '.format_map(var(a))
  6. # 使用定义了missing的类来包裹
  7. class safesub(dict):
  8. def __missing__(self, key):
  9. return '('+key+')'
  10. '{name} has {n} ... '.format_map(safesub(var()))
  11. def sub(text):
  12. return text.format_map(safesub(sys._getframe(1).f_locals))

2.16 以固定的列数重新格式化文本

os.get_terminal_size().columns()
textwarp.fill(s, 40, subsequent_indent='')

2.17 在文本中处理HTML和XML实体

转为html,转义特殊字符 -- html.escape(s, quote=False)
转回使用 html.parser.HTMLParser().unescape(s)

2.18 文本分词

使用正则表达式的命名组来识别词以及类型

  1. NAME = r'(?P<NAME>[a-zA-Z_][a-zA-Z_0-9]*)'
  2. NUM = r'(?P<NUM>\d+')
  3. TIMES = r'(?P<TIMES>\*)'
  4. ...
  5. master_pat = re.compile('|'.join([NAME, ... WS]) # re 按照指定的顺序对模式进行匹配
  6. scanner = match_pat.scanner(s='foo = 42')
  7. while ..
  8. _ = scanner.match()
  9. _.lastgroup, _.group()
  10. for m in iter(scanner.match, None):
  11. yield m.lastgroup, m.group()

2.19 编写一个简单的递归下降解析器

对于复杂的使用PyParsing/PLY解析工具

2.20 在字节串上执行文本操作

基本上与str一样,除了c[0]在b和str中返回不一样,b中返回数值

3 数字、日期和时间

3.1 对数值进行取整

round(value, ndigits), ndigits可以为负数,取整到十位、百位等

3.2 执行精确的小数计算

使用decimal模块
可以使用decimal.localcontext()进行精确控制

3.3 对数值做格式化输出

控制位数、对齐、包含千位分隔符
使用内建format函数,format(x, '<10.1f')

3.4 同二进制、八进制和十六进制数打交道

整数转为文本格式,使用 bin、hex、oct函数
要去掉前面的0x、0o、0b,使用format函数 format(x, 'b')

3.5 从字节串中打包和解包大整数

int.from_bytes(data, 'little')
int_.to_bytes(16, 'big')
可以使用struct模块,struct解包的整数大小是有限制的
int_obj.bit_length() 来获得位数

3.6 复数运算

产生 complex(real, imag) 或者 a + bj
a.real / a.imag / a.conjugate()
需要使用复数,则使用 cmath 模块

3.7 处理无穷大和NaN

float('inf') float('-inf') float('nan')
math.isinf(x) math.isnan(x)
NaN的微妙特性是做比较时从不会被判定为相等
float('inf') == float('inf') => False
float('inf') == float('inf') => False

3.8 分数的计算

模块fractions

3.9 处理大型数组的计算

使用numpy库

3.10 矩阵和线性代数的计算

numpy.linalg

3.11 随机选择

random.choice 不取走
random.sample 取走
random.suffle 洗牌
random.randint 随机整数
random.random 0~1之间小数
random.getrandbits 随机nbits数

3.12 时间换算

datetime中timedelta
复杂应用可以使用 dateutil 模块

3.13 计算上周五的日期

使用date_.weekday() weekdays.index(x)以及7进行运算
可以使用 dateutl 直接计算

3.14 找出当月的日期范围

start = date.today().replace(day=1)
_, days_in_month = calendar.monthrange(start.year, start.month)
end = start + timedelta(days=days_in_month)

3.15 将字符串转换为日期

datetime.strptime(s, forat) 性能慢
datetime.strftime(x, format_)

3.16 处理涉及到时区的日期问题

使用 pytz 模块
建立将所有时间转为 UTC 时间再进行操作,世界统一时间

4 迭代器和生成器

4.1 手动访问迭代器中的元素

使用 next(x) 函数,手动捕捉 StopIteration
使用 next(x, None) 函数,使其返回指定值
原理

4.2 委托迭代

直接将对self的迭代转移到其它对象中

  1. def __iter__(self):
  2. return iter(other_obj/self.child)

4.3 用生成器创建新的迭代模式

生成器不需要调用 iter, 直接使用 next

4.4 实现迭代协议

使用生成器,简化编写 iter next等函数

4.5 反向迭代

使用内建 reversed(x) 函数,要求对象实现了 __reversed__() 函数

4.6 定义带有额外状态的生成器函数

想要定义一个生成器函数,但是需要向外部暴露状态信息
实现成一个类,类具有状态,定义其 __iter__ 为生成器函数,也可以将其实现为闭包

  1. lines = GeneratorClass(Some_Status)
  2. for line in lines:
  3. ....

4.7 对迭代器做切片操作

对迭代器和生成器做切片操作,使用 itertools.islice(c, 10, 20) 函数
islice 会消耗掉迭代器或生成器里面的数据

4.8 跳过可迭代对象中的前一部分元素

itertools.dropwhile(lambda ..., x) 根据条件跳过
itertools.islice(c, 5, None) 跳过前5行

4.9 迭代所有可能的组合或排列

itertools.permutations(items, n)
itertools.combinations(items, n)
iteryools.combinations_with_replacement(....) 元素可重复出现

4.10 以索引-值对的形式迭代序列

enumerate(lists, start=0)
for n, (x,y) in enumerate(data) 元组的列表

4.11 同时迭代多个序列

使用zip函数,以较短的输入序列长度为准
itertools.zip_longest(a,v,fillvalue=0)
dict(zip(headers, values))

4.12 在不同的容器中进行迭代

itertools.chain(a, b, c)

4.13 创建处理数据的通道

生成器函数是一种实现管道机制的好方法。

  1. lognames = gen_find('access-log','www')
  2. files = gen_opener(lognames)
  3. lines = gen_concatenate(files)
  4. pylines = gen_grep('(?i)python', lines)
  5. for line in pylines:
  6. ...
  7. # 注意python3中引入的 yield from

4.14 扁平化处理嵌套型的序列

  1. def flatten(items, ignore_type=(str, bytes)):
  2. for x in items:
  3. if isinstance(x, Iterable) and not isinstance(x, ignore_types):
  4. yield from flatten(x)
  5. else:
  6. yield x
  7. ## yield from x 相当于 如下,但是有优化
  8. ## for i in x:
  9. ## yield i

4.15 合并多个有序序列,再对整个有序序列进行迭代

  1. for c in heapq.merge(ordered_a, ordered_b):
  2. ...
  3. # 只是简单取两个队列,将小的发出去,不会一次性取整个队列

4.16 用迭代器取代while循环

支持一个无参的可调用对象以及一个哨兵(结束)值作为输入

  1. for chunk in iter(lambda: f.read(10),b''):
  2. ....

5 文件和I/O

5.1 读写文本数据

文件读取的默认编码方式 sys.getdefaultencoding()
open函数中可以带 encoding参数, erros参数
print(xxx, file=x)

5.2 将输出重定向到文件中

print(xxx, file=f) # 必须以文本模式打开

5.3 以不同的分隔符或行尾符完成打印

使用 print 以及 sep、end 参数
也可以使用 join,其缺点是其序列元素必须为字符串

5.4 读写二进制数据

使用 open 函数的 wb、rb 参数
bytes 对象 索引会返回整数
将字符串二进制方式写入文件,需要对字符串编码
底层数组或c结构体可以直接二进制读写

5.5 对已不存在的文件执行写入操作

使用 xt、xb 模式(python3.3)创建文件并写入,如文件存在则报错

5.6 在字符串上执行IO操作

io.StringIO 以及 io.BytesIO 可以模仿文件等执行 io 操作
write / read / getvalue
可以用于单元测试中

5.7 读写压缩的数据文件

使用 gzip.open(...) / bz2.open(...)
可以对已经打开的二进制模式的文件进行叠加操作,可以同各种类型的类文件对象如套接字、管道、内存文件一起工作

5.8 对固定大小的记录进行迭代

使用 iter() 和 functools.partial() 来完成

  1. records = iter(partial(f.read, RECORD_SIZE), b'')
  2. for r in records:
  3. ...

5.9 将二进制数据读取到可变缓冲区中

使用文件对象的 readinto(x) 方法,避免额外内存分配

5.10 对二进制文件做内存映射

  1. mmap.mmap(fd, size, accsess=...)
  2. # 使用 memoryview 来以不同的方式访问它

5.11 处理路径名

os.path

5.12 检测文件是否存在

5.13 获取目录内容的列表

os.listdir(x)
可以通过 glob 或 fnmatch 对文件名进行匹配操作

5.14 绕过文件名编码

对 open/listdir 提供 字节串 参数

5.15 打印无法解码的文件名

使用 repr(filename)[1:-1]

5.16 为已经打开的文件添加或修改编码方式

用 io.TextIOWrapper() 对其进行包装
对sys.out....

5.17 将字节数据写入文本文件

将字节数据写入文件底层的buffer中就可以了
sys.stdout.buffer.write(b'...')

5.18 将已有的文件描述符包装为文件对象

可能为文件、管道、socket等,
f = open(fd,'wt',closefd=False)

5.19 创建临时文件和目录

tempfile.TemporaryFile() 可接受和内建的open函数一样的参数
NamedTemporaryFile() .name 含有其文件名
TemporaryDirectory
还有底层 mkstemp mkdtemp
tempfile.gettempdir()

5.20 同串口进行通信

ser = Serial.Serial('/dev/...' ...)

5.21 序列化Python对象

pickle.dump(..) /dumps(...)
pickle.load(..) /loads(...)

6 数据编码与处理

6.1 读写CSV数据

使用CSV库,还可以使用命名元组

  1. with open(...) as f:
  2. f_csv = csv.reader(f) ### csv.DictReader(...)
  3. heading = next(f_csv)
  4. Row = namedtuple('Row', headings)
  5. for r in f_csv:
  6. row = Row(*r)
  7. row. a ### row['a']
  8. with open('..', 'w') as f:
  9. f_csv = csv.writer(f) ### DictWriter(f, headers)
  10. f_csv.writerow(headers)
  11. f_csv.writerows(rows)

6.2 读写JSON数据

json.dumps() / json.loads()
json.dump() / json.load()
映射不同点

通过 loads 的 object_pairs_hook 或 object_hook 参数,来将JSON解码为 OrderedDict 或 某对象(创建的对象作为参数传递给类的 __init__)

几个有用的选项

对类对象进行序列化和反序列化

  1. def serialize_instance(obj):
  2. d = { "__classname__": type(obj).__name__}
  3. d.update(vars(obj))
  4. return d
  5. def unserialize_object(d):
  6. clsname = d.pop('__classname__', None)
  7. if clsname:
  8. cls = classes(clsname)
  9. obj = cls.__new__(cls) # mk instance without call init
  10. for key, value in d.items():
  11. setattr(obj, key, value)
  12. return obj
  13. return d
  14. s = json.dumps(p, defautlt=serialize_instance)
  15. a = json.loads(s, object_hook=unserialize_object)

6.3 解析简单的XML文档

xml.etree.ElementTree
复杂的过程则采用 lxml

6.4 以增量方式解析大型XML文件

使用 iterparse

6.5 将字典转换为 xml

  1. def dict_to_xml(tag, d):
  2. elem = Element(tag)
  3. for key, val in d.items():
  4. child = Element(key)
  5. child.text = str(val)
  6. elem.append(child)
  7. return elem
  8. # 赋值属性 e.set('_id', '1234')

比手工处理好,能处理val的特殊字符,手工处理需要采用 escape() / unescape()

6.6 解析、修改、重写XML

使用 root.remove() / child.insert() / root.append()
还可以使用索引和切片操作 element[i], element[i:j]

6.7 用命名空间来解析XML文档

6.8 同关系数据库进行交互

6.9 编码和解码十六进制数字

将字节码编码或解码为十六进制数
binascii.b2a_hex(s)
binascii.a2b_hex(h) # 字符串为 b'hello'
还可以使用base64模块
base64.b16encode(s) / base64.b16decode(h) # 只对大写16进制格式

6.10 Base64 编码和解码

base64.b64encode(s)
base64.b64decode(a)

6.11 读写二进制结构的数组

使用 struct 模块
使用 iter 模块来简化代码
使用 numpy.fromfile(...) 直接全部读出

6.12 读取嵌套型和大小可变的二进制结构

文件含有文件头和后续的不定长度的数据记录

TTOOOO

7 函数

7.1 编写可接受任意数量参数的函数

  1. def fun(k, *args, y, z, **kwargs): # y,z为命名参数,只能通过y=,z=定义
  2. ...

7.2 编写只接受关键字参数的函数

放置在以*打头的参数之后

  1. def recv(maxsize, *, block): # block 只能命名指定
  2. def minium(*values, clip=None): # ...
  3. def trun(*args, **kwargs):
  4. trun2(*args, **kwargs)
  5. def trun2(*args, para, **kwargs)

7.3 将元数据信息附加到函数参数上

函数注解 fun.annotations 属性

  1. def add(x:int, y:int) -> int:
  2. pass # 对原函数没有任何影响

7.4 从函数中返回多个值

使用元组

7.5 定义带有默认参数的函数

在参数后 para=init_value
如果是容器则需要确认是否期望副作用,不期望则使用 None
用来检查该值是否赋值

  1. _no_value = object()
  2. def spam(a, b=_no_value):
  3. if b is _no_value: # b 未被赋值
  4. pass

7.6 定义匿名或内联函数

lambda 表达式创建函数对象

7.7 在匿名函数中绑定变量的值

lambda 表达式中的x为自由变量,在运行时才进行绑定而不是定义的时候
如果期望使用定义时,则可以使用默认参数达到效果

7.8 让带有N个参数的可调用对象以较少的参数形式调用

使用 functools.partial() 来绑定部分参数,位置参数从左到右匹配
可以用在函数调用上,也可以用在实例的创建上
也可以使用 lambda 来代替 partial 的使用

7.9 用函数替代只有单个方法的类

单个方法,除了init外

  1. def urlopen(temp): ## 使用闭包
  2. def open(**kwargs):
  3. return ....

7.10 在回调函数中携带额外的状态

  1. class A:
  2. def __init__(self, ..):
  3. ...
  4. def handler(self, ...):
  5. ...
  6. apply_async(add, (2, 3), callback=r.handler)
  1. def make_handler():
  2. sequence = 0
  3. while True:
  4. result = yield
  5. sequence += 1
  6. print ...
  7. handler = make_handler()
  8. next(handler)
  9. apply_async(add, (2, 3), callback=handler.send)
  1. apply_async(add, (2, 3), callback=partial(handler, seq=seq))
  1. apply_async(add, (2, 3), callback=lambda r:handler(r, seq))

7.11 内联回调函数

  1. class Async:
  2. def __init__(self, fun, args):
  3. self.fun = fun
  4. self.args = args
  5. def inlined_async(func):
  6. @wraps(func)
  7. def wrapper(*args):
  8. f = func(*args)
  9. result_queue = Queue()
  10. result_queue.put(None)
  11. while True:
  12. result = result_queue.get()
  13. try:
  14. a = f.send(result)
  15. apply_async(a.func, a.args, callback=result_queue.put)
  16. except StopIteration:
  17. break
  18. return wrapper
  19. @inlined_async
  20. def test():
  21. r = yield Async(add, (2,3))
  22. r = yield Async(add, (1,1))
  23. for i in range(10):
  24. r = yield Async(add, (i,i))
  25. print(r)
  26. pool = multiprocessing.pool()
  27. apply_async = pool.apply_async
  28. test()

7.12 访问定义在闭包内的变量

  1. def sample():
  2. n = 0
  3. def fun():
  4. pass
  5. def get_n():
  6. return n
  7. def set_n(value):
  8. nolocal n
  9. n = value
  10. fun.get = get_n
  11. fun.set = set_n
  12. return fun
  13. f = sample()
  14. f.set(5)
  15. f.get()
  16. f()

因此可以用闭包来模拟类

  1. def Stack():
  2. local_val = []
  3. def push():..
  4. def pop():..
  5. return ClosureInstance() # 当stack内部的函数update到其__dict__中,闭包处理更快,因为不涉及self变量

8 类与对象

8.1 修改实例的字符串表示

  1. class Pair():
  2. def __repr__(self):
  3. return 'Pair({0.x!r,{0.y!r})'.format(self) ### eval(repr(obj)) == obj
  4. def __str__(self):
  5. return 'Pair({0.x!s,{0.y!s})'.format(self)

8.2 自定义字符串的输出格式

定义 __format__(self, code) 函数

  1. def __format__(self, code):
  2. fmt = _formats[code]
  3. return fmt.format(d=self)
  4. # 使用
  5. format(instance_, code ## = '')

8.3 让对象支持上下文管理协议

实现 __enter__ 和 __exit__(self, exc_ty, exc_val, tb) 函数
with some_instance as instance_some_member: ....
可以修改 enter 和 exit 使其可以用在嵌套的 with 中(创建新对象并保存到栈中)

8.4 当创建大量实例时如何节省内存

在类定义中定义 __slots__属性,这样针对实例会采用更加紧凑的内部表示,不会对每个实例再创建dict了,缺点是没有办法为实例再添加新的属性了,相当于将数据保存在元组中(也不能使用多重继承了)

  1. class ...
  2. __slots__ = ['year', 'month', 'day']

8.5 将名称封装到类中

以 _ 开头的名字应该总是被认为is只属于内部实现
以 __ 开头的名称会导致出现名字重整 (name mangling),为了继承,这样的属性不能通过继承而覆盖,如 __init__
如果定义变量与保留字冲突,可以在其名字后添加一个_,如 class_

8.6 创建可管理的属性

  1. ## 方法1
  2. class Person:
  3. @property
  4. def first_name(self):
  5. pass
  6. @first_name.setter
  7. def first_name(self, value):
  8. pass
  9. @first_name.deleter
  10. def first_name(self):
  11. pass
  12. ## Person.first_name.fget/fset/fdel 为底层函数
  13. ## 方法2
  14. class ...
  15. def ...
  16. name = property(get_fun, set_fun, del_fun)

8.7 调用父类的方法

使用super函数
常见用途

  1. class Proxy:
  2. def __getattr__(self, name):
  3. return getattr(self._obj, name)

8.8 在子类中扩展属性

  1. class Sub(Father):
  2. @property
  3. def name(self):
  4. return supper().name
  5. @name.setter
  6. def name(self,value):
  7. super(Sub, Sub).name.__set__(self, value) ## 必须以类变量使用
  8. @name.deleter
  9. def name(self):
  10. super(Sub, Sub).name.__delete__(self)
  1. class Sub(Father):
  2. @Father.name.getter ## 使用父类,否则其setter和deleter相当于未定义
  3. def name(self):
  4. return supper().name

8.9 创建一种新形式的类属性或实例属性

描述符是以特殊方法 __get__(), __set__() 和 __delete__() 的形式实现了三个核心的属性访问操作(对应于get、set、delete)的类。通过接受类实例作为输入来工作。

  1. class I:
  2. def __get__(self, instance, cls): ## 以类调用时,instance为None
  3. if instance is None:
  4. return self
  5. ...
  6. def __set__(self, instance, value):
  7. ...
  8. def __delete__(self, instance):
  9. ...
  10. class P:
  11. x = I()
  12. y = I()
  13. ...
  14. p = P()
  15. p.x ## call P.x.__get__(p, P)
  16. p.x = v ## call P.x.__set__(p, v)
  17. P.x ## call P.x.__get__(None, P)

@classmethod\@staticmethod\@property__slots__底层均由描述符实现

  1. class Typed:
  2. def __init__(self, name, type_):
  3. self.name = name
  4. self.type_ = type_
  5. def __get__(self, instance, cls):
  6. if instance is None: return self
  7. return instance.__dict__[self.name]
  8. def __set__(self, instance, value):
  9. if not isinstance(value, self.type_):
  10. raise ...
  11. instance.__dict__[self.name] = value
  12. def __delete__(self, instance):
  13. del instance.__dict__[self.name]
  14. def typeassert(**kwargs):
  15. def wrap(cls):
  16. for name, type_ in kwargs.items():
  17. setattr(cls, name, Typed(name, type_))
  18. return cls
  19. return wrap
  20. @typeassert(name=str, age=int)
  21. class Person:
  22. def ....

8.10 让属性具有惰性求值的能力

缺点是该值变为可修改了

  1. class Lazyproperty:
  2. def __init__(self, func):
  3. self.func = func
  4. def __get__(self, instance, cls):
  5. if instance is None:
  6. return self
  7. else:
  8. value = self.func(isntance)
  9. setattr(instance, self.func.__name__, value) ## instance.x为value,A.x为Lazyproperty对象
  10. return value
  11. class A:
  12. @Lazyproperty
  13. def area(self):
  14. ...

lingwai

8.11 简化数据结构的初始化过程

将初始化数据结构归纳到一个单独的__init__()中,缺点是帮忙以及IDE的智能补全

  1. class Structed:
  2. _fields = []
  3. def __init__(self, *args):
  4. if len(args) != len(self._fields):
  5. raise ...
  6. for name, arg in zip(self._fields, args):
  7. setattr(self, name, arg) ## 使用setattr而不使用__dict__也可以应付slots或者property等情况
  8. class A(Structed):
  9. _fields = ['name', 'shares', 'price']
  10. ## 对于**keargs,可以使其做关键字映射,也可以将其作为_fields外的额外属性添加
  11. def init_fromlocals(self):
  12. import sys
  13. locs = sys._getframe(1).f_locals
  14. for k,v in locs.items():
  15. if k != 'self':
  16. setattr(self, k, v)
  17. class Stock:
  18. def __init__(self, name, shares, price):
  19. init_fromlocals(self) ## 比前面方面慢,且更复杂

8.12 定义一个接口或抽象基类

使用abc模块,不可实例化,强制规定所需的编程接口

  1. from abc import ABCMeta, abstractmethod
  2. class IStream(metaclass=ABCMeta):
  3. @abstractmethod
  4. def read(self, maxbytes=-1):
  5. pass
  6. @abstractmethod
  7. def write(self, data):
  8. pass
  9. ## error a = IStream()
  10. ## using IStream
  11. if not isinstance(stream, IStream):
  12. raise TypeError('....')
  13. ## 可与其他修饰符一块使用
  14. class A(metaclass=ABCMeta):
  15. @property
  16. @abstractmethod
  17. def name(self):
  18. pass

collections模块中定义了多个和容器还有迭代器(序列、映射、集合等)相关的抽象基类

  1. from collections import Sequence, Iterable, Sized, Mapping

8.13 实现一种数据模型或类型系统

对属性的设定做定制化处理,使用描述符来完成

  1. class Descriptor:
  2. def __init__(self, name=None, **opts):
  3. self.name = name
  4. for key, value in opts.items():
  5. setattr(self, key, value)
  6. def __set__(self, instance, value):
  7. instance.__dict__.[self.name] = value
  8. class Typed(Descriptor):
  9. expceted_type = type(None)
  10. def __set__(self, instance, value):
  11. if not isinstance(value, self.expected_type):
  12. raise ...
  13. super().__set__(instance, value)
  14. class String(Typed):
  15. expected_type = str
  16. class SizedString(String, MaxSized):
  17. pass
  18. class Stock:
  19. name = SizedString('name', size=8)

使用方法

  1. ### case 1 类装饰器
  2. def check_attributes(**kwwargs):
  3. def decorate(cls):
  4. for key, value in kwargs.items():
  5. if isinstance(value, Descriptor):
  6. value.name = key
  7. setattr(cls, key, value)
  8. else:
  9. setattr(cls, key, value(key))
  10. return cls
  11. return decorate
  12. @check_attributes(name=SizedString(size=8),
  13. shares=UnsignedInteger,price=UnsigendFloat)
  14. class Stock:
  15. def __init__(self, name, shares, price): ...
  16. ### case 2 元类
  17. class checkedmeta(type):
  18. def __new__(cls, clsname, bases, methods):
  19. for key, value in mothods.items():
  20. if isinstance(value, Descriptor):
  21. value.name = key
  22. return type.__new__(cls, clsname, bases, methods)
  23. class Stock(metaclass=checkedmeta):
  24. name = SizedString(size=8)
  25. ...
  26. def __init__(self, name ...):
  27. self.name = name

8.14 实现自定义的容器

继承自collections库中的抽象基类,会强制要求我们定义必须的方法

8.15 委托属性的访问

访问实例的属性时能够将其委托到一个内部持有的对象上。常用管理是代理类只委托那些不以下划线开头的属性(即代理类只暴露内部类的公有属性)

  1. ## 方法1, 方法数目不多时
  2. class ...
  3. def spam(self, x):
  4. return self._a.spam(x)
  5. ## 方法2
  6. ...
  7. def __getattr__(self, name): ## 找不到name时调用
  8. return getattr(self._a, name)
  9. ## 有时候可以使用委托来替代继承
  10. class B(A):
  11. ...
  12. class B:
  13. def __init__(self):
  14. self._a = A()
  15. def bar(...
  16. def __getattr__(self, name): ## 不支持大部分以双下划线开头和结尾的特殊方法,必须手动定义
  17. return getattr(self._a, name)
  18. def __len__(self):
  19. return len(self._a)

8.16 在类中定义多个构造函数

可以采用类方法来创建对象

  1. class A:
  2. def __init__(...): ...
  3. @classmethod
  4. def special_a(cls):
  5. return cls(...) ## 使用 cls 使其在继承子类中调用时返回为子类对象
  6. ## 通过init *args或**kwargs可以达到灵活的目的,但是其不容易理解

8.17 不通过调用 init 来创建实例

直接使用 new 来跳过 init 调用,常见的例子是反序列化、以及另外的构造函数(类函数实现)

  1. d = Date.__new__(Date)
  2. ## 手动设置属性
  3. for key, value in data.items():
  4. setattr(d, key, value)

8.18 用 Mixin 技术来扩展类定义

使用 Mixin 来对类方法进行定制化处理

  1. class LoggedMappingMixin:
  2. __slots__ = ()
  3. def __getitem__(self, key):
  4. print ..
  5. return super().__getitem__(key)
  6. ...
  7. class LoggedDict(LoggedMappingMixin, dict):
  8. pass

Mixin 类的特点

另外一种方法是装饰器

  1. def LoggedMapping(cls):
  2. cls_getitem = cls.__getitem__
  3. ...
  4. def __getitem__(self, key):
  5. ....
  6. cls_getitem(self, key)
  7. ...
  8. cls.__getitem__ = __getitem__
  9. ...
  10. return cls
  11. @loggedMapping
  12. class LoggedDict(dict):
  13. pass

8.19 实现带有状态的对象或状态机

  1. # old
  2. class Connection:
  3. def open():
  4. if self.state is open():
  5. raise ..
  6. do open
  7. # new
  8. class Connection:
  9. def open():
  10. self.state.open(self) ## 状态保留在本实例中
  11. class ConnectionBase():
  12. @staticmethod
  13. def open(conn): ## 这些类只提供方法,不保存状态
  14. raise ...
  15. class OpendConnection(ConnectionBase):
  16. @staticmethod
  17. def open(conn):
  18. ...
  19. class ClosedConnection(ConnectionBase):
  20. @staticmethod
  21. def open(conn):
  22. do open
  1. class Connection: ## 代码执行速度更快
  2. def new_state(self,new_state):
  3. self.__class__ = new_state
  4. class ClosedConnection(Connection):
  5. def ...

8.20 调用对象上的方法,方法名以字符串形式给出

8.21 实现访问者模式

实例:表达式树,并实现其运行方法(求值、生成代码等)

  1. class Node(): pass
  2. class BInaryOperator(Node):
  3. def __init__(self, left, right):
  4. ... # 赋值
  5. class Add(BInaryOperator): pass
  6. # 运算方法基类
  7. class NodeVistor:
  8. def visit(self, node):
  9. methname = 'visit_' + type(node).__name__
  10. meth = getattr(self, methname, None)
  11. if meth is None:
  12. meth = self.generic_visit
  13. return meth(node)
  14. def generic_visit(self, node):
  15. raise ...
  16. class Evaluator(NodeVistor):
  17. def visit_Number(self, node): return node.value
  18. def visit_Add(self, node): return self.visit(node.left) + self.visit(node.right)
  19. # 用于http处理
  20. class HTTPHandler():
  21. def handler(self, request):
  22. methname = 'do_' + request.request_method
  23. getattr(self, methname)(request)
  24. def do_Get(self, request): ...
  25. def do_Post(self, request): ...

8.22 实现非递归的访问者模式 *

使用堆栈和生成器

  1. class NodeVisitor:
  2. def visit(self, node):
  3. stack = [node]
  4. last_result = None
  5. while stack:
  6. try:
  7. last = stack[-1]
  8. if isinstance(last, types.GeneratorType):
  9. stack.append(last.send(last_result))
  10. last_result = None
  11. elif isinstance(last, node):
  12. stack.append(self._visit(stack.pop()))
  13. else:
  14. last_result = stack.pop()
  15. except StopIteration:
  16. stack.pop()
  17. return last_result
  18. def _visit(self, node):
  19. methname = 'visit_' + type(node).__name__
  20. meth = getattr(self, methname, None)
  21. if meth is None:
  22. meth = self.generic_method
  23. return meth(node)
  24. def gen...
  25. class UnaryOperator(Node):
  26. ...
  27. class Evaluatior(NodeVisitor): ...
  28. def visit_add(self, node):
  29. return self.visit(node.left) + self.visit(node.right) ## 递归会产生问题
  30. yield (yield node.left) + (yield node.right)

8.23 在环状数据结构中管理内存

使用weakref来避免内存不被释放
取得弱引用的对象需要通过函数式的调用

8.24 让类支持比较操作

使用functools.total_ordering装饰器,可以只定义__eq__和其余任意一个比例操作就可以了(le, ge, lt, gt)

  1. def total_ordering(cls):
  2. cls.__le__ = lambda self, other: self < other and self == other
  3. ...
  4. return new_cls

8.25 创建缓存实例

同样的参数产生同一个对象
要实现该行为,应该使用一个与类本身相分离的工厂函数
可以使用weakref.WeakValueDictionary作为缓存存储位置
如果修改__new__,在有对象时不再调用父类__new__,则会导致__init__每次都会被调用
可以将缓存管理独立成为单独的类,更加灵活
为防止用户自己创建类对象,可以使用_Spam定义类名,将__init__设计为抛出异常,而类工厂函数通过__new__创建对象

9 元编程

元编程主要是创建函数和类,并用它们来操纵代码(修改、生成或者包装已有的代码)

9.1 给函数添加一个包装

给函数加上一个包装层(wrapper layer),如记录日志、计时统计,使用装饰器函数

  1. @timethis
  2. def fun() ...
  3. ## => fun = timethis(fun)

9.2 编写装饰器时如何保存函数的元数据

如函数名、文档字符串、函数注解以及调用签名
使用functools.wraps装饰器保存元数据
可以通过__wrapped__属性来访问原函数

  1. def timethis(fun):
  2. @wraps(fun)
  3. def wrapper(*args, **kwargs):
  4. fun ...
  5. return wrapper

9.3 对装饰器进行解包装

如果使用了functools.wraps则可以使用__wrapped__属性
对于staticmethod和classmethod使用__func__属性

9.4 定义一个可接受参数的装饰器

多加一层函数

9.5 定义一个属性可由用户修改的装饰器

  1. @logged(level_, msg)
  2. def add(a, b): return a + b
  3. add.set_level(level_2)
  4. add.set_msg(new_msg)
  5. def attach_wrapper(obj, fun=None):
  6. if fun is None:
  7. return partial(attach_wrapper, obj)
  8. setattr(obj, fun.__name__, fun)
  9. return fun
  10. def logged(level, msg):
  11. def decord(fun):
  12. @wraps(fun)
  13. def wrapper(*args, **kwargs):
  14. fun(...)
  15. @attach_wrapper(wrapper)
  16. def set_level(level):
  17. nplocal level
  18. level = _level
  19. return wraps
  20. return decord

如果使用wraps则属性可以依次传递,对于 @logged @timethis 和 @timethis @logged 都可以起作用,而如果采用将属性赋值到函数上的方法对于上面两种方式一种会有问题

9.6 定义一个能接受可选参数的装饰器

  1. def logged(fun=None, *, level=DEBUG, msg=''):
  2. if fun is None:
  3. return partial(logged, level=level, msg=msg)
  4. ...
  5. @logged # 也可以采用@logged()
  6. def a() ...
  7. @logged(level=INFO, ...)
  8. def b() ...

9.7 利用装饰器对函数参数强制执行类型检查

  1. def asserttype(*o_args, **o_kwargs):
  2. def dec(fun):
  3. def wrap(*args, **kwargs):
  4. for p, t in zip(args, o_args):
  5. if not isinstance(p, t):
  6. raise ...
  7. return wrap
  8. return dec
  9. ## 使用signature简化处理
  10. def dec(fun):
  11. if not __debug__:
  12. return fun
  13. sig = inspect.signature(fun)
  14. bound_types = sig.bind_partial(*o_args, **o_kwargs).argments
  15. @wraps(fun)
  16. def wrapper(*args, **kwargs):
  17. bound_values = sig.bind(*args, **kwargs)
  18. for name, value in bound_values.argments.items():
  19. if name in bound_types:
  20. ...
  21. fun(args, kwargs)

可以使用注解提取类型,但是这样一个函数就只能有一种类型,不灵活

9.8 在类中定义装饰器

内建的@property也是拥有getter、setter、deleter方法的类

todo

10 模块和包

10.1 把模块按层次结构组织成包

使用__init__来定义包
__init__中可以相关模块

10.2 对所有符号的导入进行精确控制

对from modelu import *进行控制
在模块文件(a.py)、包文件(__init__.py)中定义 __all__ = ['spam', 'z'...]

10.3 使用相对名称来导入包中的子模块

  1. from . import grok
  2. from ..B import bar
  3. from mypackage.A import grok # 使用完整绝对名称
  4. # 位于脚本顶层目录的模块不能使用相对导入
  5. # 直接以脚本执行,不能使用相对导入,需要使用-m模块模式执行

10.4 将模块分解成多个文件

可以将模块转换为包,在其__init__中将包中其他模块的符号导入,外界的调用代码是不需要修改的
在__init__定义函数来动态导入其他模块内的符号,惰性导入

  1. ## a.py
  2. class A:
  3. ...
  4. ## __init\__.py
  5. def A():
  6. from .a import A
  7. return A() # 未更改调用代码
  8. # 会破坏继承和类型检查机制 isinstance(x, module.A) 出错了

10.5 让各个目录下的代码在统一的命名空间下导入

命名空间包:用于不同目录下的代码,使其位于统一命名空间下管理

  1. #
  2. foo-package/spam/blah.py # 无init文件
  3. bar-package/spam/grok.py # 无init文件
  4. #
  5. import sys
  6. sys.path.extend(['foo-package', 'bar-package'])
  7. import spam.blah
  8. import spam.grok
  9. #
  10. import spam
  11. spam.__path__ # 包含相应的文件夹路径
  12. spam.__file__ # 出错,命名空间包无该属性

10.6 重新加载模块

  1. import A
  2. import imp
  3. imp.reload(A) # from A import 的变量不会被更像

10.7 让目录或zip文件成为可运行的脚本

在目录或者zip文件中添加一个 __main__.py 文件

10.8 读取包中的数据文件

根据解释器的当前目录不一样,其读取路径也会不一样
通过 __file__ 全路径去找对应的路径,比较麻烦,包也可能为.zip或.egg格式

  1. import pkgutil
  2. data = pkgutil.get_data(__package\__, file_name)

10.9 添加目录到sys.path中

10.10 使用字符串中给定的名字来导入模块

使用importlib.import_module(_name)

10.11 利用import钩子从远端机器上加载模块

  1. source = u.read().decode('utf8')
  2. mod = sys.modules.setdefault(url, imp.new_module(url))
  3. code = compile(source, url, 'exec')
  4. mod.__file__ = url
  5. mod.__package__ = ''
  6. exec(code, mod.__dict__)

todo

11 网络和Web编程

11.1 以客户端的形式同HTTP服务交互

使用urllib.request.urlopen(url, querystring, headers=headers)、urllib.parse.urlencode(parms)
使用requests库,resp.text/content/json/status_code/headers['content-type']/cookies

11.2 创建一个TCP服务器

使用socketserver库,为单线程

  1. class EchoHandler(BaseRequestHandler):
  2. def handle(self):
  3. while True:
  4. msg = self.request.recv(8192)
  5. if not msg
  6. break
  7. self.request.send(msg)
  8. serv = TCPServer(('', 20000), EchoHandler)
  9. serv.serve_forever()
  10. # 还可以使用SteamRequestHandler

需要多线程,需要使用ForkingTCPServer,ThreadingTCPServer
使用线程池

  1. serve = TCPServer(('', 2000), EchoHnadler)
  2. for n in range(NWORKERS):
  3. t = Thread(target=serve.serve_forever)
  4. t.daemon = True
  5. t.start()
  6. serve.serve_forever()

如果想通过设定socket选项来调整底层socket的行为,可以设置bind_and_activate=False参数

  1. # 方法 1
  2. serve = TCPServer(.., bind_and_activate=False)
  3. # 方法 2
  4. TCPServer.allow_reuse_address = True
  5. server = TCPServer(...)

还可以通过StreamRequesthandler的子类来实现
直接通过socket来编写服务器也是很容易的

11.3 创建一个UDP服务器

与TCP类似

  1. class TimeHandler(BaseRequestHandler):
  2. def handler(self):
  3. msg, sock = self.request
  4. resp = time.ctine()
  5. sock.sendto(resp.encode('ascii'), self.client_address)

11.4 从CIDR地址中生成IP地址的范围

利用ipaddress模块

  1. net = ipaddress.ip_network('123.45.67.64/27')
  2. for a in net: ...
  3. net[-1]

11.5 创建基于REST风格的简单接口

WSGI

11.6 利用XML-RPC实现简单的远端过程调用

11.7 在不同的解释器间进行通信

使用multiprocessing.connection,send、recv消息是完整的
可以将Listener直接改为unix socket对象
适用于长连接,而不是短连接

11.8 实现远端过程调用

todo

12 并发

12.1 启动和停止线程

12.2 判断线程是否已经启动

12.3 线程间通信

  1. # 使用heapq、Condition来实现优先级队列
  2. # put
  3. with self._cv:
  4. heapq.heappush(self._queue, (-priority, self._count, item)
  5. self._count += 1
  6. self._cv.notify()
  7. # get
  8. with self._cv:
  9. while len(self._queue) == 0:
  10. self._cv.wait()
  11. return heapq.heappop(self._queue)[-1]

12.4 对临界区加锁

  1. class SomeClass:
  2. _lock = threading.RLock()
  3. def fun_a(self):
  4. with SomeClass._lock:
  5. pass
  6. def fun_b(self):
  7. with SomeClass._lock:
  8. pass

12.5 避免死锁

  1. @contextmanager
  2. def acquire(*locks):
  3. locks = sorted(locks, key=lambda x:id(x))
  4. acauared = getattr(_local, 'acquired', [])
  5. # 比较锁
  6. acquired.extend(locks)
  7. _local.acquired = acqured
  8. try:
  9. for lock in locks:
  10. lock.acqure()
  11. yield
  12. finally:
  13. for lock in reversed(locks):
  14. lock.release()
  15. del acquired[-len(locks):]

12.6 保存线程专有状态

使用 threading.local(),针对一个对象可以有多份数据

  1. class Conn:
  2. def __init__(self):
  3. self.x = x
  4. self.local = threading.local()
  5. def do_something(self):
  6. if hasattr(self.local, 'sock'):
  7. raise
  8. self.local.sock = xxx
  9. conn = Conn()
  10. t1 = threading.Thread(target=test, args=(conn,)
  11. t1 = threading.Thread(target=test, args=(conn,)

12.7 创建线程池

  1. a = pool.submit(fun, *args)
  2. a.result()

12.8 实现简单的并行编程

concurrent.futures.ProcessPoolExecutor

  1. with concurrent.futures.ProcessPoolExecutor() as pool:
  2. for reobits in pool.map(func, args): # 也可以使用submit进行单个任务提交
  3. pass
  4. # with语句完进程池会关闭,程序会一直等待所有提交的任务都处理完毕

12.9 如何规避GIL带来的限制

12.10 定义一个Actor任务

actor任务之间的通信是单向且异步的,没有明确的的响应和确认
使用Queue和多线程定义actor

也可以用生成器来定义简单的actor

消息传递可以加tag,然后可以使用python的动态获取属性进行处理

  1. tag, *payload = self.recv()
  2. getattr(self, 'do_'+tag)(*payload)

使用工作者线程执行任意函数

  1. class Result:
  2. def __init__(self):
  3. self._event = Event()
  4. self._result = None
  5. def set_result(self, value):
  6. self._result = value
  7. self._event.set()
  8. def result(self):
  9. self._event.wait()
  10. return self._result
  11. class Worker(Actor):
  12. def submit(self, fun, *args, **kwargs):
  13. r = Result()
  14. self.send((fun, args, kwargs, r))
  15. def run(self):
  16. while True:
  17. func, args, kwargs, r = self.recv()
  18. r.set_result(func(*args, **kwargs))

12.11 实现发布者/订阅者消息模式

引入单独的“交换”或“网关”对象,作为所有消息的中介。

  1. class Exchange:
  2. def attach(self, task):
  3. self._subscribers.add(task)
  4. def deattach(self, task):
  5. ...
  6. def send(self, msg):
  7. for subcriber in self._subscribers:
  8. subcriber.send(msg)
  9. _exchanges = defaultdict(Exchange)
  10. def get_exchange(name):
  11. return _exchanges[name]
  1. exc = get_exchange('name')
  2. with exc.subscribe(task_a, task_b):
  3. exc.send(...)

12.12 使用生成器作为线程的替代方案

添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注