同时声明多个变量的更优雅方法

163

为了同时声明多个变量,我会这样做:

a, b = True, False

但是如果我必须声明更多的变量,它会变得越来越不简洁:

a, b, c, d, e, f, g, h, i, j = True, True, True, True, True, False, True ,True , True, True

有没有更好/优雅/方便的方法来做到这一点?

这可能非常基础,但如果我使用列表或元组来存储变量,我该如何处理才能更有帮助:

aList = [a,b]

这不是有效的,我需要做:

a, b = True, True

那我缺少了什么吗?


@JeffM:也许吧,但我不知道怎么做,似乎它们必须被定义才能属于列表(当然我可能是错的)。 - Trufa
3
如果你要声明这么多变量来存储值,这已经是一个信号表明你应该考虑其他的存储替代方案。 - Jeff Mercado
1
我觉得这些名字只是示例代码中的,Trufa 在他真正的代码中并没有使用那些名字。 - Chris Lutz
@Jeff 和 @Chris,我已经编辑了我的问题,因为我不知道如何使用列表或元组来解决我的问题。谢谢! - Trufa
你可以执行 a=b=True - Oliver Ni
显示剩余5条评论
10个回答

262
a, b, c, d, e, g, h, i, j = (True,)*9
f = False

24
末尾加逗号(d,)是一种创建包含一个元素的元组(一种序列类型)的方法。序列类型支持加法和乘法等操作。 - duozmo
40
这是一个巧妙的技巧,但要注意不要在可变元素(例如列表)上使用。例如,a, b, c = ([],)*3 不会创建 3 个列表实例,而是使 abc 指向同一个实例。 - Zac
6
我常常花费很多时间来尝试使我的 Python 代码变得更加符合 Pythonic 风格,不知道这样是否浪费了太多时间? - SARose
7
@Zac 如需正确实现,请使用a, b, c = ([] for i in range(3))来源。为了保持一致性,您还可以在这个答案中使用它的变体,即a,b,c,d,e,g,h,i,j = (True for i in range(9))f=(False i in range(1)) - Novice C
这是一个例子,说明我为什么喜欢Python,虽然最近才开始使用它.. 我希望早些时候就开始了.. 但是Web开发因项目而异。 - Angry 84
我不认为这些变量之间有什么不同,而只是彼此的副本。如果您更改其中任何一个,所有变量都会自动更改。 - pyAddict

65

使用列表/字典或定义自己的类来封装您要定义的内容,但如果您需要所有这些变量,可以这样做:

a = b = c = d = e = g = h = i = j = True
f = False

1
变量不仅会被设置为True和False。 - N 1.1
1
@N1.1:我无法理解你的意思。 - Trufa
7
为了解释为什么这是一种危险的模式(尽管是一个可行的例子),请阅读关于“臭名昭著的B.I.G.”的内容。详情请见链接:https://dev59.com/dWQo5IYBdhLWcg3wE74J#16349356。 - duozmo
如果你想分配对象而不是字面值(如True或False),那么这是一个有风险的解决方案。你可能不希望有许多变量指向/访问同一个对象。 - J punto Marcos
@duozmo 看起来结论是单个变量是安全的,但当它们是列表时就存在危险。因此,在已链接的名称中重新分配列表条目可能是危险的(不直观)。例如,>>a=b=[1,2] > >>a[0]=3 > >>print b > [3,2]。自 >>a=b 以来,b[0] 的值也发生了变化。然而,这似乎是安全的(直观的):>>a=b=2 > >>a=3 > >>print b > 2 - Novice C
显示剩余4条评论

61

正如其他人所建议的那样,使用10个具有布尔值的本地变量来编写您的程序可能不是最好的方式(特别是如果它们真的只有一个字母的名称 :))。

根据您正在做的事情,使用字典可能是更好的选择。例如,如果您想为一组单字标志设置布尔预设值,则可以这样做:

>>> flags = dict.fromkeys(["a", "b", "c"], True)
>>> flags.update(dict.fromkeys(["d", "e"], False))
>>> print flags
{'a': True, 'c': True, 'b': True, 'e': False, 'd': False}

如果你愿意,你也可以使用单个赋值语句来完成:

>>> flags = dict(dict.fromkeys(["a", "b", "c"], True),
...              **dict.fromkeys(["d", "e"], False))
>>> print flags
{'a': True, 'c': True, 'b': True, 'e': False, 'd': False}
< p > dict的第二个参数并不完全是为了这个而设计的:它实际上是为了让您使用关键字参数(如d=False)来覆盖字典的单个元素。上面的代码将跟随**的表达式结果扩展为一组关键字参数,这些参数将传递给被调用的函数。这肯定是创建字典的可靠方法,人们似乎至少接受这种习惯用法,但我怀疑有些人可能认为这种写法不符合Pythonic风格。</disclaimer>


另一种方法,如果您经常使用此模式,则可能是最直观的方法,即将数据定义为标志值(TrueFalse)的列表,这些标志值映射到标志名称(单字符字符串)。 然后,您将转换此数据定义为反转字典,该字典将标志名称映射到标志值。 这可以使用嵌套列表推导式来完成得非常简洁,但以下是一个非常易读的实现:
>>> def invert_dict(inverted_dict):
...     elements = inverted_dict.iteritems()
...     for flag_value, flag_names in elements:
...         for flag_name in flag_names:
...             yield flag_name, flag_value
... 
>>> flags = {True: ["a", "b", "c"], False: ["d", "e"]}
>>> flags = dict(invert_dict(flags))
>>> print flags
{'a': True, 'c': True, 'b': True, 'e': False, 'd': False}

函数invert_dict是一个生成器函数。它会生成或者说产生,也就是重复返回键值对。这些键值对是初始flags字典的两个元素内容的反向。它们被传递给dict构造函数。在这种情况下,dict构造函数的工作方式与上面不同,因为它接收到的是迭代器而不是字典作为其参数。


参考@Chris Lutz的评论:如果您确实将使用此方法来处理单个字符值,您可以这样做

>>> flags = {True: 'abc', False: 'de'}
>>> flags = dict(invert_dict(flags))
>>> print flags
{'a': True, 'c': True, 'b': True, 'e': False, 'd': False}

这个方法可以工作是因为Python字符串是可迭代的iterable,意味着它们可以逐个值移动。对于字符串来说,这些值就是字符串中的单个字符。因此,在这种情况下,当它们被解释为可迭代对象时(例如在for循环中使用时),['a','b','c']'abc'是等效的。另一个例子是当它们被传递给需要可迭代对象(如tuple)的函数时。
我个人不会这样做,因为这样阅读起来并不直观:当我看到一个字符串时,我希望它被用作单个值而不是列表。所以我看第一行时会想:“好的,有一个True标志和一个False标志。”虽然这是一种可能性,但我认为这不是正确的方式。但好处是,它可能有助于更清晰地解释可迭代对象和迭代器的概念。
定义函数invert_dict以返回一个字典也不是一个坏主意;我之前没有这样做的主要原因是它并不能真正帮助解释这个例程的工作原理。
显���,Python 2.7具有字典理解,这将是实现该函数的一种非常简洁的方法。由于我没有安装Python 2.7,因此将其留给读者作为练习 :)
您还可以结合itertools模块的一些功能。正如他们所说,有多种方法来做到这一点。等等,Python的人们不这么说。 在某些情况下,这仍然是真的。 我猜测Guido给了我们字典理解,以便有一个明显的方法来做到这一点。

2
请注意,['a', 'b', 'c'] 可以缩写为 list('abc'),这启发了 def truth_values(trues, falses): d = dict.from_keys(list(trues), True); d.update(dict.from_keys(list(falses), False)); return d 的使用方式,如 values = truth_values("abc", "de") - Chris Lutz
5
我很惊讶你的回答:你定义了一个不同于问题作者的问题,并且喜欢针对这个问题写出一篇长篇回答。他并不想在字典中关联字符串和值,而是想为每个对象创建一个标识符和一个值。 - eyquem
5
@eyquem:长篇回答不好吗?医生,先治愈你自己吧! - John Machin
有时候,人们只是想完成工作,而不必担心是否采用了“最佳”方式。 - aqua
5
这并没有回答问题,还带有一些居高临下的态度。请看下面的答案。 - kodu
显示剩余9条评论

11
这是对@Jeff M和我评论的进一步阐述。
当您执行以下操作时:
a, b = c, d

它使用元组打包和解包。你可以分开打包和解包步骤:

_ = c, d
a, b = _

第一行创建了一个名为_的元组,它有两个元素,第一个元素的值是c,第二个元素的值是d。第二行将_元组解包到变量ab中。这样就可以将您的一行代码拆分成多个部分:

a, b, c, d, e, f, g, h, i, j = True, True, True, True, True, False, True, True, True, True

将其分为两行:
_ = True, True, True, True, True, False, True, True, True, True
a, b, c, d, e, f, g, h, i, j = _

它将给你与第一行完全相同的结果(包括如果您向一个部分添加值或变量但忘记更新另一个部分时发生的同样异常)。然而,在这种特定情况下,yan的答案可能是最好的。
如果您有一个值列表,仍然可以对其进行解包。您只需要先将其转换为元组。例如,以下内容将分别为aj中的每个变量分配0到9之间的值:
a, b, c, d, e, f, g, h, i, j = tuple(range(10))

编辑:一个巧妙的技巧是将它们全部赋值为true,除了第5个元素(变量f):

a, b, c, d, e, f, g, h, i, j = tuple(x != 5 for x in range(10))

我不知道最后一个是可能的,我一定会尝试一下,这确实是一个很好的技巧,可能会派上用场! - Trufa

5
当人们建议“使用列表、元组或其他数据结构”时,他们的意思是,当您有许多不同的值需要关注时,将它们全部命名为局部变量可能不是最佳方法。
相反,您可能希望将它们聚集到一个更大的数据结构中,该数据结构可以存储在单个局部变量中。
intuited 展示了如何使用字典实现这一点,Chris Lutz 展示了如何使用元组来进行临时存储,然后再解包成单独的变量,但是另一个要考虑的选项是使用 collections.namedtuple 来永久捆绑这些值。
因此,您可能会做出类似以下的事情:
# Define the attributes of our named tuple
from collections import namedtuple
DataHolder = namedtuple("DataHolder", "a b c d e f g")

# Store our data
data = DataHolder(True, True, True, True, True, False, True)

# Retrieve our data
print(data)
print(data.a, data.f)

真实的代码当然会使用比"DataHolder"和字母更有意义的名称。


谢谢您的回答,我会将其作为一种选择进行考虑。但是,对于这个特定情况而言,拥有一个不可变的结构可能并不实用。稍后我会在评论中说明结果如何,再次感谢! - Trufa
你可以使用普通类完成同样的事情 - DataHolder 的定义只是变得更加冗长。 - ncoghlan

4

实际上,问题是什么?

如果你真的需要或想要10个值,如a, b, c, d, e, f, g, h, i, j,那么在某个时候,你将不可避免地需要写下abc等。

如果这些值都不同,你将被迫写例如:

a = 12
b= 'sun'
c = A() #(where A is a class)
d = range(1,102,5)
e = (line in filehandler if line.rstrip())
f = 0,12358
g = True
h = random.choice
i = re.compile('^(!=  ab).+?<span>')
j = [78,89,90,0]

也就是说需要逐个定义“变量”。或者,使用另一种写法,不需要使用下划线(_):
a,b,c,d,e,f,g,h,i,j =\
12,'sun',A(),range(1,102,5),\
(line for line in filehandler if line.rstrip()),\
0.12358,True,random.choice,\
re.compile('^(!=  ab).+?<span>'),[78,89,90,0]

或者

a,b,c,d,e,f,g,h,i,j =\
(12,'sun',A(),range(1,102,5),
 (line for line in filehandler if line.rstrip()),
 0.12358,True,random.choice,
 re.compile('^(!=  ab).+?<span>'),[78,89,90,0])

如果其中一些必须具有相同的值,问题是写起来太长了。

(如果出现重复的值)可以使用数组或循环结构来简化代码。

a, b, c, d, e, f, g, h, i, j = True, True, True, True, True, False, True ,True , True, True 

?

Then you can write:

a=b=c=d=e=g=h=i=k=j=True
f = False

我不理解你的问题是什么。如果你想编写代码,就必须使用指令和定义所需的字符。还有什么?

我想知道你的问题是否表明你对某些事情有误解。

当一个人写下 a = 10 时,并不是创建一个变量,意思是“一块内存区域,其值可以改变”。这个指令:

  • 要么触发类型为整数,值为10的对象的创建,并将名称'a'与该对象在当前命名空间中绑定

  • 要么重新将命名空间中的名称'a'绑定到对象10(因为'a'先前已绑定到另一个对象)

我之所以这样说,是因为我看不出定义10个标识符a、b、c...指向False或True的实用性。如果这些值在执行过程中不会改变,为什么需要10个标识符?如果它们会改变,为什么要先定义这些标识符?如果没有事先定义,它们将在需要时创建。

你的问题对我来说很奇怪。


2

听起来你的解决问题的方式不太对。

重写你的代码,使用元组或编写一个类来存储所有的数据。


谢谢你,即使没有看到代码,你也能说出来:) 我明白这并不理想,我可能需要重新格式化,但你的回答并没有真正解决问题。不过,我理解你的观点。 - Trufa
1
我可以说听起来确实是那样的。这就是它的本意。重构很可能会解决您的问题。您的问题是一个风格问题,而不是功能性问题,所以很难提供除了相当元级建议之外的其他建议。 - richo

1

我喜欢得票最多的答案;然而,它在列表方面存在问题,如下所示。

  >> a, b = ([0]*5,)*2
  >> print b
  [0, 0, 0, 0, 0]
  >> a[0] = 1
  >> print b
  [1, 0, 0, 0, 0]

这个问题在(此处)有详细讨论,但要点是ab是同一个对象,a is b返回Trueid(a) == id(b)也是如此)。因此,如果你改变一个索引,你会同时改变ab的索引,因为它们是链接的。要解决这个问题,你可以(查看源代码)

>> a, b = ([0]*5 for i in range(2))
>> print b
[0, 0, 0, 0, 0]
>> a[0] = 1
>> print b
[0, 0, 0, 0, 0]

这样就可以将其用作顶部答案的一个变体,具有“期望”的直观结果。
>> a, b, c, d, e, g, h, i = (True for i in range(9))
>> f = (False for i in range(1)) #to be pedantic

1

JavaScript 类似,您也可以在 Python 中一行上使用多个语句 a = 1; b = "Hello World"; c += 3


0
在你的情况下,我会使用YAML。
这是一种优雅且专业的标准,用于处理多个参数。 值从单独的文件中加载。 您可以在此链接中查看一些信息:

https://keleshev.com/yaml-quick-introduction

不过用谷歌搜索会更容易些,因为这是一个标准,有数百条相关信息,你可以找到最适合你理解的部分。 ;)

此致敬礼。


嗨,Henrique,欢迎来到SO,感谢您的回答!在回答下一个问题之前,请确保阅读有关撰写答案的内容! - Diggy.

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