本文介绍两种编程风格 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