关于python:exec如何与当地人合作?

How does exec work with locals?

我以为这会印3,但印1:

1
2
3
4
def f():
    a = 1
    exec("a = 3")
    print(a)


这个问题在python3 bug列表中有点讨论。最终,要获得这种行为,您需要执行以下操作:

1
2
3
4
5
def foo():
    ldict = {}
    exec("a=3",globals(),ldict)
    a = ldict['a']
    print(a)

如果您查看exec上的python3文档,您将看到以下注释:

The default locals act as described for function locals() below: modifications to the default locals dictionary should not be attempted. Pass an explicit locals dictionary if you need to see effects of the code on locals after function exec() returns.

在引用bug报告中的特定消息时,GeorgBrandl说:

To modify the locals of a function on the fly is not
possible without several consequences: normally, function locals are not
stored in a dictionary, but an array, whose indices are determined at
compile time from the known locales. This collides at least with new
locals added by exec. The old exec statement circumvented this, because
the compiler knew that if an exec without globals/locals args occurred
in a function, that namespace would be"unoptimized", i.e. not using the
locals array. Since exec() is now a normal function, the compiler does
not know what"exec" may be bound to, and therefore can not treat is
specially.

重点是我的。

因此,其要点是python3可以通过在默认情况下不允许这种行为来更好地优化局部变量的使用。

为了完整起见,正如上面的注释中所提到的,这在python 2.x中是可以正常工作的:

1
2
3
4
5
6
7
8
9
10
Python 2.6.2 (release26-maint, Apr 19 2009, 01:56:41)
[GCC 4.3.3] on linux2
Type"help","copyright","credits" or"license" for more information.
>>> def f():
...     a = 1
...     exec"a=3"
...     print a
...
>>> f()
3


如果您在方法中,可以这样做:

1
2
3
4
5
6
class Thing():
    def __init__(self):
        exec('self.foo = 2')

x = Thing()
print(x.foo)

你可以在这里了解更多


使用exec的函数不能改变局部变量的原因,以及exec的行为方式的原因可以概括如下:

  • exec是一个函数,它与调用它的最内部作用域的作用域共享其本地作用域。
  • 每当您在函数范围内定义一个新对象时,就可以在其本地命名空间中访问它,即它将修改local()字典。当您在exec中定义一个新对象时,它所做的大致相当于:
  • 1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    from copy import copy
    class exec_type:
        def __init__(self, *args, **kwargs):
            # default initializations
            # ...
            self.temp = copy(locals())

        def __setitem__(self, key, value):
            if var not in locals():
                set_local(key, value)
            self.temp[key] = value

    temp是一个临时名称空间,在每次实例化后(每次调用exec时)都会重置。

  • python开始从本地名称空间查找名称。这就是所谓的腿的方式。python从本地名称spce开始,然后查找封闭范围,然后查找全局范围,最后查找内置名称空间中的名称。
  • 更全面的例子如下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    g_var = 5

    def test():
        l_var = 10
        print(locals())
        exec("print(locals())")
        exec("g_var = 222")
        exec("l_var = 111")
        exec("print(locals())")

        exec("l_var = 111; print(locals())")

        exec("print(locals())")
        print(locals())
        def inner():
            exec("print(locals())")
            exec("inner_var = 100")
            exec("print(locals())")
            exec("print([i for i in globals() if '__' not in i])")

        print("Inner function:")
        inner()
        print("-------" * 3)
        return (g_var, l_var)

    print(test())
    exec("print(g_var)")

    输出:

    1
    2
    {'l_var': 10}
    {'l_var': 10}

    当地人也一样。

    1
    {'l_var': 10, 'g_var': 222}

    添加g_var和更改l_var后,只添加g_var并保持l_var不变。

    1
    {'l_var': 111, 'g_var': 222}

    由于我们在一个实例(一个对exec的调用)中更改并打印局部变量,因此l_var被更改。

    1
    2
    {'l_var': 10, 'g_var': 222}
    {'l_var': 10, 'g_var': 222}

    在这两个函数中,local s和exec的local l_var不变,并且添加了g_var

    1
    2
    3
    4
    Inner function:
    {}
    {'inner_var': 100}
    {'inner_var': 100}

    inner_function的本地与Exec的本地相同。

    1
    ['g_var', 'test']

    全局只包含g_var和函数名(不包括特殊方法后)。

    1
    2
    3
    4
    ---------------------

    (5, 10)
    5