如何创建可变变量?

546
我知道一些其他语言,比如PHP,支持“变量变量名”的概念 - 也就是说,字符串的内容可以作为变量名的一部分。
我听说这在一般情况下是一个不好的主意,但我认为它可以解决我在Python代码中遇到的一些问题。
在Python中是否可能做到这样的事情?可能会出现什么问题?
如果你只是想通过变量名查找现有的变量,请参考如何通过(字符串)名称选择变量?。然而,首先考虑是否可以重新组织代码以避免这种需求,按照这个问题中的建议。

84
这是系统的运维和调试方面造成的问题。想象一下在代码中根本没有改变“foo”变量值的情况下,如何找到它被修改了的位置。更进一步,假设你需要维护的是别人的代码...好了,现在你可以回到愉快的地方了。 - glenn jackman
18
到目前为止还没有提到的另一个陷阱是,如果这样一个动态创建的变量与您的逻辑中使用的变量同名,那么您基本上会将您的软件开放为输入所控制的人质。 - holdenweb
6
你可以通过访问全局和局部变量的底层字典来修改它们;从维护的角度来看这是个糟糕的想法... 但是可以通过 globals().update() 和 ***locals().update()(或者保存任一其中的字典引用,并像使用其他任何字典一样使用它)来实现。不建议这样做*... 但你应该知道它是可能的。 - Jim Dennis
3
实际上,不行。对locals返回的字典所做的修改不会影响CPython中的本地命名空间。这是另一个不建议这样做的原因。 - juanpa.arrivillaga
2
@juanpa.arrivillaga:我曾尝试在IPython shell中进行测试,但是在顶层执行(其中locals()的行为类似于全局变量)。在嵌套代码(在函数定义内部)中重新进行该测试确实显示我无法从其中修改locals()。正如您所说,locals(3.7.6)的帮助文档警告:“注意:更新此字典是否会影响本地范围中的名称查找以及反之亦然取决于实现,而不受任何向后兼容性保证。” - Jim Dennis
显示剩余3条评论
18个回答

437
你可以使用字典来完成这个任务。字典是键值对的存储容器。
>>> dct = {'x': 1, 'y': 2, 'z': 3}
>>> dct
{'x': 1, 'y': 2, 'z': 3}
>>> dct["y"]
2

你可以使用变量键名来实现变量变量的效果,而不会带来安全风险。
>>> x = "spam"
>>> z = {x: "eggs"}
>>> z["spam"]
'eggs'

对于那些考虑要做类似以下操作的情况

var1 = 'foo'
var2 = 'bar'
var3 = 'baz'
...

在编程中,列表可能比字典更合适。列表表示对象的有序序列,具有整数索引:

lst = ['foo', 'bar', 'baz']
print(lst[1])           # prints bar, because indices start at 0
lst.append('potatoes')  # lst is now ['foo', 'bar', 'baz', 'potatoes']

对于有序序列,列表比具有整数键的字典更方便,因为列表支持按索引顺序迭代、切片操作append和其他需要通过字典进行繁琐键管理的操作。


116

使用内置的getattr函数,通过名称获取对象上的属性。根据需要修改名称。

obj.spam = 'eggs'
name = 'spam'
getattr(obj, name)  # returns 'eggs'

94

这不是个好主意。如果你正在访问全局变量,可以使用 globals()

>>> a = 10
>>> globals()['a']
10
如果您想访问局部作用域中的变量,可以使用locals(),但是您不能为返回的字典分配值。
更好的解决方案是使用getattr或将变量存储在字典中,然后通过名称进行访问。

2
“locals().update({'new_local_var':'some local value'})”在Python 3.7.6中完全适用,所以当你说你不能通过它分配值时,我不确定你的意思是什么。 - Jim Dennis
假设 x = "foo" 并且 locals()["x"] = "bar",在 Jython 2.5.2 中使用 print x 将输出 bar。这已经在 [tag:maximo] 的一个按需自动化脚本中进行了测试。 - Preacher
7
locals() 的文档明确表示:"这个字典的内容 不应该 被修改。"(强调为我所加) - martineau
@JimDennis`locals()``提供一个字典"用来表示"局部变量。更新它并不能保证更新"实际的"局部变量。在现代Python实现中,它更像是一张图片(显示内容)放在一个漂亮的画框(高级“dict”)中 - 在图片上作画实际上不会改变真实的东西。 - MisterMiyagi
@JimDennis:你很可能在一个不是函数局部变量的上下文中使用它。在实际的函数中,真正的局部变量并没有改变。当然,你可以将locals()的结果保存起来并从中读取/写入,但它永远不会改变真正的局部变量,你只是得到了一个dict快照并修改了该dict。在它适用的情况下,它实际上不是局部变量(至少在CPython上不是)。 - ShadowRanger
3
它不能工作的原因,至少在CPython上,是因为CPython为局部变量分配了一个固定大小的数组,并且该数组的大小在函数定义时确定,而不是在运行时确定,并且无法更改(对真正的局部变量的访问甚至不使用名称;名称在函数编译时被替换为数组中的索引)。locals()返回一个真正的dict;在函数内部,当您调用locals()时,该dict通过将名称和关联值加载到数组中来创建,它不会看到未来的更改。如果它发生了变化,那么你就处于全局或类范围内(它们使用dict作用域)。 - ShadowRanger

82

新手程序员有时会写出这样的代码:

my_calculator.button_0 = tkinter.Button(root, text=0)
my_calculator.button_1 = tkinter.Button(root, text=1)
my_calculator.button_2 = tkinter.Button(root, text=2)
...

程序员最终会得到一堆命名的变量,其编码工作量为O(m * n),其中m是命名变量的数量,n是需要访问该组变量的次数(包括创建)。更加敏锐的初学者观察到,每行代码中唯一的区别是基于规则变化的数字,并决定使用循环。然而,他们被困在如何动态创建这些变量名上,可能会尝试类似以下的方法:

for i in range(10):
    my_calculator.('button_%d' % i) = tkinter.Button(root, text=i)

他们很快发现这样行不通。

如果程序需要使用任意变量“名称”,则字典是最佳选择,如其他答案中所述。然而,如果您只是尝试创建许多变量,并且您不介意使用一系列整数来引用它们,那么您可能正在寻找一个 列表。如果您的数据是同质的,例如每日温度读数、每周测验得分或图形小部件网格,则特别适用此选项。

可以按以下方式组装:

my_calculator.buttons = []
for i in range(10):
    my_calculator.buttons.append(tkinter.Button(root, text=i))

使用推导式也可以在一行内创建此list

my_calculator.buttons = [tkinter.Button(root, text=i) for i in range(10)]
无论哪种情况,结果都是一个填充的list,第一个元素可以通过my_calculator.buttons[0]访问,下一个可以通过my_calculator.buttons[1]以此类推。 "base"变量名成为list的名称,并且使用可变标识符来访问它。
最后,不要忘记其他数据结构,例如set - 这类似于一个字典,但是每个“名称”没有附加值。如果您只需要一组对象,则这可能是一个很好的选择。而不是像这样:
keyword_1 = 'apple'
keyword_2 = 'banana'

if query == keyword_1 or query == keyword_2:
    print('Match.')

你会得到这个:

keywords = {'apple', 'banana'}
if query in keywords:
    print('Match.')

使用list来表示一系列相似的对象,使用set来表示一个任意顺序的对象集合,使用dict来表示带有相关值的名称集合。


48

每当您想使用可变变量时,最好使用字典。因此,不要编写

$foo = "bar"
$$foo = "baz"

你写代码

mydict = {}
foo = "bar"
mydict[foo] = "baz"

这样做可以避免意外覆盖先前存在的变量(这是安全方面的考虑),并且您可以拥有不同的“名称空间”。


32

使用 globals()(免责声明:这是一种不好的做法,但是对于您的问题来说,这是最直接的答案,请使用其他数据结构,如被接受的答案中所示)。

实际上,您可以动态地将变量分配给全局作用域,例如,如果您想要 10 个可以在全局范围内访问的变量 i_1i_2 ... i_10

for i in range(10):
    globals()['i_{}'.format(i)] = 'a'

这将把'a'分配给这10个变量,当然你也可以动态地改变值。所有这些变量现在都可以像其他全局声明的变量一样被访问:

>>> i_5
'a'

18

除了使用字典,您还可以使用collections模块中的namedtuple,这使得访问更加方便。

例如:

# using dictionary
variables = {}
variables["first"] = 34
variables["second"] = 45
print(variables["first"], variables["second"])

# using namedtuple
Variables = namedtuple('Variables', ['first', 'second'])
v = Variables(34, 45)
print(v.first, v.second)

4
记住,namedtuple是不可变的,所以它们与仅带点符号的字典有些不同。话虽如此,这两种选项都促进了良好的设计原则,并且不像本主题中一半的答案那样滥用全局命名空间。 - ggorlen

17

SimpleNamespace类可以用于使用setattr创建新属性,或者子类化SimpleNamespace并创建您自己的函数来添加新属性名称(变量)。

from types import SimpleNamespace

variables = {"b":"B","c":"C"}
a = SimpleNamespace(**variables)
setattr(a,"g","G")
a.g = "G+"
something = a.a

14

如果您不想使用任何对象,仍然可以在当前模块中使用setattr()

import sys
current_module = module = sys.modules[__name__]  # i.e the "file" where your code is written
setattr(current_module, 'variable_name', 15)  # 15 is the value you assign to the var
print(variable_name)  # >>> 15, created from a string

然而,这不能与__dict__变量一起使用。我想知道是否有一种通用机制可以动态创建任何全局变量。 - Alexey
2
globals() 可以做到这一点。 - Guillaume Lebreton

13

Python中的变量变量

"""
<?php
$a = 'hello';
$e = 'wow'
?>
<?php
$$a = 'world';
?>
<?php
echo "$a ${$a}\n";
echo "$a ${$a[1]}\n";
?>
<?php
echo "$a $hello";
?>
"""

a = 'hello'  #<?php $a = 'hello'; ?>
e = 'wow'   #<?php $e = 'wow'; ?>
vars()[a] = 'world' #<?php $$a = 'world'; ?>
print(a, vars()[a]) #<?php echo "$a ${$a}\n"; ?>
print(a, vars()[vars()['a'][1]]) #<?php echo "$a ${$a[1]}\n"; ?>
print(a, hello) #<?php echo "$a $hello"; ?>

输出:

hello world
hello wow
hello world
使用globals()、locals()或vars()将产生相同的结果。
#<?php $a = 'hello'; ?>
#<?php $e = 'wow'; ?>
#<?php $$a = 'world'; ?>
#<?php echo "$a ${$a}\n"; ?>
#<?php echo "$a ${$a[1]}\n"; ?>
#<?php echo "$a $hello"; ?>

print('locals():\n')
a = 'hello'
e = 'wow'
locals()[a] = 'world'
print(a, locals()[a])
print(a, locals()[locals()['a'][1]])
print(a, hello)

print('\n\nglobals():\n')
a = 'hello'
e = 'wow'
globals()[a] = 'world'
print(a, globals()[a])
print(a, globals()[globals()['a'][1]])
print(a, hello)

输出:

locals():

hello world
hello wow
hello world


globals():

hello world
hello wow
hello world

奖励(从字符串创建变量)

# Python 2.7.16 (default, Jul 13 2019, 16:01:51)
# [GCC 8.3.0] on linux2

创建变量和解包元组:

g = globals()
listB = []
for i in range(10):
    g["num%s" % i] = i ** 10
    listB.append("num{0}".format(i))

def printNum():
    print "Printing num0 to num9:"
    for i in range(10):
        print "num%s = " % i, 
        print g["num%s" % i]

printNum()

listA = []
for i in range(10):
    listA.append(i)

listA = tuple(listA)
print listA, '"Tuple to unpack"'

listB = str(str(listB).strip("[]").replace("'", "") + " = listA")

print listB

exec listB

printNum()

输出:

Printing num0 to num9:
num0 =  0
num1 =  1
num2 =  1024
num3 =  59049
num4 =  1048576
num5 =  9765625
num6 =  60466176
num7 =  282475249
num8 =  1073741824
num9 =  3486784401
(0, 1, 2, 3, 4, 5, 6, 7, 8, 9) "Tuple to unpack"
num0, num1, num2, num3, num4, num5, num6, num7, num8, num9 = listA
Printing num0 to num9:
num0 =  0
num1 =  1
num2 =  2
num3 =  3
num4 =  4
num5 =  5
num6 =  6
num7 =  7
num8 =  8
num9 =  9

1
varslocals字典不能在函数内部被修改。而可变全局状态是不好的,除非在最简单的脚本中可能会有一些用处。因此,这只有有限的用途。 - wjandrea

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