【问题标题】:How Python's closure worksPython 的闭包是如何工作的
【发布时间】:2020-01-06 23:01:30
【问题描述】:

我试图了解闭包在 Python 中是如何工作的,我发现了以下代码 sn-p:

def closure():
    count = 0
    def inner():
        nonlocal count
        count += 1
        print(count)
    return inner

start = closure()
start() # prints 1
start() # prints 2
start() # prints 3

我可以理解这段代码,因为在定义inner函数时,封闭范围有一个名为count的变量,值为0,内部函数将remember这个值

但是,如果我将count = 0 移动到内部函数下方,则代码变为:

def closure():
    def inner():
        nonlocal count
        count += 1
        print(count)
    count = 0
    return inner

start = closure()
start() # prints 1
start() # prints 2
start() # prints 3

令我惊讶的是,代码仍然可以正常工作,这让我很困惑。在定义inner 时,变量count 不存在于封闭范围内,inner 函数如何能够记住此时命名空间中不存在的值?

是不是因为Python中也存在类似JS的变量hoisting的东西?

【问题讨论】:

    标签: python scope namespaces closures


    【解决方案1】:

    在这两个示例中,您都收到了 start 变量中的 closure 方法返回的值,即方法 closure 被执行并且返回 inner 本地方法。所以 count 变量也被定义并初始化为值 0

    当您使用 start() 调用 inner 方法时,该方法将被执行,到那时,在这两种情况下,count 变量都已经存在

    但是如果你有这样的代码,那么就会出现引用错误

    def closure():
        def inner():
            nonlocal count
            count += 1
            print(count)
        inner()
        count = 0
    
    start = closure()
    

    这里 inner 方法在 count 定义之前被调用

    【讨论】:

    • 是的,回答 OP 的另一个问题,No Python 没有像 JS 或其他语言那样的代码提升功能。
    • "由于python是解释器,它不会编译内部方法,直到它被调用。" => 错误...你确定吗?还是您的意思是:“在调用 closure 函数之前,它不会编译函数 inner”?
    • 删除了这一行,因为术语 compile 很棘手且令人困惑。 Python 编译发生在整个脚本上,与调用无关,因为它发生在执行/解释时。感谢您指出。
    • 您的回答暗示名称的范围是在运行时解析的。这不是真的,它是在代码运行之前定义的代码对象的一部分。还是我看错了你的解释?
    • 不是 100% 明确,而是这部分“当您使用 start() 调用内部方法时,该方法将被执行,到那时,在这两种情况下,计数变量都已经存在”对我来说,执行顺序将决定范围分辨率。 “调用”对我来说尤其意味着它会在运行时发生,而不是编译。
    【解决方案2】:

    来自Resolution of Names

    如果在代码块中的任何位置发生名称绑定操作,则块内对名称的所有使用都将被视为对当前块的引用。当在代码块中使用名称时,这可能会导致错误绑定之前的一个块。这个规则很微妙。 Python 缺少声明,并允许名称绑定操作在代码块中的任何位置发生。 代码块的局部变量可以通过扫描块的整个文本进行名称绑定操作来确定。

    (强调我的。)

    分配是name binding operations,所以只要count = 0 存在于函数中任何地方count 就被认为是该函数的局部变量,inner 将引用该变量。 (虽然如果您在将值分配给 count 之前调用 inner(),您会得到一个异常。)

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2010-09-19
      • 2013-06-07
      • 2012-09-08
      • 1970-01-01
      • 2017-07-04
      • 2011-03-02
      相关资源
      最近更新 更多