if-else vs "or" 操作用于无检查
问题:if-else vs "or" 操作用于无检查 假设我们有一个字典,它总是有键 first_name 和 last_name 但它们可能等于 None。 { 'first_name': None, 'last_name': 'Bloggs' } 如果传入了名字,我们希望保存名字,如果传入了 None,我们希望将其保存为空字符串。 first_name = account['first_nam
问题:if-else vs "or" 操作用于无检查
假设我们有一个字典,它总是有键 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 u003d account['first_name'] if account['first_name'] else ""
对比
first_name u003d 帐户['first_name'] 或 ""
主要区别在于,第一个在 Python 中是条件表达式,
表达式
x if C else y
首先计算条件C
而不是x
。如果C
为真,则计算x
并返回其值;否则,计算y
并返回其值。
而第二个使用布尔运算:
表达式
x or y
首先计算x
;如果x
为真,则返回其值;否则,计算y
并返回结果值。
请注意,第一个可能需要两个键查找,而第二个只需要一个键查找。
此查找称为下标表示法:
name[subscript_argument]
下标符号练习name
引用的对象的__getitem__
方法。
它需要加载名称和下标参数。
现在,在问题的上下文中,如果它在布尔上下文中测试为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 = ''
好处是
-
查找完成_one_时间,
-
对
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
条件上分别进行二对一的查找,从而使布尔运算的性能更高。
但是,为了正确起见,请执行一次查找,使用is None
检查None
的身份,然后在这种情况下重新分配给空字符串。
更多推荐
所有评论(0)