本文介绍两种编程风格 EAFP and LBYL。
EAFP:Easier to Ask for Forgiveness than Permission,请求宽恕比获得许可更容易。首先尝试执行你想做的任何操作,然后使用一个尝试块来捕获你的操作可能抛出的最终异常,以防它不成功。
1
2
3
4
5
6
|
print("Type a positive integer (defaults to 1):")
s = input(" >> ")
try:
n = int(s)
except ValueError:
n = 1
|
LBYL:Look Before You Leap,三思而后行。首先检查是否可以成功地进行某项操作,然后再进行操作。
1
2
3
4
5
6
|
print("Type a positive integer (defaults to 1):")
s = input(" >> ")
if s.isnumeric():
n = int(s)
else:
n = 1
|
使用 EAFP 替代 LBYL
使用 EAFP 风格来编写代码有如下几点优势。
避免冗余
假如我们要从一个字典中获取到某一个 key 的值,该 key 可能不存在。使用 LBYL 的风格如下:
1
2
3
4
5
6
7
|
d = {"a": 1, "b": 42}
print("What key do you want to access?")
key = input(" >> ")
if key in d:
print(d[key])
else:
print(f"Cannot find key '{key}'")
|
这段逻辑两次访问了字典:第一次检查 key 是否存在,然后第二次获取该 key 的值。这就好比我们你打开一个盒子,看看里面是否有东西,然后关上它。然后,如果盒子不是空的,再打开它,取出里面的东西。
EAFP 的风格代码如下:
1
2
3
4
5
6
7
|
d = {"a": 1, "b": 42}
print("What key do you want to access?")
key = input(" >> ")
try:
print(d[key])
except KeyError:
print(f"Cannot find key '{key}'")
|
另外 Python 官方提供了一个 dict.get 其实是与 EAFP 的思想一致的。当获取某一个 key 的值时,如果 key 不存在则使用一个默认值。
EAFP 的执行效率更高
如果预计失败不会经常发生,那么 EAFP 会更快。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
>>> import timeit
>>> eafp = """s = "345"
... try:
... n = int(s)
... except ValueError:
... n = 0"""
>>> timeit.timeit(eafp)
0.1687019999999393
>>> lbyl = """s = "345"
... if s.isnumeric():
... n = int(s)
... else:
... n = 0"""
>>> timeit.timeit(lbyl)
0.30682630000001154
|
备注:这里本地测试,eafp 的方式稍稍快一些。
1
2
3
4
|
>>> timeit.timeit(lbyl)
0.13487599999999134
>>> timeit.timeit(eafp)
0.10865979199999742
|
LBYL 仍然可能失败
在 LBYL 中,当你进行检查后,情况可能会发生变化,后续操作操作运行可能仍然会出错。
1
2
3
4
5
6
7
8
9
10
|
import pathlib
print("What file should I read?")
filepath = input(" >> ")
if pathlib.Path(filepath).exists():
with open(filepath, "r") as f:
contents = f.read()
# Do something with the contents.
else:
print("Woops, the file does not exist!")
|
如果我们的系统比较复杂,比如有多个用户在使用或者有其他的一些脚本同时在进行一些逻辑,这里的代码检查完文件是否存在之后,在进行打开操作时,文件仍然可能已经不存在了。
1
2
3
4
5
6
7
8
9
10
|
print("What file should I read?")
filepath = input(" >> ")
try:
with open(filepath, "r") as f:
contents = f.read()
except FileNotFoundError:
print("Woops, the file does not exist!")
else:
# Do something with the contents.
pass
|
如果使用 EAFP 方法,代码要么读取文件,要么不读取,两种情况都包括在内。
捕获多种类型的异常
如果我们在执行一段非常复杂的逻辑,可能会出现各种不同的非预期状况。列举可能得异常似乎比使用很长的 if 语句来做各种检查更好一些。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
|
def get_inverse(num_str):
return 1 / int(num_str)
print("Type an integer:")
s = input(" >> ")
try:
print(get_inverse(s))
except ValueError:
print("I asked for an integer!")
except ZeroDivisionError:
print("0 has no inverse!")
print("Type an integer:")
s = input(" >> ")
if s.isnumeric() and s != "0":
print(get_inverse(s))
elif not s.isnumeric():
print("I asked for an integer!")
else:
print("0 has no inverse!")
|
EAFP 中,我们考虑用户可能输入的不是数字或者输入了 0。在 LBYL 中使用了 isnumeric 两次,isnumeric 并不适用于负数。而且如果用户输入了比如 " 3"、“000”,这个数字可以被 int 转换。但是 isnumeric 会返回 false。
结论
EAFP 风格是 LBYL 风格的一个非常好的替代方案,从某些角度上考虑存在一些优势。在编写代码时,尽量权衡几种方法的不同利弊,不要忘了考虑 EAFP 风格代码。
EAFP 并不是在每一种情况下都是绝对最好的方法,但 EAFP 风格代码一般具有比较好的可读性和性能。
文档
https://mathspp.com/blog/pydonts/eafp-and-lbyl-coding-styles