python - 无检查的 if-else 与“或”操作
问题描述
假设我们有一个字典,它总是有键 first_name 和 last_name 但它们可能等于 None。
{
'first_name': None,
'last_name': 'Bloggs'
}
如果传入了名字,我们希望保存名字,如果传入了 None,我们希望将其保存为空字符串。
first_name = account['first_name'] if account['first_name'] else ""
对比
first_name = account['first_name'] or ""
然而,这两项工作在幕后有什么区别?一个比另一个更有效吗?
解决方案
以下两个表达式有什么区别?
first_name = account['first_name'] if account['first_name'] else ""
对比
first_name = account['first_name'] or ""
主要区别在于,在 Python 中,第一个是条件表达式,
该表达式
x if C else y
首先计算条件,C
而不是x
. 如果C
为真,x
则进行评估并返回其值;否则,y
被评估并返回其值。
而第二个使用布尔运算:
表达式
x or y
首先计算x
; 如果x
为真,则返回其值;否则,y
评估并返回结果值。
请注意,第一个可能需要两个键查找,而第二个只需要一个键查找。
这种查找称为下标表示法:
name[subscript_argument]
下标符号练习.__getitem__
引用的对象的方法name
。
它需要加载名称和下标参数。
现在,在问题的上下文中,如果它True
在布尔上下文中进行测试(非空字符串会,但None
不会),它将需要第二次(冗余)加载字典和条件表达式的键, 同时简单地返回布尔or
运算的第一次查找。
因此,我希望第二个布尔运算在值不是的情况下效率更高一些None
。
抽象语法树 (AST) 分解
其他人比较了两种表达式生成的字节码。
然而,AST 代表了解释器解析的语言的第一个细分。
以下 AST 表明第二次查找可能涉及更多工作(请注意,我已格式化输出以便于解析):
>>> print(ast.dump(ast.parse("account['first_name'] if account['first_name'] else ''").body[0]))
Expr(
value=IfExp(
test=Subscript(value=Name(id='account', ctx=Load()),
slice=Index(value=Str(s='first_name')), ctx=Load()),
body=Subscript(value=Name(id='account', ctx=Load()),
slice=Index(value=Str(s='first_name')), ctx=Load()),
orelse=Str(s='')
))
相对
>>> print(ast.dump(ast.parse("account['first_name'] or ''").body[0]))
Expr(
value=BoolOp(
op=Or(),
values=[
Subscript(value=Name(id='account', ctx=Load()),
slice=Index(value=Str(s='first_name')), ctx=Load()),
Str(s='')]
)
)
字节码分析
在这里,我们看到条件表达式的字节码要长得多。根据我的经验,这通常预示着相对表现不佳。
>>> import dis
>>> dis.dis("d['name'] if d['name'] else ''")
1 0 LOAD_NAME 0 (d)
2 LOAD_CONST 0 ('name')
4 BINARY_SUBSCR
6 POP_JUMP_IF_FALSE 16
8 LOAD_NAME 0 (d)
10 LOAD_CONST 0 ('name')
12 BINARY_SUBSCR
14 RETURN_VALUE
>> 16 LOAD_CONST 1 ('')
18 RETURN_VALUE
对于布尔运算,它几乎只有一半:
>>> dis.dis("d['name'] or ''")
1 0 LOAD_NAME 0 (d)
2 LOAD_CONST 0 ('name')
4 BINARY_SUBSCR
6 JUMP_IF_TRUE_OR_POP 10
8 LOAD_CONST 1 ('')
>> 10 RETURN_VALUE
在这里,我希望性能相对于其他性能要快得多。
因此,让我们看看性能是否有很大差异。
表现
性能在这里不是很重要,但有时我必须自己看看:
def cond(name=False):
d = {'name': 'thename' if name else None}
return lambda: d['name'] if d['name'] else ''
def bool_op(name=False):
d = {'name': 'thename' if name else None}
return lambda: d['name'] or ''
我们看到,当名称在字典中时,布尔运算比条件运算快 10% 左右。
>>> min(timeit.repeat(cond(name=True), repeat=10))
0.11814919696189463
>>> min(timeit.repeat(bool_op(name=True), repeat=10))
0.10678509017452598
但是,当名称不在字典中时,我们看到几乎没有区别:
>>> min(timeit.repeat(cond(name=False), repeat=10))
0.10031125508248806
>>> min(timeit.repeat(bool_op(name=False), repeat=10))
0.10030031995847821
关于正确性的说明
一般来说,我更喜欢or
布尔运算而不是条件表达式 - 有以下注意事项:
- 字典保证只有非空字符串或
None
. - 这里的性能至关重要。
在上述任何一个不正确的情况下,为了正确起见,我更喜欢以下内容:
first_name = account['first_name']
if first_name is None:
first_name = ''
好处是
- 查找完成一次,
- 检查
is None
速度很快, - 代码是明确的,并且
- 任何 Python 程序员都可以轻松维护代码。
这也不应该降低性能:
def correct(name=False):
d = {'name': 'thename' if name else None}
def _correct():
first_name = d['name']
if first_name is None:
first_name = ''
return _correct
我们看到,当关键在那里时,我们获得了相当有竞争力的表现:
>>> min(timeit.repeat(correct(name=True), repeat=10))
0.10948465298861265
>>> min(timeit.repeat(cond(name=True), repeat=10))
0.11814919696189463
>>> min(timeit.repeat(bool_op(name=True), repeat=10))
0.10678509017452598
当键不在字典中时,它虽然不是很好:
>>> min(timeit.repeat(correct(name=False), repeat=10))
0.11776355793699622
>>> min(timeit.repeat(cond(name=False), repeat=10))
0.10031125508248806
>>> min(timeit.repeat(bool_op(name=False), repeat=10))
0.10030031995847821
结论
条件表达式和布尔运算之间的区别是在一个True
条件上分别进行两次对一的查找,从而使布尔运算的性能更高。
None
但是,为了正确起见,请执行一次查找,检查与 with的身份is None
,然后在这种情况下重新分配给空字符串。
推荐阅读
- angular - Angular - 类型“HTMLOptionElement”上不存在属性“选项”
- logic - 时间逻辑(例如 LTL)存储库
- python-3.x - XPATH 部分匹配 tr id 与 Python、Selenium、
- c#-4.0 - 如何在标签的运行时显示当前正在执行的函数
- c++ - Consexpr if 具有非布尔条件
- react-native - React Native AsyncStorage 和传播运算符不起作用
- regex - Groovy 使用标签拆分 html 并创建一个列表
- ios - iOS - 获取 UITabBarItem 的 UIImageView/UIImage 的框架
- sql - += 在 SELECT 子句中;常数与列
- nativescript - 如何从 nativescript angular 访问本机 android 滚动视图侦听器?