“如果a或b或c但不是全部”的Python语法


130

我有一个可以接收零个或三个命令行参数的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):


4
可能从类似if if len(sys.argv)== 0的东西开始:
Edgar Aroutiounian 2013年

6
@EdgarAroutiounian len(sys.argv)将始终至少为1:它将可执行文件包括为argv[0]
RoadieRich

10
问题的正文与问题的标题不匹配。您是否要检查“是a,b还是c,但不是全部”或“如果a,b和c恰好是其中之一”(就像您给出的表达式一样)?
道格·麦克林

2
关于a + b + c,您能说什么?
gukoff 2013年

6
等等,问题可能是零或三个参数。您不能只说if not (a and b and c)(零个参数),然后再说if a and b and c(所有三个参数)吗?
侍酒师

Answers:


236

如果您的意思是最小形式,请执行以下操作:

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

这将翻译您的问题的标题。

更新:正如Volatility和Supr正确地说的那样,您可以应用De Morgan的定律并获得等效项:

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

我的建议是使用对您和其他程序员更重要的形式。第一个意思是“有一些错误,但也有一些真实”,第二个意思是“有一些真实,但不是一切”。如果要在硬件上进行优化或执行此操作,则选择第二个,这里只选择最易读的代码(还要考虑要测试的条件及其名称)。我选了第一个。


3
所有的答案都很好,但是这样做简洁明了,而且电路短路。谢谢大家!
克里斯·威尔逊

38
我会做得更简洁,然后去做if not (a and b and c) and (a or b or c)
波动性

208
甚至if (a or b or c) and not (a and b and c));完美匹配标题
SUPR

3
@HennyH我相信这个问题要求“至少一个条件成立,但不是全部”,而不是“只有一个条件成立”。
波动率

63
@Suprif any([a,b,c]) and not all([a,b,c])
eternalmatt

238

怎么样:

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

其他变体:

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

2
sum(conditions)如果其中任何一个返回2,例如,可能会出错True
eumiro

7
的确,您需要一个丑陋的东西sum(map(bool, conditions))
jamylak

5
请注意,这并不是短路,因为所有条件都已预先评估。
乔治,

14
@PaulScheltema第一种形式对任何人都更容易理解。
cmh

6
这种“任何但不是全部”是布尔方法中最好,最清晰的方法,请注意arg存在与arg是“真实的”之间的重要区别
2013年

115

这个问题已经有很多被高度认可的答案和一个被接受的答案,但是到目前为止,所有这些都被表达布尔问题的各种方法分散了注意力,并且错过了关键点:

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

首先,此逻辑不应由您的代码负责,而应由argparse模块处理。不用费心编写复杂的if语句,而喜欢设置参数解析器,如下所示:

#!/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)

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


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

#!/usr/bin/env python
import argparse as ap
parser = ap.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
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

4
是的,我是故意添加的。可以将参数设置为位置,并强制使用3或0,但这并不能提供良好的CLI,因此我不建议这样做。
2013年

8
单独的问题。您不认为这是一个很好的CLI,您可以为此辩护,并且可以说服OP。但是您的答案与问题的偏离很大,因此需要提及规格的更改。您似乎在弯腰规格以适应可用工具,而没有提及更改。
LarsH 2013年

2
@LarsH好,我添加了一个示例,该示例更适合问题中隐含的原始接口。现在它正在弯曲工具以满足现有规格...;)
2013年

2
这是我支持的唯一答案。+1回答真实的问题
乔纳森·莱因哈特

1
+1。CLI的形式是一个重要的切向问题,并不像另一个人所说的那样完全独立。我对您和其他人的帖子都进行了投票-您的帖子成为问题的根源并提供了一个优雅的解决方案,而其他帖子则回答了字面上的问题。两种答案都是有用的,值得+1。
李·李

32

我会去:

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

我认为这应该有效地短路

说明

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

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


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

not all(conds) and any(conds)

5
我不明白 它看起来像:如果为True而不是True。帮我明白
rGil

1
@rGil:读起来就像“如果有些苹果是红色的,而有些不是红色的” –就像说“有些苹果是红色的,但不是所有的红色”一样。
乔治,

2
即使有解释,我也无法理解行为...用[a, b, c] = [True, True, False]您的代码不应该“打印” False,而预期输出是True
awesoon,

6
但是,这非常聪明,但是:如果您不知道预先有多少条件,我会使用这种方法,但是对于固定的已知条件列表,失去可读性根本不值得。
蓬松的

4
这不会短路。该列表在传递给之前已完全构建iteranyall会懒洋洋地消耗名单,真实的,但名单已经完全被你到达那里的时候评价过!
icktoofay

22

英文句子:

“如果是a或b或c,但不是全部”

转换为以下逻辑:

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

单词“但是”通常表示连词,即“和”。此外,“所有这些”转换为的条件结合:这种情况下,该条件下,其它条件。“ not”反转整个连接。

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

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

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


实际上,这种形式是最小的。这是表达式的最小PoS形式
Stefano Sanfilippo

10

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

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

不如@defuz的答案那么好
jamylak

10

关于:(唯一条件)

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

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

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

1
为了记录,问题要求两个条件。至少一个,但不是全部= 1个或2个
MariusBalčytis13年

恕我直言,您应该将第二个拼写为1 <= bool(a) + bool(b) + bool(c) <= 2
恢复莫妮卡

6

需要明确的是,您要基于多少个参数为逻辑TRUE(在使用字符串参数的情况下-不为空)做出决定?

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

然后您做出了决定:

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

现在的逻辑更加清晰了。


5

为何不仅仅计算它们呢?

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

5

如果您不介意有点晦涩难懂,则可以简单地滚动一下0 < (a + b + c) < 3true如果您有一个和两个真实的陈述,则返回,如果全部为假或没有为假,则返回false。

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


4

问题表明您需要全部三个参数(a和b以及c),或者都不需要(而不是(a或b或c))

这给出:

(a and b and c)否(a或b或c)


4

据我了解,您有一个接收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”值不会传递到此方法中。


检查参数的真值与检查参数是否存在是不同的
2013年

@wim因此正在转换问题以适合您的答案。argparse模块与该问题无关,它添加了另一个导入,并且如果OP根本不打算使用argparse,那么它将无济于事。同样,如果“脚本”不是独立的,而是较大代码集中的模块或函数,则它可能已经具有参数解析器,并且该较大脚本中的此特定函数可以是默认的或自定义的。由于来自OP的信息有限,我不知道该方法应如何起作用,但是可以安全地假设OP没有通过bool。
Inbar Rose

问题明确指出“我有一个可以接收零个或三个命令行参数的python脚本”,而没有说“我有一个可以接收3个参数的函数”。由于argparse模块是在python中处理命令行参数的首选方式,因此它自动与问题相关。最后,python是“包含电池”的模块-当该模块是标准库的一部分时,“添加另一个导入”没有任何不利之处。
2013年

@wim问题尚不清楚(例如,正文与标题不匹配)。我认为这个问题还不够清楚,以至于这是对它的某种解释的有效答案。
恢复莫妮卡

2

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

#!/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])

0

当每一个给定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)

我也写了一些处理多个可迭代对象的代码,但是我从这里删除了它,因为我认为这没有意义。但是,这里仍然可用。


-2

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

这意味着结果之间应该有Falses True s。因此,您可以执行以下操作:

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)

此代码的优点之一是,您只需要对结果(布尔值)项目进行一次迭代。

缺点之一是,所有这些真值表达式总是被求值,并且不像/ 运算符那样发生短路orand


1
我认为这是不必要的并发症。为什么要使用固定集而不是普通的旧集?为什么.issuperset不仅要检查长度2,bool还不能返回True和False。为什么要给一个lambda(读:匿名函数)分配一个名称,而不是仅仅使用def?
2013年

1
lambda语法对某些人来说更合乎逻辑。无论如何,它们的长度相同,因为您需要return使用def。我认为这种解决方案的普遍性很好。不必将自己限制为布尔值,本质上,问题是“如何确保所有这些元素都出现在列表中”。为什么set您不需要可变性?如果您不需要性能,那么不变性总是更好的。
Janus Troelsen

@JanusTroelsen您的目标正确!这些是我这样做的一些原因;它使我更轻松,更清晰。我倾向于让Python适应我的编码方式:-)。
Abbafei 2013年

但它不适用于无限发电机:P请参阅我的回答:)提示:tee
Janus Troelsen,

@ JanusTroelsen我意识到这一点:-)。实际上,我最初是用另一种方法解决的(在集合中使用True / False,在方法参数中使用iterable),但是意识到这不适用于无限生成器,并且用户可能没有意识到(因为事实并非如此) (还)在文档中提到了可迭代的set方法参数,并且至少像这样,很明显,它不会采用无限迭代器。我知道,itertools.tee但是1)我一直在寻找一种简单/小到足以保证粘贴粘贴的
单线
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.