动态设置局部变量

117

如何在Python中动态设置本地变量(其中变量名是动态的)?


3
如果变量名是动态的,那么你做错了。 - agf
17
我曾经编写过程序,从输入文件中读取化学物种名称,然后根据读取到的文本创建相应的对象。通过这种方式,我可以使用类似 "H2O.mwt" 的形式来表示某个物种的分子量等信息。在我看来,这样做可能有其合理的原因。 - yosukesabai
1
我知道这是不好的做法。但在某些情况下,它可能是一种肮脏的捷径。此外,它很有趣,展示了语言的动态性。读者可以从反馈中得到启示,不会轻易使用它,这也是一个好处。但这并不是贬低它的有效理由。 - Jonathan Livni
6
快速局部变量数组与代码对象(即co_varnames、co_nlocals)耦合,是固定的。locals()只是调用PyFrame_FastToLocals来创建当前值的字典视图。如果使用ctypes在当前帧(sys._getframe(0))上调用PyFrame_LocalsToFast,可以根据这个字典动态更新快速局部变量。 - Eryk Sun
1
@eryksun - 听起来像是一个完整而深刻的答案的开端 - 如果你把它编译成一个,我会接受它。 - Jonathan Livni
1
@Jonathan:使用C API不是一个好的解决方案。在Python 2和3中,它确实可以让你动态更新函数中的本地变量(我不明白为什么要创建一个),但这是“hacky”的。即使是@kindall的答案在Python 3中也不起作用(编译的字节码仍然使用LOAD_GLOBAL)。据我所知,还没有尝试以标准方式简化此过程。 - Eryk Sun
7个回答

86

与其他已发布的答案相反,您不能直接修改locals()并期望它正常工作。

>>> def foo():
    lcl = locals()
    lcl['xyz'] = 42
    print(xyz)


>>> foo()

Traceback (most recent call last):
  File "<pyshell#6>", line 1, in <module>
    foo()
  File "<pyshell#5>", line 4, in foo
    print(xyz)
NameError: global name 'xyz' is not defined

修改locals()是未定义的。在函数外部,当locals()globals()相同时,它将起作用;但在函数内部,它通常不会起作用。

使用字典或在对象上设置属性:

d = {}
d['xyz'] = 42
print(d['xyz'])

或者如果您更喜欢,可以使用一个类:

class C: pass

obj = C()
setattr(obj, 'xyz', 42)
print(obj.xyz)

编辑: 通常使用字典查找来访问不是函数的命名空间中的变量(因此是模块、类定义、实例)。函数局部变量可以进行速度优化,因为编译器(通常)提前知道所有名称,所以在调用locals()之前没有字典。

在Python的C实现中,locals()(从函数内部调用)创建一个普通字典,其初始化为局部变量的当前值。在每个函数内,对locals()的任意数量调用将返回相同的字典,但每次调用locals()都会使用局部变量的当前值更新它。这可能会给人一种印象,即字典的元素赋值被忽略了(我最初写的是这种情况)。因此,对于从locals()返回的现有键的修改仅持续到同一作用域中下一次调用locals()

在IronPython中情况有些不同。任何调用其内部的locals()的函数都会使用一个字典作为其局部变量,因此对局部变量的赋值将更改字典,对字典的赋值将更改变量但是只有在将不同的名称绑定到IronPython中的locals函数时,才会这样做。如果绑定一个不同的名称到locals函数中,在调用它时会为您提供在绑定名称的作用域内的局部变量,并且无法通过它访问函数局部变量:

>>> def foo():
...     abc = 123
...     lcl = zzz()
...     lcl['abc'] = 456
...     deF = 789
...     print(abc)
...     print(zzz())
...     print(lcl)
...
>>> zzz =locals
>>> foo()
123
{'__doc__': None, '__builtins__': <module '__builtin__' (built-in)>, 'zzz': <built-in function locals>, 'foo': <function foo at 0x000000000000002B>, '__name__': '__main__', 'abc': 456}
{'__doc__': None, '__builtins__': <module '__builtin__' (built-in)>, 'zzz': <built-in function locals>, 'foo': <function foo at 0x000000000000002B>, '__name__': '__main__', 'abc': 456}
>>>

这一切都可能随时改变。唯一的保证是你不能依赖于对 locals() 返回的字典进行赋值的结果。


两个小修正:
  1. locals() 的返回值在 CPython 2.x 和 3.x 中是标准字典,可以像通常一样修改(但对本地作用域的更改不会传播回去)。
  2. 访问类和实例命名空间并不总是涉及字典查找。有几个例外情况,包括定义了 __slots__ 的类的实例。
- Sven Marnach
@CharlieParker,通常在调用函数时会更好。但在这种情况下,我建议你停止尝试更新本地变量:只需将值存储在dict中即可。 - Duncan
1
@CharlieParker 这样做更好,因为如果您事先不知道局部变量的名称,则除非通过locals()字典,否则无法访问它,并且不能确定您没有覆盖或隐藏另一个局部变量甚至是全局名称。使用dict可以安全地将任何字符串存储为键。 - Duncan
1
@Duncan 我一直认为Python是一种成年人的语言。如果使用exec或覆盖locals(),就会冒着你提到的风险(例如覆盖变量)。如果确切地知道将变量加载到本地并且知道名称,那么稍后就可以使用这些变量,编译器如果没有预先定义它们就会出现问题。我个人并不觉得有什么大不了的,一个人总是可以重新编写全局变量,比如for ='breaking for keyword',而不需要覆盖本地变量。我想我可能没有意识到真正的问题或者什么的... - Charlie Parker
@CharlieParker 当然,你是一个有意愿的成年人,所以去试试摧毁一些东西吧,只是不要说你没有被警告过。还有,你不能将像for这样的保留字用于赋值。 - Duncan
显示剩余4条评论

30

有人建议使用locals()进行赋值。但是在函数内部,这种方法不起作用,因为局部变量是使用LOAD_FAST操作码访问的,除非函数内有exec语句。要支持这个语句,它可能会创建在编译时未知的新变量,Python就被迫通过函数内的名称访问本地变量,因此写入locals()会起作用。 exec可以在未执行代码路径之外。

def func(varname):
    locals()[varname] = 42
    return answer           # only works if we passed in "answer" for varname
    exec ""                 # never executed

func("answer")
>>> 42

注意:这仅适用于Python 2.x。在Python 3中取消了这种愚蠢的做法,其他实现(如Jython、IronPython等)也可能不支持它。

然而这是一个坏主意。如果您不知道变量名,那么该如何访问变量呢?可能会使用locals()[xxx]。那么为什么不使用自己的字典,而不是污染locals()(并冒险覆盖函数实际需要的变量)呢?


8

(只是一个给其他人谷歌的快速提示)

好的,所以修改 locals() 不是正确的方法 (而修改 globals() 应该可以工作)。同时,exec 可以,但它非常缓慢,因此,与正则表达式一样,我们可能需要先使用 compile() 编译它:

# var0 = 0; var1 = 1; var2 = 2
code_text = '\n'.join( "var%d = %d" % (n, n) for n in xrange(3) )

filename = ''
code_chunk = compile( code_text, filename, 'exec' )

# now later we can use exec:
exec code_chunk # executes in the current context

1
如果你可以使用字典,为什么还要这样做呢? - l4mpi
1
我知道这不是一个好的做法,评论也是合理的,但这并不意味着这是一个坏问题,只是更加理论化。-- 需要花费相当长的时间来解释,而且这是一个不同的问题。可以说在大多数情况下可以忽略这个答案。附:如果你决定需要它,有这个选项不是很好吗? - ジョージ

3
您可以直接修改locals()
locals()['foo'] = 'bar'

但更好的方法是创建一个字典,将所有的动态变量名作为字典键:

d = {}
for some in thing:
    d[some] = 'whatever'

1
建议不要修改 locals() - Duncan
6
@Duncan:如果你仔细阅读,我并没有建议这样做。我只是说可以这样做,但有更好的方法。 - poke
8
我反对的是说你“可以”这样做。从文档中可以看到:“注意:不应修改此字典的内容;更改可能不会影响解释器使用的局部和自由变量的值。” - Duncan
-1 修改 locals() 不起作用。 - Raymond Hettinger
locals()['foo'] = 'bar' 在 Python 2.7 和 3 中都可以使用,这个在某个时候被改变了吗? - Georg Pfolz

3

我花了最近几个小时,尝试绕过函数闭包的缺失,最终得出以下解决方案,希望能帮到你:

common_data = ...stuff...
def process(record):
    ...logic...

def op():
    for fing in op.func_dict: # Key line number 1
        exec(fing + " = op.func_dict[fing]") # Key line number 2

    while common_data.still_recieving:
        with common_data._lock:
            if common_data.record_available:
                process(common_data.oldest_record)
        time.sleep(1.0)

op.func_dict.update(locals()) # Key line number 3
threading.Thread(target = op).start()

...

这个例子比较生硬/刻意,但如果有很多本地人或者你还在原型阶段,这种模式会变得很有用。主要是因为所有数据存储区都被复制或移动以处理回调委托等问题,让我感到有些不满。


0
假设我们有以下字典:
DictionaryA = {'No Rating': ['Hobbit', 'Movie C', 'Movie G'],
               'Forget It': ['Avenger', 'Movie B'], 
               'Must See': ['Children of Men', 'Skyfall', 'Movie F'], 
               '3': ['X-Men', 'Movie D'],
               '2': ['Captain America', 'Movie E'], 
               '4': ['Transformers', 'Movie A']} 

我想要创建以下新字典:
NewDictionary1 = {'No Rating': ['Hobbit', 'Movie C', 'Movie G']} 
NewDictionary2 = {'Forget It': ['Avenger', 'Movie B']} 
NewDictionary3 = {'Must See': ['Children of Men', 'Skyfall', 'Movie F']}

一个简洁的代码行:
dics = [{k:v} for k,v in DictionaryA.iteritems()]

将被输出为:

[{'Must See': ['Children of Men', 'Skyfall', 'Movie F']}, {'Forget It': ['Avenger', 'Movie B']}, {'No Rating': ['Hobbit', 'Movie C', 'Movie G']}, {'3': ['X-Men', 'Movie D']}, {'2': ['Captain America', 'Movie E']}, {'4': ['Transformers', 'Movie A']}]

但是,为了精确地声明变量,我们可以选择:

>>> i=0
>>> lcl = locals()
>>> for key,val in DictionaryA.iteritems():
        lcl["Dict" + str(i)] = {key:val}
        i += 1

可以看到前三个Dict变量:

>>> Dict0
{'Must See': ['Children of Men', 'Skyfall', 'Movie F']}
>>> Dict1
{'Forget It': ['Avenger', 'Movie B']}
>>> Dict2
{'No Rating': ['Hobbit', 'Movie C', 'Movie G']}

如其他人所提到的,如果你想将它放入函数中,你应该将它添加到globals()中:
>>> glb = globals()
>>> for key,val in DictionaryA.iteritems():
        glb["Dict" + str(i)] = {key:val}
        i += 1

0

你可以使用本地字典,并将所有动态绑定作为项放入字典中。然后,知道这样一个“动态变量”的名称,您可以使用名称作为键来获取其值。


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