【问题标题】:How do "and" and "or" act with non-boolean values?“and”和“or”如何处理非布尔值?
【发布时间】:2020-10-12 22:24:24
【问题描述】:

我正在尝试学习 python,并遇到了一些既漂亮又简短但并不完全有意义的代码

上下文是:

def fn(*args):
    return len(args) and max(args)-min(args)

我知道它在做什么,但为什么 python 会这样做 - 即返回值而不是 True/False?

10 and 7-2

返回 5。同样,将 and 更改为 or 将导致功能更改。所以

10 or 7 - 2

将返回 10。

这是合法/可靠的风格,还是有任何陷阱?

【问题讨论】:

  • and(以及or)不限于使用或返回布尔值。
  • IMNSHO:这种写法有点令人困惑;我无法直接判断它是否应该返回布尔值(是否有不同的最小值和最大值)或数字(最小值和最大值的区别是什么)。如果是后者,那么将零长度列表的差异作为数字给出是否有意义也是一个问题。 (而不是None 或例外)
  • 它可以工作,正如其他人所解释的那样,但是一个可能的问题是,如果它返回 0,您无法判断 args 是空的还是非空的但所有元素都相等。
  • @EspeciallyLime:完全正确。我在answer 中提到过。

标签: python operators logical-operators


【解决方案1】:

TL;DR

我们首先总结两个逻辑运算符andor 的两种行为。这些习语将构成我们下面讨论的基础。

and

如果有则返回第一个 Falsy 值,否则返回最后一个 表达式中的值。

or

如果有则返回第一个 Truthy 值,否则返回最后一个 表达式中的值。

the docs 中也总结了该行为,特别是在此表中:

无论其操作数如何,唯一返回布尔值的运算符是not 运算符。


“真实性”和“真实性”评价

声明

len(args) and max(args) - min(args)

是一种非常 pythonic 简洁(并且可以说可读性较差)的方式来表示“如果args 不为空,则返回max(args) - min(args) 的结果”,否则返回0。通常,它是if-else 表达式的更简洁表示。例如,

exp1 and exp2

应该(大致)翻译成:

r1 = exp1
if r1:
    r1 = exp2

或者,等价地,

r1 = exp2 if exp1 else exp1

同样,

exp1 or exp2

应该(大致)翻译成:

r1 = exp1
if not r1:
    r1 = exp2

或者,等价地,

r1 = exp1 if exp1 else exp2

其中exp1exp2 是任意python 对象,或返回某个对象的表达式。理解逻辑 andor 运算符的使用的关键是理解它们不限于操作或返回布尔值。任何具有真实性值的对象都可以在这里进行测试。这包括intstrlistdicttuplesetNoneType 和用户定义的对象。短路规则仍然适用。

但什么是真实性?
它指的是在条件表达式中使用时如何评估对象。 @Patrick Haugh 在this post 中很好地总结了真实性。

所有值都被认为是“真实的”,但以下除外,它们是 “假”:

  • None
  • False
  • 0
  • 0.0
  • 0j
  • Decimal(0)
  • Fraction(0, 1)
  • [] - 一个空的list
  • {} - 一个空的dict
  • () - 一个空的tuple
  • '' - 一个空的str
  • b'' - 一个空的bytes
  • set() - 一个空的set
  • 一个空的range,比如range(0)
  • 对象
    • obj.__bool__() 返回False
    • obj.__len__() 返回0

“真实”值将满足ifwhile 执行的检查 陈述。我们使用“truthy”和“falsy”来区分 boolTrueFalse


and 的工作原理

我们以 OP 的问题为基础,继续讨论这些运算符在这些情况下的作用。

给定一个具有定义的函数

def foo(*args):
    ...

如何返回最小值和最大值之间的差值 在零个或多个参数的列表中?

找到最小值和最大值很容易(使用内置函数!)。这里唯一的障碍是适当地处理参数列表可能为空的极端情况(例如,调用foo())。感谢and 运算符,我们可以在一行中完成这两项操作:

def foo(*args):
     return len(args) and max(args) - min(args)
foo(1, 2, 3, 4, 5)
# 4

foo()
# 0

由于使用了and,如果第一个表达式是True,则还必须计算第二个表达式。请注意,如果第一个表达式被评估为真,则返回值总是第二个表达式的结果。如果第一个表达式被评估为 Falsy,那么返回的结果就是第一个表达式的结果。

在上面的函数中,如果foo接收到一个或多个参数,len(args)大于0(一个正数),所以返回的结果是max(args) - min(args)。 OTOH,如果没有传递任何参数,len(args)0,这是 Falsy,并返回 0

请注意,编写此函数的另一种方法是:

def foo(*args):
    if not len(args):
        return 0
    
    return max(args) - min(args)

或者,更简洁地说,

def foo(*args):
    return 0 if not args else max(args) - min(args)

当然,如果这些函数都不执行任何类型检查,那么除非您完全信任所提供的输入,否则不要依赖这些构造的简单性。


or 的工作原理

我用一个人为的例子以类似的方式解释or的工作原理。

给定一个具有定义的函数

def foo(*args):
    ...

您将如何完成 foo 以返回所有超过 9000 的数字?

我们使用or 来处理这里的角落案例。我们将foo 定义为:

def foo(*args):
     return [x for x in args if x > 9000] or 'No number over 9000!'

foo(9004, 1, 2, 500)
# [9004]

foo(1, 2, 3, 4)
# 'No number over 9000!'

foo 对列表执行过滤以保留9000 上的所有数字。如果存在任何这样的数字,列表推导的结果是一个非空列表,它是真实的,所以它被返回(这里是短路)。如果不存在这样的数字,则列表 comp 的结果为 [],即 Falsy。所以第二个表达式现在被求值(一个非空字符串)并被返回。

使用条件,我们可以将这个函数重写为,

def foo(*args):
    r = [x for x in args if x > 9000]
    if not r:
        return 'No number over 9000!' 
    
    return r

和以前一样,这种结构在错误处理方面更加灵活。

【讨论】:

  • 为了简洁而牺牲所有清晰度并不是“pythonic”,我认为这里就是这种情况。这不是一个简单的结构。
  • 我认为应该注意到 Python 条件表达式使这种语法变得不那么常见。我当然更喜欢 max(args) - min(args) if len(args) else 0 到原来的。
  • 另一个一开始让人困惑的常见问题是,如果不存在则分配一个值:“some_var = arg or 3”
  • @Baldrickk 在人们开始抨击这种语法以支持三元运算符之前,请记住,当涉及到 n 元条件表达式时,三元运算符很快就会失控。例如,if ... else (if ... else (if ... else (if ... else ...))) 也可以改写为... and ... and ... and ... and ...,这样就很难争论这两种情况的可读性了。
  • 为了简洁而牺牲清晰度并不是pythonic,但事实并非如此。这是一个众所周知的成语。这是一个你必须学习的习语,就像任何其他习语一样,但它几乎不是“牺牲清晰度”。
【解决方案2】:

引用Python Docs

请注意,andor 都没有 restrict valuetype 他们返回 到FalseTrue,而是返回最后评估的参数。这 有时很有用,例如,如果 s 是一个应该替换为的字符串 一个默认值,如果它为空,则表达式s or 'foo' 产生 期望值。

因此,这就是 Python 被设计用来评估布尔表达式的方式,而上述文档让我们了解了他们这样做的原因。

要获得布尔值,只需对其进行类型转换。

return bool(len(args) and max(args)-min(args))

为什么?

短路。

例如:

2 and 3 # Returns 3 because 2 is Truthy so it has to check 3 too
0 and 3 # Returns 0 because 0 is Falsey and there's no need to check 3 at all

or 也是如此,即它会在找到表达式后立即返回 Truthy,因为计算表达式的其余部分是多余的。

Python 不会返回硬核 TrueFalse,而是返回 TruthyFalsey,它们无论如何都会计算为 TrueFalse。您可以按原样使用表达式,它仍然可以工作。


要了解什么是 TruthyFalsey,请查看Patrick Haugh's answer

【讨论】:

    【解决方案3】:

    andor 执行布尔逻辑,但它们在比较时返回实际值之一。当使用 时,值在布尔上下文中从左到右进行评估。 0、''、[]、()、{}、None 在布尔上下文中为 false;其他都是真的。

    如果布尔上下文中的所有值都为真,并且返回最后一个值。

    >>> 2 and 5
    5
    >>> 2 and 5 and 10
    10
    

    如果布尔上下文中的任何值为假并且返回第一个假值。

    >>> '' and 5
    ''
    >>> 2 and 0 and 5
    0
    

    所以代码

    return len(args) and max(args)-min(args)
    

    当有 args 时返回 max(args)-min(args) 的值,否则返回 len(args),即 0。

    【讨论】:

      【解决方案4】:

      这是合法/可靠的风格,还是有任何陷阱?

      这是合法的,它是返回最后一个值的short circuit evaluation

      你提供了一个很好的例子。如果没有传递参数,该函数将返回0,并且代码不必检查没有传递参数的特殊情况。

      使用它的另一种方法是将 None 参数默认为可变原语,例如空列表:

      def fn(alist=None):
          alist = alist or []
          ....
      

      如果将某些非真实值传递给alist,它默认为一个空列表,这是避免if 语句和mutable default argument pitfall 的便捷方法

      【讨论】:

        【解决方案5】:

        陷阱

        是的,有一些问题。

        fn() == fn(3) == fn(4, 4)

        首先,如果fn返回0,你无法知道它是在没有任何参数、一个参数还是多个相等参数的情况下调用的:

        >>> fn()
        0
        >>> fn(3)
        0
        >>> fn(3, 3, 3)
        0
        

        fn 是什么意思?

        那么,Python 是一种动态语言。它没有在任何地方指定fn 做了什么,它的输入应该是什么以及它的输出应该是什么样子。因此,正确命名函数非常重要。同样,参数不必称为argsdelta(*numbers)calculate_range(*numbers) 可能会更好地描述该函数应该做什么。

        参数错误

        最后,逻辑and 运算符应该防止函数在没有任何参数的情况下调用失败。但是,如果某些参数不是数字,它仍然会失败:

        >>> fn('1')
        Traceback (most recent call last):
          File "<stdin>", line 1, in <module>
          File "<stdin>", line 2, in fn
        TypeError: unsupported operand type(s) for -: 'str' and 'str'
        >>> fn(1, '2')
        Traceback (most recent call last):
          File "<stdin>", line 1, in <module>
          File "<stdin>", line 2, in fn
        TypeError: '>' not supported between instances of 'str' and 'int'
        >>> fn('a', 'b')
        Traceback (most recent call last):
          File "<stdin>", line 1, in <module>
          File "<stdin>", line 2, in fn
        TypeError: unsupported operand type(s) for -: 'str' and 'str'
        

        可能的替代方案

        下面是按照"Easier to ask for forgiveness than permission." principle写函数的方法:

        def delta(*numbers):
            try:
                return max(numbers) - min(numbers)
            except TypeError:
                raise ValueError("delta should only be called with numerical arguments") from None
            except ValueError:
                raise ValueError("delta should be called with at least one numerical argument") from None
        

        举个例子:

        >>> delta()
        Traceback (most recent call last):
          File "<stdin>", line 1, in <module>
          File "<stdin>", line 7, in delta
        ValueError: delta should be called with at least one numerical argument
        >>> delta(3)
        0
        >>> delta('a')
        Traceback (most recent call last):
          File "<stdin>", line 1, in <module>
          File "<stdin>", line 5, in delta
        ValueError: delta should only be called with numerical arguments
        >>> delta('a', 'b')
        Traceback (most recent call last):
          File "<stdin>", line 1, in <module>
          File "<stdin>", line 5, in delta
        ValueError: delta should only be called with numerical arguments
        >>> delta('a', 3)
        Traceback (most recent call last):
          File "<stdin>", line 1, in <module>
          File "<stdin>", line 5, in delta
        ValueError: delta should only be called with numerical arguments
        >>> delta(3, 4.5)
        1.5
        >>> delta(3, 5, 7, 2)
        5
        

        如果您真的不想在不带任何参数的情况下调用 delta 时引发异常,则可以返回一些其他情况下不可能的值(例如 -1None):

        >>> def delta(*numbers):
        ...     try:
        ...         return max(numbers) - min(numbers)
        ...     except TypeError:
        ...         raise ValueError("delta should only be called with numerical arguments") from None
        ...     except ValueError:
        ...         return -1 # or None
        ... 
        >>> 
        >>> delta()
        -1
        

        【讨论】:

          【解决方案6】:

          这是合法/可靠的风格,还是有任何陷阱?

          我想在这个问题上补充一点,它不仅合法可靠,而且非常实用。这是一个简单的例子:

          >>>example_list = []
          >>>print example_list or 'empty list'
          empty list
          

          因此,您可以真正发挥自己的优势。为了简洁起见,这是我的看法:

          Or运营商

          Python 的 or 运算符返回第一个真值,或最后一个值,然后停止

          And运营商

          Python 的 and 运算符返回第一个 False-y 值或最后一个值,然后停止

          幕后

          在python中,除了0之外,所有数字都被解释为True。因此,说:

          0 and 10 
          

          等同于:

          False and True
          

          显然是False。因此它返回 0 是合乎逻辑的

          【讨论】:

            【解决方案7】:

            是的。这是正确的行为和比较。

            至少在 Python 中,如果 A 本质上是 True,则 A and B 返回 B,包括如果 A 不是 Null,不是 None 不是空容器(例如空的 listdict 等)。 A 返回 IFF A 本质上是 FalseNone 或 Empty 或 Null。

            另一方面,如果A 本质上是True,则A or B 返回A,包括如果A 不是空的,不是None 不是空容器(例如空的listdict 等),否则返回 B

            很容易不注意到(或忽略)这种行为,因为在 Python 中,任何non-null 非空对象的计算结果为 True 都被视为布尔值。

            例如,以下所有内容都会打印“True”

            if [102]: 
                print "True"
            else: 
                print "False"
            
            if "anything that is not empty or None": 
                print "True"
            else: 
                print "False"
            
            if {1, 2, 3}: 
                print "True"
            else: 
                print "False"
            

            另一方面,以下所有内容都将打印“False”

            if []: 
                print "True"
            else: 
                print "False"
            
            if "": 
                print "True"
            else: 
                print "False"
            
            if set ([]): 
                print "True"
            else: 
                print "False"
            

            【讨论】:

            • 谢谢。我想写A本质上是True。已更正。
            【解决方案8】:

            通俗易懂,

            与: if first_val is False return first_val else second_value

            例如:

            1 and 2 # here it will return 2 because 1 is not False
            

            但是,

            0 and 2 # will return 0 because first value is 0 i.e False
            

            and =>如果有人为假,那就是假的。如果两者都是真的,那么只有它才会成为真的

            或: if first_val is False return second_val else first_value

            原因是,如果 first 为假,则检查 2 是否为真。

            例如:

            1 or 2 # here it will return 1 because 1 is not False
            

            但是,

            0 or 2 # will return 2 because first value is 0 i.e False
            

            或=>如果有人为假,那就是真的。所以如果第一个值是假的,不管 2 值应该是什么。 所以它会返回第二个值。

            如果有人是真的,那么它就会变成真的。如果两者都是假的,那么它就会变成假的。

            【讨论】:

              猜你喜欢
              • 2022-07-29
              • 2012-04-05
              • 2013-07-09
              • 1970-01-01
              相关资源
              最近更新 更多