如何测试多个变量是否等于单个值?

842

我想编写一个函数,将多个变量与整数进行比较,并输出三个字母的字符串。我想知道是否有办法将其转换为Python代码。例如:

x = 0
y = 1
z = 3
mylist = []

if x or y or z == 0:
    mylist.append("c")
if x or y or z == 1:
    mylist.append("d")
if x or y or z == 2:
    mylist.append("e")
if x or y or z == 3: 
    mylist.append("f")

将返回一个列表:

["c", "d", "f"]

8
在元组中使用1 - user9011445
7
如果你想以任何/所有的方式评估一系列语句,可以使用any/all函数。 例如: all([1, 2, 3, 4, False]) 将返回 False all([True, 1, 2, 3]) 将返回 True any([False, 0, 0, False]) 将返回 False any([False, 0, True, False]) 将返回 True - eddd
11
这个问题是一个非常流行的重复目标,但我认为它不适合这个目的。大多数人尝试像 if x == 0 or 1: 这样做,当然类似于 if x or y == 0:,但对新手来说可能仍然有点困惑。考虑到 "为什么我的 'x == 0 or 1' 不起作用?" 这类问题的数量之多,我更愿意使用 这个问题 作为我们的标准重复目标。 - Aran-Fey
2
在与 00.0False 等“假值”进行比较时要特别小心。你很容易写出错误的代码,从而得到“正确”的答案。 - smci
显示剩余3条评论
31个回答

1117
您误解了布尔表达式的工作方式;它们不像英语句子那样工作,猜测您在这里谈论所有名称的相同比较。您要寻找的是:
if x == 1 or y == 1 or z == 1:

xy 将按照它们自己的方式进行评估(如果是0则为False,否则为True)。

您可以使用针对元组的包含性测试来缩短它:

if 1 in (x, y, z):

或者更好的是:

if 1 in {x, y, z}:

使用 一个 set 来利用常量成员测试(即,in 无论左操作数是什么都需要花费固定的时间)。

说明

当您使用 or 时,Python 将运算符的每个侧面视为 独立的 表达式。表达式 x or y == 1 被视为首先对 x 进行布尔测试,然后如果该测试结果为 False,则测试表达式 y == 1

这是由于 运算符优先级or 运算符的优先级低于 == 测试,因此后者会被首先计算。

然而,即使不这样做,如果表达式 x or y or z == 1 实际上被解释为 (x or y or z) == 1,它仍然无法达到你的预期目的。在这种情况下,x or y or z 将计算为第一个“真值”参数,例如不是 False、数字0或空(有关Python在布尔上下文中认为什么是false的详细信息,请参见boolean expressions)。因此,对于值 x=2; y=1; z=0x or y or z 将解析为 2,因为它是参数中第一个类似true的值。然后,2 == 1 将是 False,即使 y == 1True
对于相反的情况也是一样的;测试多个值与单个变量的比较; x == 1 or 2 or 3 会因为同样的原因失败。使用 x == 1 or x == 2 or x == 3x in {1, 2, 3}

155
我不会轻易选择使用 set 版本。元组创建和迭代非常便宜。至少在我的电脑上,只要元组的大小大约为4-8个元素,元组就比集合更快。如果你需要扫描的元素超过这个范围,请使用集合,但如果你正在寻找2-4个可能性之一的项目,则元组仍然更快!如果您能够安排最有可能的情况出现在元组的第一位,那么胜利将会更加显着:(我的测试:timeit.timeit('0 in {seq}'.format(seq=tuple(range(9, -1, -1)))) - SingleNegationElimination
71
在Python 3.3及以上版本中,集合被存储为常量,完全绕过了创建时间,从而消除了创建时间。元组的创建可能会很廉价,因为Python缓存了一组元组以避免内存波动,这是它与集合最大的不同之处。 - Martijn Pieters
18
如果仅仅测试成员身份,对于整数而言,元组和集合在理想情况下的速度是相等的,因为它们匹配第一个元素。但在此之后,元组的速度逊于集合。 - Martijn Pieters
26
使用set字面值符号进行测试并不能节省时间,除非set字面值里的内容也是字面值。因为if 1 in {x, y, z}:无法缓存set,因为xyz可能会改变,所以任何一种解决方案都需要从头开始构建一个tuple或者set。我怀疑当检查成员身份时,所能获得的任何查找优势都将被更大的set创建时间所淹没。 - ShadowRanger
14
@ShadowRanger:是的,窥孔优化(无论是针对in [...]还是in {...})只有在列表或集合的内容也是不可变字面量时才起作用。未来的语言更新可能会改变这一点。 - Martijn Pieters
显示剩余14条评论

118

你的问题可以更容易地通过使用类似字典结构的方式解决:

x = 0
y = 1
z = 3
d = {0: 'c', 1:'d', 2:'e', 3:'f'}
mylist = [d[k] for k in [x, y, z]]

27
甚至可以将 d = "cdef" 赋值给变量,即 d = "cdef", 然后通过列表推导式 MyList = ["cdef"[k] for k in [x, y, z]] 来创建一个包含索引值为 x、y、z 对应的字母的列表。 - aragaer
11
或者 map(lambda i: 'cdef'[i], [x, y, z]) - dansalmo
2
除了列表推导式,我还没有完全习惯,我们大多数人都有同样的反应:构建字典! - LoneWanderer

78

正如Martijn Pieters所述,正确且最快的格式为:

if 1 in {x, y, z}:

使用他的建议,现在您将拥有独立的if语句,这样Python会读取每个语句,不管之前的语句是True还是False。例如:

if 0 in {x, y, z}:
    mylist.append("c")
if 1 in {x, y, z}:
    mylist.append("d")
if 2 in {x, y, z}:
    mylist.append("e")
...

这样做可以生效,但是如果您熟悉使用字典(看到我做了什么了吗),您可以通过创建一个初始字典将数字映射为所需的字母,然后只需使用for循环来简化代码:

num_to_letters = {0: "c", 1: "d", 2: "e", 3: "f"}
for number in num_to_letters:
    if number in {x, y, z}:
        mylist.append(num_to_letters[number])

1
@VisioN 你是指 for number in num_to_letters 吗?你不需要 .keys(),字典默认会迭代键。关于使用字符串,你是指像这样吗?for i, c in enumerate('cdef'): if i in {x, y, z}: mylist.append(c)同意,那会更简单。或者更好的方法是,s = 'cdef'; mylist = [s[i] for i in [x, y, z]] - wjandrea
@wjandrea 是的,你说得对,这是我的错误!我完全忘记了默认行为。不幸的是,我不能编辑我的评论,所以我已经将其删除,因为你在评论中提出了更好的方法。 - VisioN

55

x or y or z == 0 的直接方式是:

if any(map((lambda value: value == 0), (x,y,z))):
    pass # write your logic.

但我认为你不喜欢它。 :) 而且这种方式很丑陋。

另一种(更好的)方法是:

0 in (x, y, z)

顺便说一下,很多if语句可以像这样编写

my_cases = {
    0: Mylist.append("c"),
    1: Mylist.append("d")
    # ..
}

for key in my_cases:
    if key in (x,y,z):
        my_cases[key]()
        break

9
在你提到的dict的例子中,如果使用.append方法作为键,你会得到错误信息,因为.append的返回值是None,调用None会引发AttributeError异常。不过总体来说,我同意这种方法。 - SethMMorton
4
字典初始化时使用列表作为值而非键是错误的,即使你注释掉了“for...循环”部分,你也会得到Mylist=['c', 'd']。 - Mahmoud Elshahat
2
在你的第一个例子中,filtermap 更好,因为它只会返回 lambda 表达式评估为 true 的实例。 - Alex
4
理解一个表达式比理解一个 lambda 函数的映射要简单得多:any(v == 0 for v in (x, y, z)) - wjandrea

36

如果你非常懒,可以将值放在一个数组中,例如:

list = []
list.append(x)
list.append(y)
list.append(z)
nums = [add numbers here]
letters = [add corresponding letters here]
for index in range(len(nums)):
    for obj in list:
        if obj == num[index]:
            MyList.append(letters[index])
            break

您也可以将数字和字母放入字典中进行操作,但这可能比仅使用if语句复杂得多。这就是你尝试变得更加懒惰时会遇到的问题 :)

还有一件事,您的

if x or y or z == 0:

它会编译,但不是你想要的方式。当你仅在if语句中放置一个变量(例如)

if b

该程序将检查变量是否不为空。另一种更好理解的写法是:

if bool(b)

Bool 是 Python 中的一个内置函数,用于验证布尔语句(如果你不知道这是什么,那么现在你正在尝试编写 if 语句中的内容:))

我还发现了另一种懒人方法:

if any([x==0, y==0, z==0])

8
这里有很多不好的做法。list是Python内置的,应该使用另一个名称,比如xyz。为什么要用四个步骤构建列表,如果可以一步完成呢,即xyz = [x, y, z]?不要使用平行列表,而是使用字典。总的来说,这个解决方案比ThatGuyRussell的要复杂得多。对于最后一部分,为什么不使用推导式,即any(v == 0 for v in (x, y, z))?此外,在Python中还有另一种数据类型叫做arrays - wjandrea

35

要检查一个值是否包含在一组变量中,您可以使用内置模块itertoolsoperator

例如:

导入:

from itertools import repeat
from operator import contains

声明变量:

x = 0
y = 1
z = 3
创建值的映射(按您要检查的顺序):
check_values = (0, 1, 3)

使用 itertools 允许变量重复:

check_vars = repeat((x, y, z))

最后,使用 map 函数创建一个迭代器:

checker = map(contains, check_vars, check_values)

接着,在检查值(按原始顺序)时,请使用 next()

if next(checker)  # Checks for 0
    # Do something
    pass
elif next(checker)  # Checks for 1
    # Do something
    pass

这种方法比lambda x: x in (variables)更优,因为operator是一个内置模块,比使用lambda创建一个自定义的函数要更快、更高效。

另一种检查列表中是否存在非零(或False)值的选项:

not (x and y and z)

等价的:

not all((x, y, z))

3
这并没有回答原帖的问题,只涵盖了提供示例中的第一个情况。 - wallacer

33

使用 Set 是一个好的方法,因为它会对变量进行排序,这似乎是你在这里的目标。无论参数的顺序如何,{z,y,x} 都是 {0,1,3}

>>> ["cdef"[i] for i in {z,x,y}]
['c', 'd', 'f']

这种方式,整个解决方案的时间复杂度为O(n)。


32
如果您想使用if、else语句,则以下是另一种解决方案:
myList = []
aList = [0, 1, 3]

for l in aList:
    if l==0: myList.append('c')
    elif l==1: myList.append('d')
    elif l==2: myList.append('e')
    elif l==3: myList.append('f')

print(myList)

32

我认为这样处理会更好:

my_dict = {0: "c", 1: "d", 2: "e", 3: "f"}

def validate(x, y, z):
    for ele in [x, y, z]:
        if ele in my_dict.keys():
            return my_dict[ele]

输出:

print validate(0, 8, 9)
c
print validate(9, 8, 9)
None
print validate(9, 8, 2)
e

32

这里提供的所有优秀答案都集中在原帖的具体要求上,集中在Martijn Pieters提出的if 1 in {x,y,z}解决方案上。
它们忽略了问题的更广泛含义:
如何测试一个变量与多个值的匹配情况?
提供的解决方案如果使用字符串,将无法对部分匹配进行检测,例如:
测试字符串“Wild”是否在多个值中。

>>> x = "Wild things"
>>> y = "throttle it back"
>>> z = "in the beginning"
>>> if "Wild" in {x, y, z}: print (True)
... 
或者
>>> x = "Wild things"
>>> y = "throttle it back"
>>> z = "in the beginning"
>>> if "Wild" in [x, y, z]: print (True)
... 

对于这种情况,将其转换为字符串是最简单的。

>>> [x, y, z]
['Wild things', 'throttle it back', 'in the beginning']
>>> {x, y, z}
{'in the beginning', 'throttle it back', 'Wild things'}
>>> 

>>> if "Wild" in str([x, y, z]): print (True)
... 
True
>>> if "Wild" in str({x, y, z}): print (True)
... 
True

需要注意的是,正如@codeforester所提到的,使用这种方法会丢失单词边界,例如:

>>> x=['Wild things', 'throttle it back', 'in the beginning']
>>> if "rot" in str(x): print(True)
... 
True

在列表中,的确存在由3个字母rot组合而成的单词,但是它们不会作为一个独立的单词出现。如果搜索" rot ",测试将失败,但如果其中一个列表项是"rot in hell",那么也会失败。
因此,如果使用这种方法,请注意搜索条件并知道它有这个限制。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接