Python黑魔法手册 2.0 文档第五章:魔法开发技巧【6-10】
Python黑魔法手册 2.0 文档第五章:魔法开发技巧【6-10】
5.6 如何流式读取数G超大文件
使用 with…open… 可以从一个文件中读取数据,这是所有 Python 开发者都非常熟悉的操作。
但是如果你使用不当,也会带来很大的麻烦。
比如当你使用了 read 函数,其实 Python 会将文件的内容一次性的全部载入内存中,如果文件有 10 个G甚至更多,那么你的电脑就要消耗的内存非常巨大。
# 一次性读取
with open("big_file.txt", "r") as fp:
content = fp.read()
对于这个问题,你也许会想到使用 readline 去做一个生成器来逐行返回。
def read_from_file(filename):
with open(filename, "r") as fp:
yield fp.readline()
可如果这个文件内容就一行呢,一行就 10个G,其实你还是会一次性读取全部内容。
最优雅的解决方法是,在使用 read 方法时,指定每次只读取固定大小的内容,比如下面的代码中,每次只读取 8kb 返回。
def read_from_file(filename, block_size = 1024 * 8):
with open(filename, "r") as fp:
while True:
chunk = fp.read(block_size)
if not chunk:
break
yield chunk
上面的代码,功能上已经没有问题了,但是代码看起来代码还是有些臃肿。借助偏函数 和 iter 函数可以优化一下代码
from functools import partial
def read_from_file(filename, block_size = 1024 * 8):
with open(filename, "r") as fp:
for chunk in iter(partial(fp.read, block_size), ""):
yield chunk
5.7 实现类似 defer 的延迟调用
在 Golang 中有一种延迟调用的机制,关键字是 defer,例如下面的示例
import "fmt"
func myfunc() {
fmt.Println("B")
}
func main() {
defer myfunc()
fmt.Println("A")
}
输出如下,myfunc 的调用会在函数返回前一步完成,即使你将 myfunc 的调用写在函数的第一行,这就是延迟调用。
A
B
那么在 Python 中否有这种机制呢?
当然也有,只不过并没有 Golang 这种简便。
在 Python 可以使用 上下文管理器 达到这种效果
import contextlib
def callback():
print('B')
with contextlib.ExitStack() as stack:
stack.callback(callback)
print('A')
输出如下
A
B
5.8 如何快速计算函数运行时间
计算一个函数的运行时间,你可能会这样子做
import time
start = time.time()
# run the function
end = time.time()
print(end-start)
你看看你为了计算函数运行时间,写了几行代码了。 有没有一种方法可以更方便的计算这个运行时间呢? 有。 有一个内置模块叫
import time
import timeit
def run_sleep(second):
print(second)
time.sleep(second)
# 只用这一行
print(timeit.timeit(lambda :run_sleep(2), number=5))
运行结果如下
2
2
2
2
2
10.020059824
5.9 重定向标准输出到日志
假设你有一个脚本,会执行一些任务,比如说集群健康情况的检查。
检查完成后,会把各服务的的健康状况以 JSON 字符串的形式打印到标准输出。
如果代码有问题,导致异常处理不足,最终检查失败,是很有可能将一些错误异常栈输出到标准错误或标准输出上。
由于最初约定的脚本返回方式是以 JSON 的格式输出,此时你的脚本却输出各种错误异常,异常调用方也无法解析。
如何避免这种情况的发生呢?
我们可以这样做,把你的标准错误输出到日志文件中。
import contextlib
log_file="/var/log/you.log"
def you_task():
pass
@contextlib.contextmanager
def close_stdout():
raw_stdout = sys.stdout
file = open(log_file, 'a+')
sys.stdout = file
yield
sys.stdout = raw_stdout
file.close()
with close_stdout():
you_task()
5.10 快速定位错误进入调试模式
当你在写一个程序时,最初的程序一定遇到不少零零散散的错误,这时候就免不了调试一波。
如果你和我一样,习惯使用 pdb 进行调试的话,一定有所体会,通常我们都要先把pdb.set_trace() 去掉,让程序畅通无阻,直到它把异常抛出来。
出现异常后,再使用 vim 跳转到抛出异常的位置,敲入 import pdb;pdb.set_trace() ,然后再到运行,进入调试模式,找到问题并修改代码后再去掉我们加上的那行 pdb 的代码。
如此反复这样一个过程,直到最后程序没有异常。
你应该能够感受到这个过程有多繁锁,令人崩溃。
接下来介绍一种,可以让你不需要修改源代码,就可以在异常抛出时,快速切换到调试模式,进入 『案发现场』排查问题。
方法很简单,只需要你在执行脚本时,加入 -i 参考
如果你的程序没有任何问题,加上 -i 后又会有什么不一样呢?
从下图可以看出,程序执行完成后会自动进入 console 交互模式。