小编典典

在python 3中的函数中创建动态命名的变量/在python 3中了解exec / eval / locals

python

首先,我要说的是,我阅读了许多与创建动态命名变量类似的主题,但是它们大多与Python 2有关,或者假定您正在使用类。是的,我阅读了Python
2和Python 3中exec函数的行为

我也知道在99%的时间里创建动态命名的变量是一个坏主意,而字典是获得的方法,但是我只想知道是否仍然可行,以及exec和locals在python
3中的工作原理。

我想显示一些示例代码来说明我的问题(斐波纳契计算斐波纳契数,ListOfLetters提供[“ A”,“ B”,…]):

def functionname():
    for index, buchstabe in enumerate(ListOfLetters.create_list("A", "K"), 1): 
        exec("{} = {}".format(buchstabe, fibonacci(index)) ) #A = 1, B = 1, C = 2, D = 3, E = 5,...
        print(index, buchstabe, eval(buchstabe)) #works nicely, e.g. prints "4 D 3"
    print(locals()) #pritns all locals: {'B': 1, 'A': 1, 'index': 11, 'C': 2, 'H': 21, 'K': 89, ...
    print(locals()['K']) #prints 89 as it should
    print(eval("K")) #prints 89 as it should
    print(K) #NameError: name 'K' is not defined

因此,至少就我目前的理解而言,的行为存在一些不一致之处locals(),因为它包含所添加的变量名,exec()但该变量在函数中不可用。

如果有人可以解释一下并告诉您这是设计使然还是语言中确实存在不一致之处,我将不胜感激。是的,我知道locals不应该修改它,但是我没有修改它,我在打电话给exec()


阅读 218

收藏
2020-12-20

共1个答案

小编典典

当您不确定某个东西为什么能在Python中正常工作时,它通常可以帮助将您困惑的行为放入函数中,然后使用该dis模块将其从Python字节码中分解出来。

让我们从一个简单的代码版本开始:

def foo():
    exec("K = 89")
    print(K)

如果运行foo(),您将得到与更复杂的函数相同的异常:

>>> foo()
Traceback (most recent call last):
  File "<pyshell#167>", line 1, in <module>
    foo()
  File "<pyshell#166>", line 3, in foo
    print(K)
NameError: name 'K' is not defined

让我们分解一下,看看为什么:

>>> import dis
>>> dis.dis(foo)
  2           0 LOAD_GLOBAL              0 (exec)
              3 LOAD_CONST               1 ('K = 89')
              6 CALL_FUNCTION            1 (1 positional, 0 keyword pair)
              9 POP_TOP

  3          10 LOAD_GLOBAL              1 (print)
             13 LOAD_GLOBAL              2 (K)
             16 CALL_FUNCTION            1 (1 positional, 0 keyword pair)
             19 POP_TOP
             20 LOAD_CONST               0 (None)
             23 RETURN_VALUE

您需要注意的操作是标为“
13”的操作。这是编译器K在函数的最后一行(print(K))中查找的地方。它使用的LOAD_GLOBAL操作码失败,因为“
K”不是全局变量名称,而是它在我们的locals()字典中的值(由exec调用添加)。

如果我们说服编译器将其K视为局部变量(在运行之前为其提供值exec),那么它将知道不寻找不存在的全局变量怎么办?

def bar():
    K = None
    exec("K = 89")
    print(K)

如果运行该函数,将不会出现错误,但不会输出期望值:

>>> bar()
None

让我们分解一下原因:

>>> dis.dis(bar)
  2           0 LOAD_CONST               0 (None)
              3 STORE_FAST               0 (K)

  3           6 LOAD_GLOBAL              0 (exec)
              9 LOAD_CONST               1 ('K = 89')
             12 CALL_FUNCTION            1 (1 positional, 0 keyword pair)
             15 POP_TOP

  4          16 LOAD_GLOBAL              1 (print)
             19 LOAD_FAST                0 (K)
             22 CALL_FUNCTION            1 (1 positional, 0 keyword pair)
             25 POP_TOP
             26 LOAD_CONST               0 (None)
             29 RETURN_VALUE

注意在“ 3”和“
19”处使用的操作码。Python编译器使用STORE_FASTLOAD_FAST将局部变量的值K放入插槽0,然后将其取回。使用编号的插槽比从像这样的字典中插入和获取值的速度要快得多locals(),这就是Python编译器为函数中的所有局部变量访问而这样做的原因。您不能通过修改返回的字典来覆盖插槽中的局部变量locals()exec如果您不将其传递给它的命名空间的字典,也一样)。

确实,让我们尝试函数的第三个版本,locals当我们将K其定义为常规局部变量时,可以再次浏览:

def baz():
    K = None
    exec("K = 89")
    print(locals())

89这次您也不会在输出中看到!

>>> baz()
{"K": None}

函数文档中解释了您看到旧K值的原因:locals()

更新并返回代表当前本地符号表的字典。

该语句K未更改存储本地变量值的插槽,该exec语句仅修改locals()字典。locals()再次调用时,Python使用插槽中的值“更新”字典,用替换存储在其中的值exec

这就是为什么文档继续说:

注意: 此字典的内容不得修改;更改可能不会影响解释器使用的局部变量和自由变量的值。

您的exec呼叫正在修改locals()字典,并且您发现以后的代码并不总是看到其更改。

2020-12-20