Python3:列出理解和堆栈框架

时间:2019-02-19 07:58:41

标签: python list-comprehension stackframe

考虑此功能:

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。如果还有一个额外的堆栈框,它应该是不可见的,不是吗?

并且从更实际的角度出发,无论我的函数是从列表理解中获取还是从多个嵌套列表理解中获取,无论如何,我如何才能获得调用函数的堆栈框架?

2 个答案:

答案 0 :(得分:1)

这是一个很好的观察结果(无论其有用性如何:))。回答问题的最好方法是亲自查看:转储反汇编(使用dis.dis(quux)的两个版本)。您会注意到两个版本的重要区别是在第一个版本中加载了闭包。这是因为您在列表理解对象中引用了变量i,并且这样做使i成为闭包的一部分,并且列表comp可以访问它。在第二种情况下,没有这样的事情,所以您会得到该错误。

对于第二部分,现在您已经了解了它,也许您想重新措辞?

答案 1 :(得分:1)

给出变量可见性的是变量 scope ,而不是可能的帧。 Python局部变量具有函数作用域,因此在第一个示例中,i函数的任何行中都可见quux变量。

框架只是CPython实现的细节。来自inspect模块的标准库文档:

  

inspect.currentframe()
  返回调用者堆栈框架的框架对象。

     

CPython实现细节:此函数依赖于解释器中的Python堆栈框架支持,并不能保证在所有Python实现中都存在。如果在没有Python堆栈框架支持的实现中运行,此函数将返回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