你们好。python
今天给你们分享 10 个我平时整理很是实用的 Python 开发小技巧,内容目录以下:linux
值得一提的是,这 10 个技巧所有收录在我本身写的 《Python黑魔法指南》里redis
你能够在按照以下方法,后台发送『黑魔法』就能够获取精美排版的 PDF 电子书。shell
查看函数的源代码,咱们一般会使用 IDE 来完成。缓存
好比在 PyCharm 中,你能够 Ctrl + 鼠标点击 进入函数的源代码。app
那若是没有 IDE 呢?python2.7
当咱们想使用一个函数时,如何知道这个函数须要接收哪些参数呢?函数
当咱们在使用函数时出现问题的时候,如何经过阅读源代码来排查问题所在呢?性能
这时候,咱们可使用 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 <module> 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 <module> 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 <module> 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 <module> 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 <module> 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 循环代码
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 用法,可能会对你有用。
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)
参数解读:
举个例子
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 仍然有一些局限性,好比:
os._exit()
,你注册的函数没法正常执行。在 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
使用 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