Python 3. 'exec' 函数的混淆问题

Python 3. Confusing issue with 'exec' function

我一直在使用 tkinter for gui 开发我的贪吃蛇游戏。问题出在其中一个函数中。

该函数应该分别使用 Canvas().create_rectangle 和 Canvas().create_oval 绘制 body 个碎片和水果。因此,我没有为每种情况编写单独的代码,而是决定编写一次并使用 'shape' 参数修改它,该参数可以是 'rectangle' 或 'oval'。函数还必须 return 绘制元素的 id 以用于其自身目的。最初那部分代码看起来像这样:

        exec(

        """
segment_id = self.grid.create_{}(coords, 
    [coord + self.pxSize for coord in coords],  
        fill=color, tag=tag, width=0)

        """.format(shape))

        return segment_id

我得到的不是普通 NameError: name 'segment_id' is not defined,而是 NameError: name 'self' is not defined

谷歌搜索后我只找到了这个:

ldict = {}
exec('var = something', globals(), ldict)
var = ldict['var']

其中解决了 NameError: name 'segment_id' is not defined,但没有解决另一个问题。因此,使用科学戳方法,我通过将 locals() 传递给它的 'globals' 参数来修复它。它有效,现在我更加困惑了。

代码如下:

class Game(Tk):
    def __init__(self):
         ...
    # ...

    def drawSegment(self, position, color, tag, shape, id_=None):
        coords = self.field.from_1D_to_2D(position)
        coords = [i * self.pxSize for i in coords]

        # id > 1, otherwise deletes background
        if id_ and id_ > 1:
            self.grid.delete(id_)

        # ???
        ldict = {}

        exec(

        """
segment_id = self.grid.create_{}(coords,
    [coord + self.pxSize for coord in coords],
        fill=color, tag=tag, width=0)

        """.format(shape), locals(), ldict)

        segment_id = ldict['segment_id']

        return segment_id

    # ...

我需要的是关于为什么它有效以及发生了什么的答案甚至

像这样使用 exec 是不必要的,而且当您 运行 进入时,非常混乱;所以我有两个不同的答案给你。

怎么回事?

当你将globals=globals(), locals=ldict传递给exec时,它会在一个只能看到globalsldict的范围内执行代码;所以特别是,它不会看到 drawSegment 方法局部范围内的任何局部变量。由于 self 仅在此局部范围内定义,为了从 exec 调用内部引用 self,您需要传入 locals(),而不仅仅是 globals().

你应该怎么做?

无需根据 shape 的值动态执行整个代码块,您只需根据 shape:

动态查找所需的 create_* 方法即可
creator = getattr(self.grid, 'create_{}'.format(shape))
segment_id = creator(coords, [coord + self.pxSize for coord in coords],
                     fill=color, tag=tag, width=0)

如果您知道形状只有两种可能性,根据个人品味,您可能会更加明显:

creator = self.grid.create_oval if shape == 'oval' else self.grid.create_rectangle