作者 | 写代码的明哥
源自 | Python编程时光
怎样在运行状态查看源代码
查看函数的源代码,咱们一般会运用 IDE 来完成。
例如在 PyCharm 中,你能够 Ctrl +鼠标点击进入函数的源代码。
那倘若无 IDE 呢?
当咱们想运用一个函数时,怎样晓得这个函数必须接收那些参数呢?
当咱们在运用函数时显现问题的时候,怎样经过阅读源代码来排查问题所在呢?
此时候,咱们能够运用 inspect 来代替 IDE 帮忙你完成这些事:
# demo.py
import inspect
def add(x, y):
return x + y
print("===================") print(inspect.getsource(add))运行结果如下:
$ python demo.py
===================
def add(x, y): return x + y
怎样关闭反常自动相关上下文
当你在处理反常时,因为处理欠妥或其他问题,再次抛出另一个反常时,往外抛出的反常亦会携带原始的反常信息。
就像这般子:
try:
print(1 / 0)
except Exception as exc: raise RuntimeError("Something bad happened")从输出能够看到两个反常信息:
Traceback (most recent call last):
File "demo.py", line 2, in
print(1 / 0)
ZeroDivisionError: division by zero
During handling of the above exception, another exception occurred:
Traceback (most recent call last):
File "demo.py", line 4, in
raise RuntimeError("Something bad happened") RuntimeError: Something bad happened倘若在反常处理程序或 finally 块中诱发反常,默认状况下,反常机制会隐式工作会将先前的反常附加为新反常的 __context__属性。这便是 Python 默认开启的自动相关反常上下文。
倘若你想自己掌控这个上下文,能够加个 from 关键字(from 语法会有个限制,便是第二个表达式必要是另一个反常类或实例。)来显示你的新反常是直接由哪个反常导致的。
try:
print(1 / 0)
except Exception as exc: raise RuntimeError("Something bad happened") from exc输出如下:
Traceback (most recent call last):
File "demo.py", line 2, in
print(1 / 0)
ZeroDivisionError: division by zero
The above exception was the direct cause of the following exception:
Traceback (most recent call last):
File "demo.py", line 4, in
raise RuntimeError("Something bad happened") from exc RuntimeError: Something bad happened当然,你亦能够经过 with_traceback 办法为异常设置上下文__context__属性,这亦能在 traceback 更好的表示反常信息。
try:
print(1 / 0)
except Exception as exc: raise RuntimeError("bad thing").with_traceback(exc)最后,倘若我想彻底关闭这个自动相关反常上下文的机制?有什么办法呢?
能够运用 raise...from None,从下面的例子上看,已然无了原始反常。
$ cat demo.py
try:
print(1 / 0)
except Exception as exc:
raise RuntimeError("Something bad happened") from None
$
$ python demo.py
Traceback (most recent call last):
File "demo.py", line 4, in
raise RuntimeError("Something bad happened") from None
RuntimeError: Something bad happened (PythonCodingTime)
最快查看包搜索路径的方式
当你运用 import 导入一个包或模块时,Python 会去有些目录下查询,而这些目录是有优先级次序的,正一般人会运用 sys.path 查看。
>>> import sys
>>> from pprint import pprint
>>> pprint(sys.path)
[,
/usr/local/Python3.7/lib/python37.zip,
/usr/local/Python3.7/lib/python3.7,
/usr/local/Python3.7/lib/python3.7/lib-dynload,
/home/wangbm/.local/lib/python3.7/site-packages,
/usr/local/Python3.7/lib/python3.7/site-packages] >>>那有无更快的方式呢?
我这有一种连console 模式都不消进入的办法呢!
你可能会想到这种,但这本质上与上面并无区别:
[wangbm@localhost ~]$ python -c "print(\n.join(__import__(sys).path))"
/usr/lib/python2.7/site-packages/pip-18.1-py2.7.egg
/usr/lib/python2.7/site-packages/redis-3.0.1-py2.7.egg
/usr/lib64/python27.zip
/usr/lib64/python2.7
/usr/lib64/python2.7/plat-linux2
/usr/lib64/python2.7/lib-tk
/usr/lib64/python2.7/lib-old
/usr/lib64/python2.7/lib-dynload
/home/wangbm/.local/lib/python2.7/site-packages
/usr/lib64/python2.7/site-packages
/usr/lib64/python2.7/site-packages/gtk-2.0 /usr/lib/python2.7/site-packages这儿我要介绍的是比上面两种都方便得多的办法,一行命令就可处理。
[wangbm@localhost ~]$ python3 -m site
sys.path = [
/home/wangbm,
/usr/local/Python3.7/lib/python37.zip,
/usr/local/Python3.7/lib/python3.7,
/usr/local/Python3.7/lib/python3.7/lib-dynload,
/home/wangbm/.local/lib/python3.7/site-packages,
/usr/local/Python3.7/lib/python3.7/site-packages,
]
USER_BASE: /home/wangbm/.local (exists)
USER_SITE: /home/wangbm/.local/lib/python3.7/site-packages (exists) ENABLE_USER_SITE: True从输出你能够发掘,这个列的路径会比 sys.path 更全,它包括了用户环境的目录。
将嵌套 for 循环写成单行
咱们经常会如下这种嵌套的 for 循环代码:
list1 = range(1,3)
list2 = range(4,6)
list3 = range(7,9)
for item1 in list1:
for item2 in list2:
for item3 in list3: print(item1+item2+item3)这儿仅仅是三个 for 循环,在实质编码中,有可能会有更层。
这般的代码,可读性非常的差,非常多人不想这么写,可又无更好的写法。
这儿介绍一种我常用的写法,运用 itertools 这个库来实现更优雅易读的代码。
from itertools import product
list1 = range(1,3)
list2 = range(4,6)
list3 = range(7,9)
for item1,item2,item3 in product(list1, list2, list3): print(item1+item2+item3)输出如下:
$ python demo.py
12
13
13
14
13
14
14 15
怎样运用 print 输出日志
初学者爱好运用 print 来调试代码,并记录程序运行过程。
然则 print 只会将内容输出到终端上,不可持久化到日志文件中,并有害于问题的排查。
倘若你热衷于运用 print 来调试代码(虽然这并不是最佳做法),记录程序运行过程,那样下面介绍的这个 print 用法,可能会对你有用。
Python 3 中的 print 做为一个函数,因为能够接收更加多的参数,因此功能变为更加强大,指定有些参数能够将 print 的内容输出到日志文件中。
代码如下:
>>> with open(test.log, mode=w) as f:
... print(hello, python, file=f, flush=True)
>>> exit
$ cat test.log hello, python
怎样快速计算函数运行时间
计算一个函数的运行时间,你可能会这般子做:
import time
start = time.time
# run the function
end = time.time print(end-start)你瞧瞧你为了计算函数运行时间,写了几行代码了。
有无一种办法能够更方便的计算这个运行时间呢?
有。
有一个内置模块叫 timeit,运用它,只用一行代码就可:
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
利用自带的缓存机制提有效率
缓存是一种将定量数据加以保留,以备迎合后续获取需求的处理方式,旨在加快数据获取的速度。
数据的生成过程可能必须经过计算,规整,远程获取等操作,倘若是同一份数据必须多次运用,每次都重新生成会大大浪费时间。因此,倘若将计算或远程请求等操作得到的数据缓存下来,会加快后续的数据获取需求。
为了实现这个需求,Python 3.2 + 中给咱们供给了一个机制,能够很方便的实现,而不必须你去写这般的规律代码。
这个机制实现于 functool 模块中的 lru_cache 装饰器。
@functools.lru_cache(maxsize=None, typed=False)
参数诠释:
maxsize:最多能够缓存多少个此函数的调用结果,倘若为None,则无限制,设置为 2 的幂时,性能最佳;
typed:若为 True,则区别参数类型的调用将分别缓存。
举个例子:
from functools import lru_cache
@lru_cache(None)
def add(x, y):
print("calculating: %s + %s" % (x, y))
return x + y
print(add(1, 2))
print(add(1, 2)) print(add(2, 3))输出如下,能够看到第二次调用并无真正的执行函数体,而是直接返回缓存里的结果:
calculating: 1 + 2
3
3
calculating: 2 + 3 5下面这个是经典的斐波那契数列,当你指定的 n 很强时,会存在海量的重复计算:
def fib(n):
if n < 2:
return n return fib(n - 2) + fib(n - 1)第六点介绍的 timeit,此刻能够用它来测试一下到底能够加强多少的效率。
不运用 lru_cache 的状况下,运行时间 31 秒。
import timeit
def fib(n):
if n < 2:
return n
return fib(n - 2) + fib(n - 1)
print(timeit.timeit(lambda :fib(40), number=1)) # output: 31.2725698948因为运用了 lru_cache 后,运行速度实在太快了,因此我将 n 值由 30 调到 500,可即使是这般,运行时间亦才 0.0004 秒。加强速度非常明显。
import timeit
from functools import lru_cache
@lru_cache(None)
def fib(n):
if n < 2:
return n
return fib(n - 2) + fib(n - 1)
print(timeit.timeit(lambda :fib(500), number=1)) # output: 0.0004921059880871326
在程序退出前执行代码的技巧
运用 atexit 这个内置模块,能够很方便的注册退出函数。
不管你在哪个地区引起程序崩溃,都会执行哪些你注册过的函数。
示例如下:
倘若clean函数有参数,那样你能够不消装饰器,而是直接调用atexit.register(clean_1, 参数1, 参数2, 参数3=xxx)。
可能你有其他办法能够处理这种需求,但肯定比上不运用 atexit 来得优雅,来得方便,并且它很容易扩展。
然则运用atexit 仍然有有些局限性,例如:
倘若程序是被你无处理过的系统信号杀死的,那样注册的函数没法正常执行。
倘若出现了严重的 Python 内部错误,你注册的函数没法正常执行。
倘若你手动调用了os._exit,你注册的函数没法正常执行。
实现类似 defer 的延迟调用
在 Golang 中有一种延迟调用的机制,关键字是 defer,例如下面的示例:
import "fmt"
func myfunc {
fmt.Println("B")
}
func main {
defer myfunc
fmt.Println("A") }输出如下,myfunc 的调用会在函数返回前一步完成,即使你将 myfunc 的调用写在函数的第1行,这便是延迟调用。
A B那样在 Python 中否有这种机制呢?
当然亦有,只不外并无 Golang 这种简便。
在 Python 能够运用上下文管理器达到这种效果:
import contextlib
def callback:
print(B)
with contextlib.ExitStack as stack:
stack.callback(callback) print(A)输出如下:
A B
怎样流式读取数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☞架构的腐化是必然的!
☞腾讯云 AI 在新基建行业下一盘什么大棋
☞张一鸣是怎样练就字节跳动的
☞性能超越最新序列举荐模型,华为诺亚方舟提出记忆加强的图神经网络
☞DevOps 在移动应用程序研发中扮演什么角色?
☞稳定币经济:十大稳定币简史
|