程序员

首页 » 常识 » 诊断 » 5年Python功力,总结了10个开发技
TUhjnbcbe - 2023/2/19 18:01:00

作者

写代码的明哥

来源

Python编程时光

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

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

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

那如果没有IDE呢?

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

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

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

#demo.pyimportinspectdefadd(x,y):returnx+yprint(===================)print(inspect.getsource(add))

运行结果如下:

$pythondemo.py===================defadd(x,y):returnx+y

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

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

就像这样子:

try:print(1/0)exceptExceptionasexc:raiseRuntimeError(Somethingbadhappened)

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

Traceback(mostrecentcalllast):Filedemo.py,line2,inmoduleprint(1/0)ZeroDivisionError:divisionbyzeroDuringhandlingoftheaboveexception,anotherexceptionoccurred:Traceback(mostrecentcalllast):Filedemo.py,line4,inmoduleraiseRuntimeError(Somethingbadhappened)RuntimeError:Somethingbadhappened

如果在异常处理程序或finally块中引发异常,默认情况下,异常机制会隐式工作会将先前的异常附加为新异常的__context__属性。这就是Python默认开启的自动关联异常上下文。

如果你想自己控制这个上下文,可以加个from关键字(from语法会有个限制,就是第二个表达式必须是另一个异常类或实例。)来表明你的新异常是直接由哪个异常引起的。

try:print(1/0)exceptExceptionasexc:raiseRuntimeError(Somethingbadhappened)fromexc

输出如下:

Traceback(mostrecentcalllast):Filedemo.py,line2,inmoduleprint(1/0)ZeroDivisionError:divisionbyzeroTheaboveexceptionwasthedirectcauseofthefollowingexception:Traceback(mostrecentcalllast):Filedemo.py,line4,inmoduleraiseRuntimeError(Somethingbadhappened)fromexcRuntimeError:Somethingbadhappened

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

try:print(1/0)exceptExceptionasexc:raiseRuntimeError(badthing).with_traceback(exc)

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

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

$catdemo.pytry:print(1/0)exceptExceptionasexc:raiseRuntimeError(Somethingbadhappened)fromNone$$pythondemo.pyTraceback(mostrecentcalllast):Filedemo.py,line4,inmoduleraiseRuntimeError(Somethingbadhappened)fromNoneRuntimeError:Somethingbadhappened(PythonCodingTime)

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

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

importsysfrompprintimportpprintpprint(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-cprint(\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-msitesys.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)foritem1inlist1:foritem2inlist2:foritem3inlist3:print(item1+item2+item3)

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

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

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

fromitertoolsimportproductlist1=range(1,3)list2=range(4,6)list3=range(7,9)foritem1,item2,item3inproduct(list1,list2,list3):print(item1+item2+item3)

输出如下:

$pythondemo.py

如何使用print输出日志

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

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

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

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

代码如下:

withopen(test.log,mode=w)asf:...print(hello,python,file=f,flush=True)exit()$cattest.loghello,python

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

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

importtimestart=time.time()#runthefunctionend=time.time()print(end-start)

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

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

有。

有一个内置模块叫timeit,使用它,只用一行代码即可:

importtimeimporttimeitdefrun_sleep(second):print(second)time.sleep(second)#只用这一行print(timeit.timeit(lambda:run_sleep(2),number=5))

运行结果如下:

.

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

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

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

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

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

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

参数解读:

maxsize:最多可以缓存多少个此函数的调用结果,如果为None,则无限制,设置为2的幂时,性能最佳;typed:若为True,则不同参数类型的调用将分别缓存。举个例子:

fromfunctoolsimportlru_cache

lru_cache(None)defadd(x,y):print(calculating:%s+%s%(x,y))returnx+yprint(add(1,2))print(add(1,2))print(add(2,3))

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

calculating:1+calculating:2+35

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

deffib(n):ifn2:returnnreturnfib(n-2)+fib(n-1)

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

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

importtimeitdeffib(n):ifn2:returnnreturnfib(n-2)+fib(n-1)print(timeit.timeit(lambda:fib(40),number=1))#output:31.

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

importtimeitfromfunctoolsimportlru_cache

lru_cache(None)deffib(n):ifn2:returnnreturnfib(n-2)+fib(n-1)print(timeit.timeit(lambda:fib(),number=1))#output:0.92105988

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

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

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

示例如下:

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

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

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

如果程序是被你没有处理过的系统信号杀死的,那么注册的函数无法正常执行。如果发生了严重的Python内部错误,你注册的函数无法正常执行。如果你手动调用了os._exit(),你注册的函数无法正常执行。

实现类似defer的延迟调用

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

importfmtfuncmyfunc(){fmt.Println(B)}funcmain(){defermyfunc()fmt.Println(A)}

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

AB

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

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

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

importcontextlibdefcallback():print(B)withcontextlib.ExitStack()asstack:stack.callback(callback)print(A)

输出如下:

AB

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

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

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

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

#一次性读取withopen(big_file.txt,r)asfp:content=fp.read()

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

defread_from_file(filename):withopen(filename,r)asfp:yieldfp.readline()

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

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

defread_from_file(filename,block_size=*8):withopen(filename,r)asfp:whileTrue:chunk=fp.read(block_size)ifnotchunk:breakyieldchunk

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

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

fromfunctoolsimportpartialdefread_from_file(filename,block_size=*8):withopen(filename,r)asfp:forchunkiniter(partial(fp.read,block_size),):yieldchunk

1
查看完整版本: 5年Python功力,总结了10个开发技