关于if语句:”if a或b或c但不是全部”的python语法

Python syntax for “if a or b or c but not all of them”

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

什么是这样的理想语法:

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


怎么样:

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

其他变型:

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


如果您的意思是最小形式,请使用以下内容:

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

它翻译了你问题的标题。

更新:正如易变性和SUPR所正确指出的,您可以应用De Morgan的法律并获得等价物:

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

我的建议是使用对您和其他程序员更重要的形式。第一个意思是"有假,也有真",第二个意思是"有真,但不是一切"。如果我要在硬件上进行优化或这样做,我会选择第二个,这里只选择最可读的(还要考虑您将要测试的条件和它们的名称)。我选了第一个。


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

I have a python script that can receive either zero or three command
line arguments. (Either it runs on default behavior or needs all three
values specified)

首先,这个逻辑不应该是代码的责任,而应该由argparse模块处理。不要费心编写复杂的if语句,而是更喜欢像这样设置参数解析器:

1
2
3
4
5
6
#!/usr/bin/env python
import argparse as ap
parser = ap.ArgumentParser()
parser.add_argument('--foo', nargs=3, default=['x', 'y', 'z'])
args = parser.parse_args()
print(args.foo)

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

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

1
2
3
4
5
6
7
8
9
#!/usr/bin/env python
import argparse as ap
parser = ap.ArgumentParser(usage='%(prog)s [-h] [a b c]
'
)
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)

以下是一些用法示例:

1
2
3
4
5
6
7
8
9
10
11
12
# default case
wim@wim-zenbook:/tmp$ ./three_or_none.py
['x', 'y', 'z']

# explicit case
wim@wim-zenbook:/tmp$ ./three_or_none.py 1 2 3
['1', '2', '3']

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


我想去:

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

我认为这应该相当有效地短路

解释

通过使conds成为迭代器,第一次使用any将短路,如果任何项为真,迭代器将指向下一个元素;否则,它将消耗整个列表并成为False。下一个any取iterable中的剩余项,并确保没有任何其他真正的值…如果有的话,整个语句就不可能是真的,因此没有一个唯一的元素(因此再次短路)。最后一个any将返回False或将耗尽iterable和True

注意:以上检查是否只设置了一个条件

如果要检查是否设置了一个或多个项,但没有设置每个项,则可以使用:

1
not all(conds) and any(conds)


英语句子:

"if a or b or c but not all of them"

转换成这个逻辑:

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

"but"一词通常意味着连词,换言之,"and"。此外,"所有的条件"翻译成条件的连词:这个条件,那个条件,和其他条件。"not"颠倒了整个连词。

我不同意接受的回答。作者忽略了将最直接的解释应用于规范,而忽略了将De Morgan定律应用于将表达式简化为更少的运算符:

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

同时声称答案是"最小形式"。


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

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


关于:(特殊条件)

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

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

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


要明确的是,您希望根据多少参数是逻辑真(在字符串参数的情况下,不是空的)做出决定?

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

然后你决定:

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

现在逻辑更清楚了。


如果你不介意有点神秘,你可以同时使用0 < (a + b + c) < 3,如果你有一到两个正确的陈述,它将返回True,如果所有的陈述都是错误的或没有错误的,它将返回false。

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


为什么不数一数呢?

1
2
3
4
5
6
7
8
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

这个问题表明,你要么需要所有三个论点(A、B和C),要么一个都不需要(A、B或C)。

这给出:

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


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

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

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

1
2
3
4
5
6
7
8
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值"不会传递到该方法中。


如果使用条件的迭代器,则访问速度可能会很慢。但您不需要多次访问每个元素,也不总是需要读取所有元素。下面是一个解决方案,可用于无限发电机:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
#!/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])

当每个给定的boolTrue时,或当每个给定的boolFalse时…他们都是平等的!

因此,我们只需要找到两个对不同的bools进行评估的元素要知道至少有一个True和至少一个False

我的简短解决方案:

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

我相信它会短路,因为afaik a==b==c等于a==b and b==c

我的广义解:

1
2
3
4
5
6
7
8
9
10
11
12
13
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)

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


这基本上是一个"部分(但不是全部)"功能(与any()all()内置功能相比)。

这意味着结果中应该有FalseTrue。因此,您可以执行以下操作:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
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运算符那样进行短路。