Python3:列表理解和堆栈框架
Python3: list comprehensions and stack frames
考虑这个函数:
def quux():
i = 42
print([i for x in [1]])
结果:[42]
因此,我假设局部变量在列表推导中可见。
现在考虑一下:
def foo():
return currentframe().f_back.f_locals["i"]
def quux():
i = 42
print([foo() for x in [1]])
结果:
KeyError: 'i'
检查栈帧,发现在 quux 和 foo 的帧之间插入了一个额外的栈:
{'x': 1, '.0': <tuple_iterator object at 0x7f59eb94c860>}
好的,很公平。不过,令我感到困惑的是,为什么第一个示例会看到 i
。如果有一个额外的堆栈框架,它不应该是可见的,不是吗?
从更实际的角度来看,我如何才能获得调用函数的堆栈框架,而不管我的函数是从列表推导之外调用还是从列表推导中调用,或者就此而言,从多个嵌套列表推导中调用?
这是一个很好的观察(不管它的用处:))。回答你的问题的最好方法是自己看看:转储反汇编(在两个版本中使用 dis.dis(quux)
)。您会注意到两个版本的重要区别是第一个版本中加载的闭包。这是因为您在列表推导对象中引用了变量 i 并且通过这样做您使 i 成为闭包的一部分并且列表 comp 可以访问它。在第二种情况下没有这样的事情所以你得到那个错误。
对于第二部分,既然你明白了,你可能会想重新措辞?
赋予变量可见性的是它的范围,而不是可能的框架。 Python 局部变量具有函数范围,因此在第一个示例中,i
变量在 quux
函数内的任何行都是可见的。
框架只是一个 CPython 实现细节。来自 inspect
模块的标准库文档:
inspect.currentframe()
Return the frame object for the caller’s stack frame.
CPython implementation detail: This function relies on Python stack frame support in the interpreter, which isn’t guaranteed to exist in all implementations of Python. If running in an implementation without Python stack frame support this function returns None.
当实现使用 Python 堆栈帧支持时,该实现将具有用于显式函数调用的帧,并且可能会添加交错帧供内部使用,此处用于列表理解,但可能还有其他用例.
所以这里你有 2 个可能的选择:
实用:你已经发现在那个用例中只有一个交错帧,所以你可以跳过它:
def foo():
return currentframe().f_back.f_back.f_locals["i"]
文档方面:
您知道外框将包含一个 i
局部变量。只需扫描包含它的第一个外框:
def foo():
for f in getouterframes(currentframe()):
if 'i' in f.frame.f_locals:
return f.frame.f_locals['i']
return None
考虑这个函数:
def quux():
i = 42
print([i for x in [1]])
结果:[42]
因此,我假设局部变量在列表推导中可见。
现在考虑一下:
def foo():
return currentframe().f_back.f_locals["i"]
def quux():
i = 42
print([foo() for x in [1]])
结果:
KeyError: 'i'
检查栈帧,发现在 quux 和 foo 的帧之间插入了一个额外的栈:
{'x': 1, '.0': <tuple_iterator object at 0x7f59eb94c860>}
好的,很公平。不过,令我感到困惑的是,为什么第一个示例会看到 i
。如果有一个额外的堆栈框架,它不应该是可见的,不是吗?
从更实际的角度来看,我如何才能获得调用函数的堆栈框架,而不管我的函数是从列表推导之外调用还是从列表推导中调用,或者就此而言,从多个嵌套列表推导中调用?
这是一个很好的观察(不管它的用处:))。回答你的问题的最好方法是自己看看:转储反汇编(在两个版本中使用 dis.dis(quux)
)。您会注意到两个版本的重要区别是第一个版本中加载的闭包。这是因为您在列表推导对象中引用了变量 i 并且通过这样做您使 i 成为闭包的一部分并且列表 comp 可以访问它。在第二种情况下没有这样的事情所以你得到那个错误。
对于第二部分,既然你明白了,你可能会想重新措辞?
赋予变量可见性的是它的范围,而不是可能的框架。 Python 局部变量具有函数范围,因此在第一个示例中,i
变量在 quux
函数内的任何行都是可见的。
框架只是一个 CPython 实现细节。来自 inspect
模块的标准库文档:
inspect.currentframe()
Return the frame object for the caller’s stack frame.CPython implementation detail: This function relies on Python stack frame support in the interpreter, which isn’t guaranteed to exist in all implementations of Python. If running in an implementation without Python stack frame support this function returns None.
当实现使用 Python 堆栈帧支持时,该实现将具有用于显式函数调用的帧,并且可能会添加交错帧供内部使用,此处用于列表理解,但可能还有其他用例.
所以这里你有 2 个可能的选择:
实用:你已经发现在那个用例中只有一个交错帧,所以你可以跳过它:
def foo(): return currentframe().f_back.f_back.f_locals["i"]
文档方面:
您知道外框将包含一个
i
局部变量。只需扫描包含它的第一个外框:def foo(): for f in getouterframes(currentframe()): if 'i' in f.frame.f_locals: return f.frame.f_locals['i'] return None