Python 进阶(一些进阶技巧)
个人笔记,基本都摘抄自 Python3 官方文档
一. 上下文管理
1. 传统的类方式
Java 使用 try 来自动管理资源,只要实现了 AutoCloseable 接口,就可以部分摆脱手动 colse 的地狱了。
而 Python,则是定义了两个 Protocol:__enter__
和 __exit__
. 下面是一个 open 的模拟实现:
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)。
Note 1:Python 3.6 提供了 async with 异步上下文管理器,它的 Protocol 和同步的 with 完全类似,是 __aenter__
和 __aexit__
两个方法。
Note 2:与 Java 相同,with 支持同时管理多个资源,因此可以直接写 with open(x) as a, open(y) as b:
这样的形式。
2. contextlib
2.1 @contextlib.contextmanager
对于简单的 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
2.2 contextlib.closing(thing)
用于将原本不支持 with 管理的资源,包装成一个 Context 对象。
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 的功能
2.3 contextlib.suppress(*exceptions)
使 with 管理器抑制代码块内任何被指定的异常:
from contextlib import suppress
with suppress(FileNotFoundError):
os.remove('somefile.tmp')
# 等同于
try:
os.remove('somefile.tmp')
except FileNotFoundError:
pass
2.4 contextlib.redirect_stdout(new_target)
将 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),可以重复使用。
二、pathlib
提供了 OS 无关的文件路径抽象,可以完全替代 os.path
和 glob
.
学会了 pathlib.Path
,你就会了 Python 处理文件路径的所有功能。
1. 路径解析与拼接
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')
2. 常用的路径操作函数
if not data_folder.exists():
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() # 在不区分大小写的系统上(Windows),这个函数也会将大小写转换成实际的形式。
# 可以直接获取 Home 路径或者当前路径
Path.home() / "file.txt" # 有时需要以 home 为 base path 来构建文件路径
Path.cwd() / "file.txt" # 或者基于当前路径构建
还有很多其它的实用函数,可在使用中慢慢探索。
3. glob
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
functools 提供了几个有时很有用的函数和装饰器
1. @functools.wraps
这个装饰器用于使装饰器 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() 装饰器是个好习惯。
2. functools.partial
这个感觉和高等数学的偏函数很像:比如函数 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 函数,待了解
3. @functools.lru_cache(maxsize=128, typed=False)
如果某方法可能被频繁调用(使用相同的参数),而且它的结果在一定时间内不会改变。可以用 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 插件。
4. @functools.singledispatch
单重派发,即根据函数的第一个参数的类型,来决定调用哪一个同名函数。
@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 语言根本没有类这个定义,类型的所有方法都是通过多重派发来定义的。
其他
- @functools.total_ordering:用于自动生成比较函数。
- functools.cmp_to_key(func):用于将老式的比较函数,转换成新式的 key 函数。
四、operator
operator 模块包含四种类型的方法:
1. operator.itemgetter
经常被用于 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)]
2. 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__
唯一的差别在于,访问对象的任何属性,都会直接调用这个方法,不管属性存不存在。
3. operator.methodcaller
可用于调用函数,它和 attrgetter 很像,差别在于 attrgetter 只是返回指定的属性,而 methodcaller 会直接把指定的属性当成函数调用,然后返回结果。
举例
f = methodcaller('name', 'foo', bar=1)
f(b) # returns b.name('foo', bar=1)
4. 各种操作符对应的函数
operator.add、operator.sub、operator.mul、operator.div 等等,函数式编程有时需要用到。
五、itertools
itertools 提供了许多针对可迭代对象的实用函数
方法很多,基本不可能一次全记住。还是要用到时多查吧。大致记住有提供哪些功能,需要用到时能想起可以查这个模块就行。
1. 无限迭代器
- count(start=0, step=1): 从 start 开始,每次迭代时,返回值都加一个 step
- 默认返回序列为 0 1 2 3...
- cycle(iterable): 不断循环迭代 iterable
- repeat(element, times=None): 默认永远返回 element。(如果 times 不为 None,就迭代 times 后结束)
2. 排列组合迭代器
- product(p1, p2, ..., repeat=1):p1, p2... 的元素的笛卡尔积,相当于多层 for 循环
- repeat 指参数重复次数,比如
>>> 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),
...
- permutations(p[, r]):p 中元素,长度为 r 的所有可能的排列。相当于 product 去重后的结果。
- combinations(p, r):既然有排列,当然就有组合了。
3. 其他
- zip_longest(*iterables, fillvalue=None):和 zip 的差别在于,缺失的元素它会用 fillvalue 补全,而不是直接结束。
- takewhile()
- dropwhile()
- groupby()
等等等,用得到的时候再查了。。。
六、collections
提供了一些实用的高级数据结构(容器)
- defaultdict:这个感觉是最常用的,可以给定 key 的默认值
- Counter:方便、快速的计数器。常用于分类统计
- deque:一个线程安全的双端队列
- OrderedDict:有时候会需要有序字典
- namedtuple:命名元组,有时用于参数传递。与 tuple 的差别是它提供了关键字参数和通过名字访问属性的功能
- ChainMap:将多个 map 连接(chain)在一起,提供一个统一的视图。因为是视图,所以原来的 map 不会被影响。
Python 进阶(一些进阶技巧)的更多相关文章
- Python语言学习之Python入门到进阶
人们常说Python语言简单,编写简单程序时好像也确实如此.但实际上Python绝不简单,它也是一种很复杂的语言,其功能特征非常丰富,能支持多种编程风格,在几乎所有方面都能深度定制.要想用好Pytho ...
- Python学习笔记进阶篇——总览
Python学习笔记——进阶篇[第八周]———进程.线程.协程篇(Socket编程进阶&多线程.多进程) Python学习笔记——进阶篇[第八周]———进程.线程.协程篇(异常处理) Pyth ...
- python基础——面向对象进阶下
python基础--面向对象进阶下 1 __setitem__,__getitem,__delitem__ 把对象操作属性模拟成字典的格式 想对比__getattr__(), __setattr__( ...
- python基础——面向对象进阶
python基础--面向对象进阶 1.isinstance(obj,cls)和issubclass(sub,super) isinstance(obj,cls)检查是否obj是否是类 cls 的对象 ...
- python面向对象编程进阶
python面向对象编程进阶 一.isinstance(obj,cls)和issubclass(sub,super) isinstance(obj,cls)检查是否obj是否是类 cls 的对象 1 ...
- Python基础与进阶
1 Python基础与进阶 欢迎来到Python世界 搭建编程环境 变量 | 字符串 | 注释 | 错误消除 他只用一张图,就把Python中的列表拿下了! 使用 If 语句进行条件测试 使用字典更准 ...
- 【转】python 面向对象(进阶篇)
[转]python 面向对象(进阶篇) 上一篇<Python 面向对象(初级篇)>文章介绍了面向对象基本知识: 面向对象是一种编程方式,此编程方式的实现是基于对 类 和 对象 的使用 类 ...
- 【转】Python之函数进阶
[转]Python之函数进阶 本节内容 上一篇中介绍了Python中函数的定义.函数的调用.函数的参数以及变量的作用域等内容,现在来说下函数的一些高级特性: 递归函数 嵌套函数与闭包 匿名函数 高阶函 ...
- Python的生成器进阶玩法
Python的生成器进阶玩法 作者:尹正杰 版权声明:原创作品,谢绝转载!否则将追究法律责任. 一.yield的表达式形式 #!/usr/bin/env python #_*_coding:utf-8 ...
- Python定制类(进阶6)
转载请标明出处: http://www.cnblogs.com/why168888/p/6411919.html 本文出自:[Edwin博客园] Python定制类(进阶6) 1. python中什么 ...
随机推荐
- CentOS 7安装Oracle (CentOS Linux release 7.5.1804)
从安装操作系统到完成oracle安装 1.安装centos7 下载CentOS7 iso安装包,配置虚拟机,由于只进行oracle安装练习,随便配置20G空间.选择安装文件. 开机,开始安装系统: 直 ...
- Angularjs基础(八)
AngularJS Bootstrap AngularJS 的首选样式表是 Twitter Bootstrap ,Twitter Bootstrap 是目前最受欢迎的前端框架 Bootstrap 你可 ...
- react中图片校验码实现以及new Buffer()使用方法
图片校验码原理就是图片是后端生成的前端只是前后端传过来的数据流做些处理展示即可,先直接上核心代码图: 这里就是简单得对axios的一些默认项属性重写:最后你只需要将resolve的内容插入页面的< ...
- poj_3256_Cow Picnic
The cows are having a picnic! Each of Farmer John's K (1 ≤ K ≤ 100) cows is grazing in one of N (1 ≤ ...
- 用户交互input
input() 函数 接收到的都是str,如果输入为数字,打印结果想进行运算,此时需要转义.语法:内容=input("提示信息")这里可以直接获取到用户输入的内容. a = inp ...
- mongo数据库相关目录
mongodb的docker化安装 mongodb的windows系统下安装 grafana使用Prometheus数据源监控mongo数据库 mongodb副本集的docker化安装 mongodb ...
- 百度app红包? 百度全家桶?果断卸载
听说今年的春晚红包与百度合作.这不 刚又下载了一个百度app,之前下载过,太卡了,用户体验极.本身对百度也没啥好感,再加上这周看了:百度已死的文章,搜索全百家号.具体啥情况,你们百度搜一搜吧
- 【Hbase一】基础
此笔记仅用于作者记录复习使用,如有错误地方欢迎留言指正,作者感激不尽,如有转载请指明出处 Hbase基础 Hbase基础 Hbase定义 行存储 v s 列存储 Hbase数据模型 Hbase物理模型 ...
- pads怎么高亮网络
pads怎么高亮网络 选择完整个网络----再按CTRL+H 就高亮了. 取消高亮是,选择需要取消高亮的整个网络,按 CTRL+U 就取消了. PADS在生成Gerber时过孔盖油设置方法 PADS2 ...
- pyspider -- 禁止请求非200响应码抛异常
在pyspider中若crawl()网址时出现非200的异常信息,会抛出一个异常. 可以在对应的回调函数上面通过@catch_status_code_error 进行修饰,这样就能不抛出异常正常进入回 ...