5年Python功力,总结了10个开发技巧

新闻 后端
查看函数的源代码,我们通常会使用 IDE 来完成。比如在 PyCharm 中,你可以 Ctrl + 鼠标点击 进入函数的源代码。

 [[329607]]

1. 如何在运行状态查看源代码?

查看函数的源代码,我们通常会使用 IDE 来完成。

比如在 PyCharm 中,你可以 Ctrl + 鼠标点击 进入函数的源代码。

那如果没有 IDE 呢?

当我们想使用一个函数时,如何知道这个函数需要接收哪些参数呢?

当我们在使用函数时出现问题的时候,如何通过阅读源代码来排查问题所在呢?

这时候,我们可以使用 inspect 来代替 IDE 帮助你完成这些事。

  1. # demo.py 
  2. import inspect 
  3.  
  4.  
  5. def add(x, y): 
  6. return x + y 
  7.  
  8. print("==================="
  9. print(inspect.getsource(add)) 
  10. 运行结果如下 
  11.  
  12. $ python demo.py 
  13. =================== 
  14. def add(x, y): 
  15. return x + y 

2. 如何关闭异常自动关联上下文?

当你在处理异常时,由于处理不当或者其他问题,再次抛出另一个异常时,往外抛出的异常也会携带原始的异常信息。

就像这样子。

  1. try
  2. print(1 / 0
  3. except Exception as exc: 
  4. raise RuntimeError("Something bad happened"

从输出可以看到两个异常信息

  1. Traceback (most recent call last): 
  2. File "demo.py", line 2, in <module> 
  3. print(1 / 0
  4. ZeroDivisionError: division by zero 
  5.  
  6. During handling of the above exception, another exception occurred: 
  7.  
  8. Traceback (most recent call last): 
  9. File "demo.py", line 4, in <module> 
  10. raise RuntimeError("Something bad happened"
  11. RuntimeError: Something bad happened 

如果在异常处理程序或 finally 块中引发异常,默认情况下,异常机制会隐式工作会将先前的异常附加为新异常的 __context__属性。这就是 Python 默认开启的自动关联异常上下文。如果你想自己控制这个上下文,可以加个 from 关键字(from语法会有个限制,就是第二个表达式必须是另一个异常类或实例。),来表明你的新异常是直接由哪个异常引起的。

  1. try
  2. print(1 / 0
  3. except Exception as exc: 
  4. raise RuntimeError("Something bad happened") from exc 

输出如下

  1. Traceback (most recent call last): 
  2. File "demo.py", line 2, in <module> 
  3. print(1 / 0
  4. ZeroDivisionError: division by zero 
  5.  
  6. The above exception was the direct cause of the following exception: 
  7.  
  8. Traceback (most recent call last): 
  9. File "demo.py", line 4, in <module> 
  10. raise RuntimeError("Something bad happened") from exc 
  11. RuntimeError: Something bad happened 

当然,你也可以通过with_traceback方法为异常设置上下文__context__属性,这也能在traceback更好的显示异常信息。

  1. try
  2. print(1 / 0
  3. except Exception as exc: 
  4. raise RuntimeError("bad thing").with_traceback(exc) 

最后,如果我想彻底关闭这个自动关联异常上下文的机制?有什么办法呢?

可以使用 raise...from None,从下面的例子上看,已经没有了原始异常

  1. $ cat demo.py 
  2. try
  3. print(1 / 0
  4. except Exception as exc: 
  5. raise RuntimeError("Something bad happened") from None 
  6. $ python demo.py 
  7. Traceback (most recent call last): 
  8. File "demo.py", line 4, in <module> 
  9. raise RuntimeError("Something bad happened") from None 
  10. RuntimeError: Something bad happened 
  11. (PythonCodingTime) 

3. 最快查看包搜索路径的方式

当你使用 import 导入一个包或模块时,Python 会去一些目录下查找,而这些目录是有优先级顺序的,正常人会使用 sys.path 查看。

  1. >>> import sys 
  2. >>> from pprint import pprint 
  3. >>> pprint(sys.path) 
  4. [''
  5. '/usr/local/Python3.7/lib/python37.zip'
  6. '/usr/local/Python3.7/lib/python3.7'
  7. '/usr/local/Python3.7/lib/python3.7/lib-dynload'
  8. '/home/wangbm/.local/lib/python3.7/site-packages'
  9. '/usr/local/Python3.7/lib/python3.7/site-packages'
  10. >>> 

那有没有更快的方式呢?

我这有一种连 console 模式都不用进入的方法呢?

你可能会想到这种,但这本质上与上面并无区别

  1. [wangbm@localhost ~]$ python -c "print('\n'.join(__import__('sys').path))" 
  2.  
  3. /usr/lib/python2.7/site-packages/pip-18.1-py2.7.egg 
  4. /usr/lib/python2.7/site-packages/redis-3.0.1-py2.7.egg 
  5. /usr/lib64/python27.zip 
  6. /usr/lib64/python2.7 
  7. /usr/lib64/python2.7/plat-linux2 
  8. /usr/lib64/python2.7/lib-tk 
  9. /usr/lib64/python2.7/lib-old 
  10. /usr/lib64/python2.7/lib-dynload 
  11. /home/wangbm/.local/lib/python2.7/site-packages 
  12. /usr/lib64/python2.7/site-packages 
  13. /usr/lib64/python2.7/site-packages/gtk-2.0 
  14. /usr/lib/python2.7/site-packages 

这里我要介绍的是比上面两种都方便的多的方法,一行命令即可解决

  1. [wangbm@localhost ~]$ python3 -m site 
  2. sys.path = [ 
  3. '/home/wangbm'
  4. '/usr/local/Python3.7/lib/python37.zip'
  5. '/usr/local/Python3.7/lib/python3.7'
  6. '/usr/local/Python3.7/lib/python3.7/lib-dynload'
  7. '/home/wangbm/.local/lib/python3.7/site-packages'
  8. '/usr/local/Python3.7/lib/python3.7/site-packages'
  9. USER_BASE: '/home/wangbm/.local' (exists) 
  10. USER_SITE: '/home/wangbm/.local/lib/python3.7/site-packages' (exists) 
  11. ENABLE_USER_SITE: True 

从输出你可以发现,这个列的路径会比 sys.path 更全,它包含了用户环境的目录。

4. 将嵌套 for 循环写成单行

我们经常会如下这种嵌套的 for 循环代码

  1. list1 = range(1,3
  2. list2 = range(4,6
  3. list3 = range(7,9
  4. for item1 in list1: 
  5. for item2 in list2: 
  6. for item3 in list3: 
  7. print(item1+item2+item3) 

这里仅仅是三个 for 循环,在实际编码中,有可能会有更层。

这样的代码,可读性非常的差,很多人不想这么写,可又没有更好的写法。

这里介绍一种我常用的写法,使用 itertools 这个库来实现更优雅易读的代码。

  1. from itertools import product 
  2. list1 = range(1,3
  3. list2 = range(4,6
  4. list3 = range(7,9
  5. for item1,item2,item3 in product(list1, list2, list3): 
  6. print(item1+item2+item3) 

输出如下

  1. $ python demo.py 
  2. 12 
  3. 13 
  4. 13 
  5. 14 
  6. 13 
  7. 14 
  8. 14 
  9. 15 

5. 如何使用 print 输出日志

初学者喜欢使用 print 来调试代码,并记录程序运行过程。

但是 print 只会将内容输出到终端上,不能持久化到日志文件中,并不利于问题的排查。

如果你热衷于使用 print 来调试代码(虽然这并不是最佳做法),记录程序运行过程,那么下面介绍的这个 print 用法,可能会对你有用。

Python 3 中的 print 作为一个函数,由于可以接收更多的参数,所以功能变为更加强大,指定一些参数可以将 print 的内容输出到日志文件中

代码如下:

  1. >>> with open('test.log', mode='w') as f: 
  2. ... print('hello, python', file=f, flush=True) 
  3. >>> exit 
  4.  
  5. $ cat test.log 
  6. hello, python 

6. 如何快速计算函数运行时间

计算一个函数的运行时间,你可能会这样子做

  1. import time 
  2.  
  3. start = time.time 
  4.  
  5. # run the function 
  6.  
  7. end = time.time 
  8. print(end-start) 

你看看你为了计算函数运行时间,写了几行代码了。

有没有一种方法可以更方便的计算这个运行时间呢?

有。

有一个内置模块叫 timeit

使用它,只用一行代码即可

  1. import time 
  2. import timeit 
  3.  
  4. def run_sleep(second): 
  5. print(second) 
  6. time.sleep(second) 
  7.  
  8. # 只用这一行 
  9. print(timeit.timeit(lambda :run_sleep(2), number=5)) 

运行结果如下

  1. 2 
  2. 2 
  3. 2 
  4. 2 
  5. 2 
  6. 10.020059824 

7. 利用自带的缓存机制提高效率

缓存是一种将定量数据加以保存,以备迎合后续获取需求的处理方式,旨在加快数据获取的速度。

数据的生成过程可能需要经过计算,规整,远程获取等操作,如果是同一份数据需要多次使用,每次都重新生成会大大浪费时间。所以,如果将计算或者远程请求等操作获得的数据缓存下来,会加快后续的数据获取需求。

为了实现这个需求,Python 3.2 + 中给我们提供了一个机制,可以很方便的实现,而不需要你去写这样的逻辑代码。

这个机制实现于 functool 模块中的 lru_cache 装饰器。

  1. @functools.lru_cache(maxsize=None, typed=False) 

参数解读:

  • maxsize:最多可以缓存多少个此函数的调用结果,如果为None,则无限制,设置为 2 的幂时,性能最佳

  • typed:若为 True,则不同参数类型的调用将分别缓存。

举个例子

  1. from functools import lru_cache 
  2.  
  3. @lru_cache(None) 
  4. def add(x, y): 
  5. print("calculating: %s + %s" % (x, y)) 
  6. return x + y 
  7.  
  8. print(add(12)) 
  9. print(add(12)) 
  10. print(add(23)) 

输出如下,可以看到第二次调用并没有真正的执行函数体,而是直接返回缓存里的结果

  1. calculating: 1 + 2 
  2. 3 
  3. 3 
  4. calculating: 2 + 3 
  5. 5 

下面这个是经典的斐波那契数列,当你指定的 n 较大时,会存在大量的重复计算

  1. def fib(n): 
  2. if n < 2
  3. return n 
  4. return fib(n - 2) + fib(n - 1

第六点介绍的 timeit,现在可以用它来测试一下到底可以提高多少的效率。

不使用 lru_cache 的情况下,运行时间 31 秒

  1. import timeit 
  2.  
  3. def fib(n): 
  4. if n < 2
  5. return n 
  6. return fib(n - 2) + fib(n - 1
  7.  
  8.  
  9.  
  10. print(timeit.timeit(lambda :fib(40), number=1)) 
  11. # output: 31.2725698948 

由于使用了 lru_cache 后,运行速度实在太快了,所以我将 n 值由 30 调到 500,可即使是这样,运行时间也才 0.0004 秒。提高速度非常显著。

  1. import timeit 
  2. from functools import lru_cache 
  3.  
  4. @lru_cache(None) 
  5. def fib(n): 
  6. if n < 2
  7. return n 
  8. return fib(n - 2) + fib(n - 1
  9.  
  10. print(timeit.timeit(lambda :fib(500), number=1)) 
  11. # output: 0.0004921059880871326 

8. 在程序退出前执行代码的技巧

使用 atexit 这个内置模块,可以很方便的注册退出函数。

不管你在哪个地方导致程序崩溃,都会执行那些你注册过的函数。

示例如下

5年Python功力,总结了10个开发技巧

如果clean函数有参数,那么你可以不用装饰器,而是直接调用atexit.register(clean_1, 参数1, 参数2, 参数3='xxx')

可能你有其他方法可以处理这种需求,但肯定比上不使用 atexit 来得优雅,来得方便,并且它很容易扩展。

但是使用 atexit 仍然有一些局限性,比如:

  • 如果程序是被你没有处理过的系统信号杀死的,那么注册的函数无法正常执行。

  • 如果发生了严重的 Python 内部错误,你注册的函数无法正常执行。

  • 如果你手动调用了os._exit,你注册的函数无法正常执行。

9. 实现类似 defer 的延迟调用

在 Golang 中有一种延迟调用的机制,关键字是 defer,例如下面的示例

  1. import "fmt" 
  2.  
  3. func myfunc { 
  4. fmt.Println("B"
  5.  
  6. func main { 
  7. defer myfunc 
  8. fmt.Println("A"

输出如下,myfunc 的调用会在函数返回前一步完成,即使你将 myfunc 的调用写在函数的第一行,这就是延迟调用。

那么在 Python 中否有这种机制呢?

当然也有,只不过并没有 Golang 这种简便。

在 Python 可以使用 上下文管理器达到这种效果

  1. import contextlib 
  2.  
  3. def callback: 
  4. print('B'
  5.  
  6. with contextlib.ExitStack as stack: 
  7. stack.callback(callback) 
  8. print('A'

输出如下

 

10. 如何流式读取数G超大文件

使用 with...open... 可以从一个文件中读取数据,这是所有 Python 开发者都非常熟悉的操作。

但是如果你使用不当,也会带来很大的麻烦。

比如当你使用了 read 函数,其实 Python 会将文件的内容一次性的全部载入内存中,如果文件有 10 个G甚至更多,那么你的电脑就要消耗的内存非常巨大。

  1. # 一次性读取 
  2. with open("big_file.txt""r") as fp: 
  3. content = fp.read 

对于这个问题,你也许会想到使用 readline 去做一个生成器来逐行返回。

  1. def read_from_file(filename): 
  2. with open(filename, "r") as fp: 
  3. yield fp.readline 

可如果这个文件内容就一行呢,一行就 10个G,其实你还是会一次性读取全部内容。

最优雅的解决方法是,在使用 read 方法时,指定每次只读取固定大小的内容,比如下面的代码中,每次只读取 8kb 返回。

  1. def read_from_file(filename, block_size = 1024 * 8): 
  2. with open(filename, "r") as fp: 
  3. while True: 
  4. chunk = fp.read(block_size) 
  5. if not chunk: 
  6. break 
  7.  
  8. yield chunk 

上面的代码,功能上已经没有问题了,但是代码看起来代码还是有些臃肿。

借助偏函数 和 iter 函数可以优化一下代码

  1. from functools import partial 
  2.  
  3. def read_from_file(filename, block_size = 1024 * 8): 
  4. with open(filename, "r") as fp: 
  5. for chunk in iter(partial(fp.read, block_size), ""): 
  6. yield chunk 

 

责任编辑:张燕妮 来源: Python编程时光
相关推荐

2020-06-07 16:16:01

Python开发工具

2019-12-02 14:39:14

密码登陆体验

2020-06-30 08:28:29

Vue开发前端

2020-06-09 10:55:16

Python编程代码

2021-05-17 09:31:58

爬虫伪装技巧

2021-11-19 16:54:11

Python代码开发

2023-07-17 11:43:07

2018-05-21 09:55:09

Java编程技巧

2019-07-31 14:33:23

UI设计UI界面动画

2020-06-23 08:28:26

前端开发技巧

2015-07-27 09:36:09

storyboard

2019-03-15 10:25:00

技术研发指标

2020-06-08 07:52:31

Python开发工具

2010-01-22 16:35:41

C++开发

2014-07-03 16:35:38

WebApp开发技巧总结

2009-08-27 16:54:59

C#开发技巧

2015-06-17 10:28:10

WebAPP开发技巧

2015-06-04 10:44:59

WebAPP开发技巧

2013-04-18 10:19:40

iOS开发Xcode调试

2020-07-10 14:25:32

Python编程代码
点赞
收藏

51CTO技术栈公众号