Python黑魔法手册 2.0 文档第五章:魔法开发技巧【1-5】

Python2年前 (2022)发布 safedragon
139 0

Python魔法手册 2.0 文档

第五章:魔法开发技巧【1-5】

这个章节可能会是很多人感兴趣的,因为里面介绍的是所有开发者都有可能用到的开发技巧,掌握这些代码编写技巧,对提高你代码的可读性、优雅性会很有帮助。

5.1 嵌套上下文管理的另类写法

当我们要写一个嵌套的上下文管理器时,可能会这样写

import contextlib
@contextlib.contextmanager
def test_context(name):
 print('enter, my name is {}'.format(name))
 yield
 print('exit, my name is {}'.format(name))
with test_context('aaa'):
 with test_context('bbb'):
 print('========== in main ============')

输出结果如下

enter, my name is aaa
enter, my name is bbb
========== in main ============
exit, my name is bbb
exit, my name is aaa

除此之外,你可知道,还有另一种嵌套写法

with test_context('aaa'), test_context('bbb'):
 print('========== in main ============')

5.2 将嵌套 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

5.3 单行实现 for 死循环如何写?

如果让你在不借助 while ,只使用 for 来写一个死循环?
你会写吗?
如果你还说简单,你可以自己试一下。

如果你尝试后,仍然写不出来,那我给出自己的做法。

for i in iter(int, 1):pass

是不是傻了?iter 还有这种用法?这为啥是个死循环?
关于这个问题,你如果看中文网站,可能找不到相关资料。
还好你可以通过 IDE 看py源码里的注释内容,介绍了很详细的使用方法。
原来iter有两种使用方法。

  • 通常我们的认知是第一种,将一个列表转化为一个迭代器。
  • 而第二种方法,他接收一个 callable对象,和一个sentinel参数。第一个对象会一直运行,直到它返回 sentinel值才结束。

int 呢?
这又是一个知识点,int 是一个内建方法。通过看注释,可以看出它是有默认值0的。你可以在console 模式下输入 int() 看看是不是返回0。
由于int() 永远返回0,永远返回不了1,所以这个 for 循环会没有终点。一直运行下去。

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

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

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)

5.5 自带的缓存机制不用白不用

缓存是一种将定量数据加以保存,以备迎合后续获取需求的处理方式,旨在加快数据获取的速度。
数据的生成过程可能需要经过计算,规整,远程获取等操作,如果是同一份数据需要多次使用,每次都重新生成会大大浪费时间。所以,如果将计算或者远程请求等操作获得的数据缓存下来,会加快后续的数据获取需求。
为了实现这个需求,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

 

© 版权声明

相关文章

暂无评论

暂无评论...