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

标签 python python-3.x

首先,我要说的是,我阅读了很多关于创建动态命名变量的类似主题的帖子,但它们大多与 Python 2 相关,或者它们假定您正在使用类。是的,我读了Behavior of exec function in Python 2 and Python 3 .

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

我想展示一些示例代码来说明我的问题(fibonacci 计算斐波那契数,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()...

最佳答案

当您不确定为什么某些东西会以它在 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”的操作。这是编译器在函数的最后一行 (print(K)) 中处理查找 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 所做的那样,如果您不向它传递要使用的字典为其命名空间)。

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

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

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

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

您在 locals() 中看到旧的 K 值的原因在 the function's documentation 中有解释。 :

Update and return a dictionary representing the current local symbol table.

局部变量K的值所在的槽没有被exec语句改变,它只修改了locals()字典。当您再次调用 locals() 时,Python 使用槽中的值“更新[s]”字典,用 exec 替换存储在那里的值。

这就是文档继续说的原因:

Note: The contents of this dictionary should not be modified; changes may not affect the values of local and free variables used by the interpreter.

您的 exec 调用正在修改 locals() 字典,您发现它的更改并不总是被您以后的代码看到。

关于python - 在 python 3 中的函数中创建动态命名变量/理解 python 3 中的 exec/eval/locals,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/25076883/

相关文章:

python - SQLAlchemy的反射工具可以输出python源码吗?

mysql - 每次项目运行时django都会给出重复的错误

python - 来自 sqlalchemy 的 psycopg2 register_composite

python-3.x - Python3 requests.get 太慢

python - 如何使用 keras-self-attention 包可视化注意力 LSTM?

python - python plot get_loc(self,key,method,tolerance)错误

python - 切片对象在函数声明中是否可变?

python - 在 PySpark 1.5.0 中,如何根据 `y` 列的值列出 `x` 列的所有项目?

python - 尾递归函数无法返回值(Python 3)

python-3.x - Python 3 BeautifulSoup4 在源页面中搜索文本