我的笔记,基本都摘抄自 Python3 官方文档html
Java 使用 try 来自动管理资源,只要实现了 AutoCloseable 接口,就能够部分摆脱手动 colse 的地狱了。
而 Python,则是定义了两个 Protocol:__enter__
和 __exit__
. 下面是一个 open 的模拟实现:python
class OpenContext(object): def __init__(self, filename, mode): # 调用 open(filename, mode) 返回一个实例 self.fp = open(filename, mode) def __enter__(self): # 用 with 管理 __init__ 返回的实例时,with 会自动调用这个方法 return self.fp # 退出 with 代码块时,会自动调用这个方法。 def __exit__(self, exc_type, exc_value, traceback): self.fp.close() # 这里先构造了 OpenContext 实例,而后用 with 管理该实例 with OpenContext('/tmp/a', 'a') as f: f.write('hello world')
这里惟一有点复杂的,就是 __exit__
方法。和 Java 同样,__exit__
至关于 try - catch - finally
的 finally
代码块,在发生异常时,它也会被调用。正则表达式
当没有异常发生时,__exit__
的三个参数 exc_type, exc_value, traceback
都为 None,而当发生异常时,它们就对应异常的详细信息。
发生异常时,** __exit__
的返回值将被用于决定是否向外层抛出该异常**,返回 True 则抛出,返回 False 则抑制(swallow it)。redis
Note 1:Python 3.6 提供了 async with 异步上下文管理器,它的 Protocol 和同步的 with 彻底相似,是 __aenter__
和 __aexit__
两个方法。
Note 2:与 Java 相同,with 支持同时管理多个资源,所以能够直接写 with open(x) as a, open(y) as b:
这样的形式。shell
对于简单的 with 资源管理,编写一个类可能会显得比较繁琐,为此 contextlib 提供了一个方便的装饰器 @contextlib.contextmanager
用来简化代码。编程
使用它,上面的 OpenContext 能够改写成这样:缓存
from contextlib import contextmanager @contextmanager def make_open_context(filename, mode): fp = open(filename, mode) try: yield fp # 没错,这是一个生成器函数 finally: fp.close() with make_open_context('/tmp/a', 'a') as f: f.write('hello world')
使用 contextmanager
装饰一个生成器函数,yield 以前的代码对应 __enter__
,finally 代码块就对应 __exit__
.安全
Note:一样,也有异步版本的装饰器 @contextlib.asynccontextmanager
数据结构
用于将本来不支持 with 管理的资源,包装成一个 Context 对象。app
from contextlib import closing from urllib.request import urlopen with closing(urlopen('http://www.python.org')) as page: for line in page: print(line) # closing 等同于 from contextlib import contextmanager @contextmanager def closing(thing): try: yield thing finally: thing.close() # 就是添加了一个自动 close 的功能
使 with 管理器抑制代码块内任何被指定的异常:
from contextlib import suppress with suppress(FileNotFoundError): os.remove('somefile.tmp') # 等同于 try: os.remove('somefile.tmp') except FileNotFoundError: pass
将 with 代码块内的 stdout 重定向到指定的 target(可用于收集 stdout 的输出)
f = io.StringIO() with redirect_stdout(f): # 将输出直接写入到 StringIO help(pow) s = f.getvalue() # 或者直接写入到文件 with open('help.txt', 'w') as f: with redirect_stdout(f): help(pow)
redirect_stdout 函数返回的 Context 是可重入的( reentrant),能够重复使用。
提供了 OS 无关的文件路径抽象,能够彻底替代 os.path
和 glob
.
基本上,pathlib.Path
就是你须要了解的全部内容。
from pathlib import Path data_folder = Path("./source_data/text_files/") data_file = data_folder / "raw_data.txt" # Path 重载了 / 操做符,路径拼接超级方便 # 路径的解析 data_file.parent # 获取父路径,这里的结果就是 data_folder data_foler.parent # 会返回 Path("source_data") data_file.parents[1] # 即获取到 data_file 的上上层目录,结果和上面同样是 Path("source_data") data_file.parents[2] # 上上上层目录,Path(".") dara_file.name # 文件名 "raw_data.txt" dara_file.suffix # 文件的后缀(最末尾的)".txt",还可用 suffixes 获取全部后缀 data_file.stem # 去除掉最末尾的后缀后(只去除一个),剩下的文件名:raw_data # 替换文件名或者文件后缀 data_file.with_name("test.txt") # 变成 .../test.txt data_file.with_suffix(".pdf") # 变成 .../raw_data.pdf # 当前路径与另外一路径 的相对路径 data_file.relative_to(data_folder) # PosixPath('raw_data.txt')
if not data_folder.exist(): data_folder.mkdir(parents=True) # 直接建立文件夹,若是父文件夹不存在,也自动建立 if not filename.exists(): # 文件是否存在 filename.touch() # 直接建立空文件,或者用 filename.open() 直接获取文件句柄 # 路径类型判断 if data_file.is_file(): # 是文件 print(data_file, "is a file") elif data_file.is_dir(): # 是文件夹 for child in p.iterdir(): # 经过 Path.iterdir() 迭代文件夹中的内容 print(child) # 路径解析 filename.resolve() # 获取文件的绝对路径(符号连接也会被解析到真正的文件) # 能够直接获取 Home 路径或者当前路径 Path.home() / "file.txt" # 有时须要以 home 为 base path 来构建文件路径 Path.cwd() / "file.txt" # 或者基于当前路径构建
还有不少其它的实用函数,可在使用中慢慢探索。
pathlib 也提供了 glob 支持,也就是普遍用在路径匹配上的一种简化正则表达式。
data_file.match(glob_pattern) # 返回 True 或 False,表示文件路径与给出的 glob pattern 是否匹配 for py_file in data_folder.glob("*/*.py"): # 匹配当前路径下的子文件夹中的 py 文件,会返回一个可迭代对象 print(py_file) # 反向匹配,至关于 glob 模式开头添加 "**/" for py_file in data_folder.glob("*/*.py"): # 匹配当前路径下的全部 py 文件(全部子文件夹也会被搜索),返回一个可迭代对象 print(py_file)
glob 中的 * 表示任意字符,而 ** 则表示任意层目录。(在大型文件树上使用 ** 速度会很慢!)
functools 提供了几个有时颇有用的函数和装饰器
这个装饰器用于使装饰器 copy 被装饰的对象的 __module__
, __name__
, __qualname__
, __annotations__
and __doc__
属性,这样装饰器就显得更加透明。
from functools import wraps def my_decorator(f): @wraps(f) def wrapper(*args, **kwds): print('Calling decorated function') return f(*args, **kwds) return wrapper # 用了 wraps,wrapper 会复制 f 的各类文档属性 @my_decorator def func(xx): """ this is func's docstring""" print("this is func~")
若是不用 wraps 的话,由于实际上返回的是 wrapper,被装饰对象的这些文档属性都会丢失。(好比 docstring)
所以在使用 wrapper 装饰器时,添加 @wraps() 装饰器是个好习惯。
这个感受和高等数学的偏函数很像:好比函数 z = f(x, y) 有 x 和 y 两个变量,如今把 x 看做常数,就能够对 y 进行求导运算。
而 python 的 partial 也差很少,不过它不是把 x 看做常数,而是先给定 x 的值。用法以下:
from functools import partial basetwo = partial(int, base=2) # 先给定 int 函数的 base 参数为 2 basetwo.__doc__ = 'Convert base 2 string to an int.' # 若是须要文档,能够添加 __doc__ 属性 basetwo('10010') # return 18
此外,还有个 partialmethod 函数,待了解
若是某方法可能被频繁调用(使用相同的参数),并且它的结果在必定时间内不会改变。能够用 lru_cache 装饰它,减小运算量或 IO 操做。
from functools import lru_cache # 缓存最近的(least recently used,lru) 64 次参数不一样的调用结果。 @lru_cache(maxsize=64) def my_sum(x): # 后续的调用中,若是参数能匹配到缓存,就直接返回缓存结果 return sum(x)
好比用递归计算斐波那契数列,数值较低的参数会被频繁使用,因而能够用 lru_cache 来缓存它们。
或者爬取网页,可能会须要频繁爬取一个变化不快的网页,这时彻底能够用 cache 缓存。
可是它不能控制缓存失效时间,所以不能用于 Web 系统的缓存。仍是得本身写个简单的装饰器,把缓存存到 redis 里并设置 expires。或者直接用 Flask 或 Django 的 caching 插件。
单重派发,即根据函数的第一个参数的类型,来决定调用哪个同名函数。
@singledispatch def parse(arg): # 首先定义一个默认函数 print('没有合适的类型被调用') # 若是参数类型没有匹配上,就调用这个默认函数 @parse.register(type(None)) # 第一个参数为 None def _(arg): print('出现 None 了') @parse.register(int) # 第一个参数为整数 def _(arg): print('此次输入的是整数') @parse.register def _(arg: list): # python3.7 开始,能够直接用类型注解来标注第一个参数的类型 print('此次输入的是列表')
画外:有单重派发,天然就有多重派发,Julia 语言就支持多重派发,即根据函数全部参数的类型,来决定调用哪个同名函数。
Julia 语言根本没有类这个定义,类型的全部方法都是经过多重派发来定义的。
operator 模块包含四种类型的方法:
常常被用于 sorted/max/mix/itertools.groupby 等
使用方法:
# itemgetter f = itemgetter(2) f(r) # return r[2] # 还能一次获取多个值,像 numpy 那样索引 f2 = itemgetter(2,4,5) f2(r) # return (r[2], r[4], r[5]) # 或者使用 slice 切片 s = itemgetter(slice(2, None)) s[r] # return r[2:] # dict 索引也能用 d = itemgetter('rank', 'name') d[r] # return d['rank'], d['name']
用途:
# 用于指定用于比较大小的属性 key = itemgetter(1) sorted(iterable, key=key) # 使用 iterable[1] 对 iterable 进行排序 max(iterable, key=key) # 找出最大的元素,使用 iterable[1] 作比较 # 用于高级切片(好比像 numpy 那样的,指定只获取某几列) s = itemgetter(1,3,4) matrix = [[0,1,2,3,4], [1,2,3,4,5]] map(s, matrix) # list 后获得 [(1, 3, 4), (2,4,5)]
operator.attrgetter
可用于动态获取对象的属性,与直接用 getattr()
不一样的是,它能够嵌套访问属性。
# 嵌套访问属性 att = attrgetter("a.b.c") att(obj) # return obj.a.b.c # 和 itemgetter 同样,也能够一次获取多个属性 att = attrgetter("a.b.c", "x.y") att(obj) # return (obj.a.b.c, obj.x.y) # 不嵌套的话,用 getattr 就行 getattr(obj, "a") # return obj.a
这里能够回顾一下类的两个魔法函数:
__getattr__
: 当被访问的属性不存在时,这个方法会被调用,它的返回值会成为对象的该属性。
__getattribute__
: 与 __getattr__
惟一的差异在于,访问对象的任何属性,都会直接调用这个方法,无论属性存不存在。可用于调用函数,它和 attrgetter 很像,差异在于 attrgetter 只是返回指定的属性,而 methodcaller 会直接把指定的属性当成函数调用,而后返回结果。
举例
f = methodcaller('name', 'foo', bar=1) f(b) # returns b.name('foo', bar=1)
operator.add、operator.sub、operator.mul、operator.div 等等,函数式编程有时须要用到。
itertools 提供了许多针对可迭代对象的实用函数
方法不少,基本不可能一次全记住。仍是要用到时多查吧。大体记住有提供哪些功能,须要用到时能想起能够查这个模块就行。
>>> from itertools import product >>> r = product([1, 2], [3, 4], [5, 6]) # 重复一次,也就是 (p1, p2, p3) 的笛卡尔积 >>> pprint(list(r)) [(1, 3, 5), (1, 3, 6), (1, 4, 5), (1, 4, 6), (2, 3, 5), (2, 3, 6), (2, 4, 5), (2, 4, 6)] >>> r2 = product([1, 2], [3, 4], [5, 6], repeat=2) # 重复两次,即 (p1, p2, p3, p1, p2, p3) 的笛卡尔积 >>> pprint(list(r2)) [(1, 3, 5, 1, 3, 5), (1, 3, 5, 1, 3, 6), (1, 3, 5, 1, 4, 5), (1, 3, 5, 1, 4, 6), (1, 3, 5, 2, 3, 5), ...
等等等,用获得的时候再查了。。。
提供了一些实用的高级数据结构(容器)