问题: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的身份,然后在这种情况下重新分配给空字符串。

Logo

学AI,认准AI Studio!GPU算力,限时免费领,邀请好友解锁更多惊喜福利 >>>

更多推荐