文章详情

短信预约-IT技能 免费直播动态提醒

请输入下面的图形验证码

提交验证

短信预约提醒成功

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

2024-12-11 22:18

关注

[[329219]]

查看函数的源代码,我们通常会使用 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)) 

运行结果如下

 

  1. $ python demo.py 
  2. =================== 
  3. def add(x, y): 
  4.     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  
  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  
  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  
  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  
  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  
  9.     raise RuntimeError("Something bad happened"from None 
  10. RuntimeError: Something bad happened 
  11. (PythonCodingTime) 

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

当你使用 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. 10.020059824 

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

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

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

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

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

 

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

参数解读:

举个例子

 

  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(1, 2)) 
  9. print(add(1, 2)) 
  10. print(add(2, 3)) 

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

 

  1. calculating: 1 + 2 
  2. calculating: 2 + 3 

下面这个是经典的斐波那契数列,当你指定的 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 这个内置模块,可以很方便的注册退出函数。

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

示例如下

 

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

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

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

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编程时光内容投诉

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

软考中级精品资料免费领

  • 历年真题答案解析
  • 备考技巧名师总结
  • 高频考点精准押题
  • 2024年上半年信息系统项目管理师第二批次真题及答案解析(完整版)

    难度     813人已做
    查看
  • 【考后总结】2024年5月26日信息系统项目管理师第2批次考情分析

    难度     354人已做
    查看
  • 【考后总结】2024年5月25日信息系统项目管理师第1批次考情分析

    难度     318人已做
    查看
  • 2024年上半年软考高项第一、二批次真题考点汇总(完整版)

    难度     435人已做
    查看
  • 2024年上半年系统架构设计师考试综合知识真题

    难度     224人已做
    查看

相关文章

发现更多好内容

猜你喜欢

AI推送时光机
位置:首页-资讯-后端开发
咦!没有更多了?去看看其它编程学习网 内容吧
首页课程
资料下载
问答资讯