“ if a、 b 或 c but not all of them”的 Python 语法

我有一个 python 脚本,它可以接收零个或三个命令行参数。(它要么以默认行为运行,要么需要指定所有三个值。)

类似这样的东西的理想语法是什么:

if a and (not b or not c) or b and (not a or not c) or c and (not b or not a):

271906 次浏览

这个怎么样:

conditions = [a, b, c]
if any(conditions) and not all(conditions):
...

其他变体:

if 1 <= sum(map(bool, conditions)) <= 2:
...

如果三个条件中只有一个是 True,则返回 True。也许这正是您在示例代码中想要的。

if sum(1 for x in (a,b,c) if x) == 1:

如果你指的是最小的形式,那就用这个:

if (not a or not b or not c) and (a or b or c):

翻译一下你问题的标题。

更新: 正如波动率和超级波动率所说的,你可以应用德摩根定律,获得相应的结果:

if (a or b or c) and not (a and b and c):

我的建议是使用对您和其他程序员更重要的表单。第一个意味着 “有些东西是假的,但也有些东西是真的”,第二个意味着 “有些事情是真的,但不是一切”。如果我要在硬件上进行优化或完成这项工作,我会选择第二个,这里只选择最易读的(同时考虑到要测试的条件和它们的名称)。我选了第一个。

我会选:

conds = iter([a, b, c])
if any(conds) and not any(conds):
# okay...

我觉得这应该能有效地短路

解释

通过使 conds成为一个迭代器,any的第一次使用将会导致短路,并且如果有任何条目为真,那么迭代器将会指向下一个元素; 否则,它将会消耗整个列表并成为 False。下一个 any获取迭代中剩余的项,并确保没有任何其他真值... 如果有,整个语句不可能为真,因此不存在唯一的元素(所以再次短路)。最后一个 any要么返回 False,要么用尽迭代器,变成 True

注意: 上面的代码检查是否只设置了一个条件


如果您想检查是否设置了一个或多个项,但并非每个项都设置了,那么可以使用:

not all(conds) and any(conds)

怎么样: (特殊情况)

if (bool(a) + bool(b) + bool(c) == 1):

注意,如果你也允许两个条件,你可以这样做

if (bool(a) + bool(b) + bool(c) in [1,2]):

这个问题已经有了许多高度赞成的答案和一个公认的答案,但到目前为止,所有这些答案都被表达布尔问题的各种方式分散了注意力,并且忽略了一个关键点:

我有一个 Python 脚本,它可以接收0或3个命令 行参数(它要么运行在默认行为上,要么需要所有三个 值)

这种逻辑首先不应该由库代码负责,而应该由命令行解析(通常是 Python 中的 argparse模块)处理。不必费心编写复杂的 if 语句,相反,您可以像这样设置参数解析器:

#!/usr/bin/env python
import argparse


parser = argparse.ArgumentParser()
parser.add_argument('--foo', nargs=3, default=['x', 'y', 'z'])
args = parser.parse_args()


print(args.foo)

是的,它应该是一个 选择而不是一个位置参数,因为它毕竟是 可以选择


编辑: 为了解决 LarsH 在注释中的问题,下面是一个例子,说明如果您确定需要使用3个或0个 位置参数的接口,您可以如何编写它。我认为前面的接口样式更好(因为 可以选择参数应该是 选择) ,但是为了完整性,这里有一个替代方法。注意,我们在创建解析器时重写 kwargusage,因为 argparse将自动生成一个误导性的使用消息,否则!

#!/usr/bin/env python
import argparse


parser = argparse.ArgumentParser(usage='%(prog)s [-h] [a b c]\n')
parser.add_argument('abc', nargs='*', help='specify 3 or 0 items', default=['x', 'y', 'z'])
args = parser.parse_args()
if len(args.abc) != 3:
parser.error('expected 3 arguments')


print(args.abc)

下面是一些用法示例:

# default case
$ ./three_or_none.py
['x', 'y', 'z']


# explicit case
$ ./three_or_none.py 1 2 3
['1', '2', '3']


# example failure mode
$ ./three_or_none.py 1 2
usage: three_or_none.py [-h] [a b c]
three_or_none.py: error: expected 3 arguments

根据我的理解,您有一个接收3个参数的函数,但是如果它不接收,它将按默认行为运行。由于您还没有解释当提供1或2个参数时应该发生什么,我将假设它应该只是执行默认行为。在这种情况下,我想你会发现以下答案非常有利:

def method(a=None, b=None, c=None):
if all([a, b, c]):
# received 3 arguments
else:
# default behavior

但是,如果希望以不同的方式处理1个或2个参数:

def method(a=None, b=None, c=None):
args = [a, b, c]
if all(args):
# received 3 arguments
elif not any(args):
# default behavior
else:
# some args (raise exception?)

注意: 这里假设“ False”值不会传递给这个方法。

如果使用条件迭代器,访问速度可能会很慢。但是您不需要多次访问每个元素,并且您不总是需要读取所有元素。这里有一个可以用于无限生成器的解决方案:

#!/usr/bin/env python3
from random import randint
from itertools import tee


def generate_random():
while True:
yield bool(randint(0,1))


def any_but_not_all2(s): # elegant
t1, t2 = tee(s)
return False in t1 and True in t2 # could also use "not all(...) and any(...)"


def any_but_not_all(s): # simple
hadFalse = False
hadTrue = False
for i in s:
if i:
hadTrue = True
else:
hadFalse = True
if hadTrue and hadFalse:
return True
return False




r1, r2 = tee(generate_random())
assert any_but_not_all(r1)
assert any_but_not_all2(r2)


assert not any_but_not_all([True, True])
assert not any_but_not_all2([True, True])


assert not any_but_not_all([])
assert not any_but_not_all2([])


assert any_but_not_all([True, False])
assert any_but_not_all2([True, False])

这个问题说明你要么需要全部三个参数(a 和 b 和 c) ,要么一个都不需要(不需要(a 或 b 或 c))

这意味着:

(a 和 b 和 c)或不(a 或 b 或 c)

英文句子:

“如果 a b c 不是全部”

也就是说:

(a or b or c) and not (a and b and c)

“ but”这个词通常意味着连词,换句话说就是“ and”。此外,“所有这些”翻译成条件的连接: 这个条件,还有那个条件,还有其他条件。“不”颠倒了整个连词。

我不同意接受的答案。作者没有将最直接的解释应用到规范中,也没有应用德摩根定律将表达式简化为更少的运算符:

 not a or not b or not c  ->  not (a and b and c)

同时声称答案是一个“最小形式”。

为了清楚起见,您希望根据有多少参数是逻辑正确的(在字符串参数的情况下,而不是空的)来做出决定?

argsne = (1 if a else 0) + (1 if b else 0) + (1 if c else 0)

然后你做了一个决定:

if ( 0 < argsne < 3 ):
doSth()

现在逻辑更加清晰了。

这基本上是一个“一些(但不是全部)”功能(与 any()all()内置函数相比)。

这意味着在测试结果中应该包含 Falses还有True,因此,你可以这样做:

some = lambda ii: frozenset(bool(i) for i in ii).issuperset((True, False))


# one way to test this is...
test = lambda iterable: (any(iterable) and (not all(iterable))) # see also http://stackoverflow.com/a/16522290/541412


# Some test cases...
assert(some(()) == False)       # all() is true, and any() is false
assert(some((False,)) == False) # any() is false
assert(some((True,)) == False)  # any() and all() are true


assert(some((False,False)) == False)
assert(some((True,True)) == False)
assert(some((True,False)) == True)
assert(some((False,True)) == True)

这段代码的一个优点是您只需要迭代一次就可以得到结果(布尔值)项。

一个缺点是所有这些真值表达式总是被求值,并且不像 or/and运算符那样执行 短路

为什么不数一数呢?

import sys
a = sys.argv
if len(a) = 1 :
# No arguments were given, the program name count as one
elif len(a) = 4 :
# Three arguments were given
else :
# another amount of arguments was given

如果你不介意有点神秘,你可以简单地使用 0 < (a + b + c) < 3,如果你有一到两个 true 语句,它将返回 true; 如果所有的都是 false 或者没有一个是 false,它将返回 false。

这也简化了如果您使用函数来计算布尔值,因为您只计算变量一次,这意味着您可以内联编写函数,而不需要临时存储变量。(例子: 0 < ( a(x) + b(x) + c(x) ) < 3)

当每个给定的 boolTrue,或者当每个给定的 boolFalse..。
他们都是平等的!

所以,我们只需要找到两个求值为不同 bool的元素
知道至少有一个 True和至少有一个 False

我的简短解决方案是:

not bool(a)==bool(b)==bool(c)

我认为它短路了,因为 AFAIK a==b==c等于 a==b and b==c

我的一般解决方案是:

def _any_but_not_all(first, iterable): #doing dirty work
bool_first=bool(first)
for x in iterable:
if bool(x) is not bool_first:
return True
return False


def any_but_not_all(arg, *args): #takes any amount of args convertable to bool
return _any_but_not_all(arg, args)


def v_any_but_not_all(iterable): #takes iterable or iterator
iterator=iter(iterable)
return _any_but_not_all(next(iterator), iterator)

我还写了一些代码处理多个迭代,但我从这里删除了它,因为我认为这是毫无意义的。但它仍然可用 给你