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)
首先,这个逻辑不应该是代码的责任,而应该由
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
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... |
我认为这应该相当有效地短路
解释
通过使
注意:以上检查是否只设置了一个条件
如果要检查是否设置了一个或多个项,但没有设置每个项,则可以使用:
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) |
同时声称答案是"最小形式"。
如果三个条件中只有一个是
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() |
现在逻辑更清楚了。
如果你不介意有点神秘,你可以同时使用
如果使用函数来计算bools,这也会简化,因为只计算一次变量,这意味着您可以直接编写函数,而不需要临时存储变量。(示例:
为什么不数一数呢?
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?) |
注意:这假设"
如果使用条件的迭代器,则访问速度可能会很慢。但您不需要多次访问每个元素,也不总是需要读取所有元素。下面是一个解决方案,可用于无限发电机:
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]) |
当每个给定的
因此,我们只需要找到两个对不同的
1 | not bool(a)==bool(b)==bool(c) |
我相信它会短路,因为afaik
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的代码,但是我从这里删除了它,因为我认为这是毫无意义的。不过,它在这里仍然可用。
这基本上是一个"部分(但不是全部)"功能(与
这意味着结果中应该有
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) |
此代码的一个优点是,您只需要对结果(布尔值)项进行一次迭代。
一个缺点是,所有这些真值表达式都是经过评估的,并且不像