在python 3的函数中创建动态命名的变量/了解python 3中的exec/eval/locals [英] Creating dynamically named variables in a function in python 3 / Understanding exec / eval / locals in python 3

查看:39
本文介绍了在python 3的函数中创建动态命名的变量/了解python 3中的exec/eval/locals的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

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

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

def 函数名():对于索引,枚举中的 buchstabe(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)) #效果很好,例如打印4 D 3"print(locals()) #pritns 所有本地人:{'B': 1, 'A': 1, 'index': 11, 'C': 2, 'H': 21, 'K': 89, ...print(locals()['K']) #prints 89,因为它应该打印(eval(K"))#打印89,因为它应该打印(K)#NameError:名称'K'未定义

所以至少在我目前的理解中,locals() 的行为存在一些不一致,因为它包含由 exec() 添加的变量名称,但是变量在函数中不可用.

如果有人能够解释这一点并说明这是设计使然还是语言中的真正不一致,我会非常高兴.是的,我知道 locals 应该没有被修改,但我没有修改它,我正在调用 exec()...

解决方案

当你不确定为什么某些东西会在 Python 中以这种方式工作时,将你感到困惑的行为放在一个函数中通常会有所帮助然后使用 dis 模块从 Python 字节码中反汇编它.

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

def foo():exec("K = 89")打印(K)

如果你运行 foo(),你会得到与你在更复杂的函数中看到的相同的异常:

<预><代码>>>>富()回溯(最近一次调用最后一次):文件<pyshell#167>",第 1 行,在 <module> 中富()文件<pyshell#166>",第 3 行,在 foo 中打印(K)NameError: 名称 'K' 未定义

让我们把它拆开看看为什么:

<预><代码>>>>导入文件>>>dis.dis(foo)2 0 LOAD_GLOBAL 0 (执行)3 LOAD_CONST 1 ('K = 89')6 CALL_FUNCTION 1(1 个位置,0 个关键字对)9 POP_TOP3 10 LOAD_GLOBAL 1(打印)13 LOAD_GLOBAL 2 (K)16 CALL_FUNCTION 1(1 个位置,0 个关键字对)19 POP_TOP20 LOAD_CONST 0 (无)23 RETURN_VALUE

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

如果我们说服编译器将 K 看作一个局部变量(通过在运行 exec 之前给它一个值),它会知道不去寻找一个不存在的全局变量?

def bar():K = 无exec("K = 89")打印(K)

如果你运行这个函数不会给你一个错误,但你不会打印出预期的值:

<预><代码>>>>酒吧()没有任何

让我们拆开看看为什么:

<预><代码>>>>dis.dis(bar)2 0 LOAD_CONST 0(无)3 STORE_FAST 0 (K)3 6 LOAD_GLOBAL 0 (执行)9 LOAD_CONST 1 ('K = 89')12 CALL_FUNCTION 1(1 个位置,0 个关键字对)15 POP_TOP4 16 LOAD_GLOBAL 1(打印)19 LOAD_FAST 0 (K)22 CALL_FUNCTION 1(1 个位置,0 个关键字对)25 POP_TOP26 LOAD_CONST 0 (无)29 RETURN_VALUE

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

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

def baz():K = 无exec("K = 89")打印(当地人())

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

<预><代码>>>>巴兹(){K":无}

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

<块引用>

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

局部变量K的值所在的槽没有被exec语句改变,只是修改了locals()代码>字典.当您再次调用 locals() 时,Python 会使用槽中的值更新 [s]"字典,替换 exec 中存储的值.

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

<块引用>

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

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

First of all, let me say that I read the many threads with similar topics on creating dynamically named variables, but they mostly relate to Python 2 or they assume you are working with classes. And yes, I read Behavior of exec function in Python 2 and Python 3.

I'm also aware that creating dynamically named variables is a bad idea in 99% of time and dictionaries are the way to got, but I just want to know whether it is still possible and how exactly exec and locals work in python 3.

I'd like to show a bit of sample code illustrating my question (fibonacci calculates fibonacci numbers, ListOfLetters provides ["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

So at least at my current understanding, there is some inconsistency in the behaviour of locals(), since it contains the variable names added by exec() but the variables are not available in the function.

I would be greatful if someone could explain this and tell whether this is by design or if it is a real inconsistency in the language. Yes, I know that locals should not be modified , but I'm not modifying it, I'm calling exec()...

解决方案

When you're not sure why something works the way it does in Python, it often can help to put the behavior that you're confused by in a function and then disassemble it from the Python bytecode with the dis module.

Lets start with a simpler version of your code:

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

If you run foo(), you'll get the same exception you're seeing with your more complicated function:

>>> 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

Lets disassemble it and see why:

>>> 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

The operation that you need to pay attention to is the one labeled "13". This is where the compiler handles looking up K within the last line of the function (print(K)). It is using the LOAD_GLOBAL opcode, which fails because "K" is not a global variable name, rather it's a value in our locals() dict (added by the exec call).

What if we persuaded the compiler to see K as a local variable (by giving it a value before running the exec), so it will know not to look for a global variable that doesn't exist?

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

This function won't give you an error if you run it, but you won't get the expected value printed out:

>>> bar()
None

Lets disassemble to see why:

>>> 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

Note the opcodes used at "3" and "19". The Python compiler uses STORE_FAST and LOAD_FAST to put the value for the local variable K into slot 0 and later fetch it back out. Using numbered slots is significantly faster than inserting and fetching values from a dictionary like locals(), which is why the Python compiler does it for all local variable access in a function. You can't overwrite a local variable in a slot by modifying the dictionary returned by locals() (as exec does, if you don't pass it a dict to use for its namespace).

Indeed, lets try a third version of our function, where we peek into locals again when we have K defined as a regular local variable:

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

You won't see 89 in the output this time either!

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

The reason you see the old K value in locals() is explained in the function's documentation:

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

The slot that the local variable K's value is stored in was not changed by the exec statement, which only modifies the locals() dict. When you call locals() again, Python "update[s]" the dictionary with the value from the slot, replacing the value stored there by exec.

This is why the docs go on to say:

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.

Your exec call is modifying the locals() dict, and you're finding how its changes are not always seen by your later code.

这篇关于在python 3的函数中创建动态命名的变量/了解python 3中的exec/eval/locals的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆