python中的奇怪线程行为

python中的奇怪线程行为,python,multithreading,scope,Python,Multithreading,Scope,我有一个问题,我需要将数组的索引传递给我内联定义的函数。然后将该函数作为参数传递给另一个函数,该函数最终将作为回调调用它 问题是,当调用代码时,索引的值是错误的。我最终通过创建一个丑陋的解决方案来解决这个问题,但我有兴趣了解这里发生了什么。我创建了一个简单的示例来演示问题: from __future__ import print_function import threading def works_as_expected(): for i in range(10):

我有一个问题,我需要将数组的索引传递给我内联定义的函数。然后将该函数作为参数传递给另一个函数,该函数最终将作为回调调用它

问题是,当调用代码时,索引的值是错误的。我最终通过创建一个丑陋的解决方案来解决这个问题,但我有兴趣了解这里发生了什么。我创建了一个简单的示例来演示问题:

from __future__ import print_function
import threading


def works_as_expected():
    for i in range(10):
        run_in_thread(lambda: print('the number is: {}'.format(i)))

def not_as_expected():
    for i in range(10):
        run_later_in_thread(lambda: print('the number is: {}'.format(i)))

def run_in_thread(f):
    threading.Thread(target=f).start()

threads_to_run_later = []
def run_later_in_thread(f):
    threads_to_run_later.append(threading.Thread(target=f))


print('this works as expected:\n')
works_as_expected()

print('\nthis does not work as expected:\n')
not_as_expected()
for t in threads_to_run_later: t.start()
以下是输出:

this works as expected:

the number is: 0
the number is: 1
the number is: 2
the number is: 3
the number is: 4
the number is: 6
the number is: 7
the number is: 7
the number is: 8
the number is: 9

this does not work as expected:

the number is: 9
the number is: 9
the number is: 9
the number is: 9
the number is: 9
the number is: 9
the number is: 9
the number is: 9
the number is: 9
the number is: 9
有人能解释一下这里发生了什么吗?我假设它与封闭范围或其他东西有关,但是一个带有引用的答案对我来说是很有价值的,它可以解释python作用域的这个黑暗的(对我来说)角落


我在Python2.7.11上运行这个程序,这是python中闭包和作用域工作方式的结果

发生的情况是,
i
被绑定在
的范围内,而不是像预期的那样
函数。因此,即使您正在向线程提供
lambda
函数,它使用的变量也在每个lambda和每个线程之间共享

考虑这个例子:

def make_function():
    i = 1
    def inside_function():
        print i
    i = 2
    return inside_function

f = make_function()
f()
你认为它会打印什么号码?定义函数之前的
i=1
,还是定义函数之后的
i=2

它将打印
i
(即
2
)的当前值。创建函数时,
i
的值是多少并不重要,它总是使用当前值。同样的事情也发生在
lambda
函数中

即使在您预期的结果中,您也可以看到它并不总是正常工作,它跳过了
5
并显示了
7
两次。在这种情况下,每个lambda通常在循环进入下一个迭代之前运行。但在某些情况下(如
5
),循环会在控制传递给其他线程之一之前完成两次迭代,
i
会增加两次,并跳过一个数字。在其他情况下(如
7
),当循环仍在同一迭代中时,两个线程设法运行,并且由于
i
在两个线程之间没有变化,因此会打印相同的值

如果您改为这样做:

def function_maker(i):
    return lambda: print('the number is: {}'.format(i))

def not_as_expected():
    for i in range(10):
        run_later_in_thread(function_maker(i))

i
变量与
lambda
函数一起绑定到
函数中。每个lambda函数将引用不同的变量,它将按预期工作。

Python中的闭包捕获自由的变量,而不是创建闭包时的当前值。例如:

def make_closures():
    L = []

    # Captures variable L
    def push(x):
        L.append(x)
        return len(L)

    # Captures the same variable
    def pop():
        return L.pop()

    return push, pop

pushA, popA = make_closures()
pushB, popB = make_closures()

pushA(10); pushB(20); pushA(30); pushB(40)
print(popA(), popA(), popB(), popB())
将显示30、10、40、20:这是因为第一对闭包
pushA
popA
将引用一个列表
L
,第二对
pushB
popB
将引用另一个独立列表

重要的一点是,在每对
push
pop
中,闭包引用相同的列表,即它们捕获了变量
L
,而不是创建时
L
值。如果
L
被一个闭包突变,则另一个闭包将看到变化

例如,一个常见的错误是期望

L = []
for i in range(10):
    L.append(lambda : i)
for x in L:
    print(x())
将显示从0到9的数字。。。这里所有未命名的闭包都捕获了用于循环的相同变量
i
,调用时所有闭包都将返回相同的值

解决这个问题的常用Python习惯用法是

L.append(lambda i=i: i)

i、 e.使用在创建函数时评估参数默认值的事实。使用这种方法,每个闭包将返回一个不同的值,因为它们返回的是它们的私有局部变量(一个具有默认值的参数)。

我建议启动一个调试器就足够了,删除代码中按预期工作的部分,只需逐行执行,它会让你很快明白到底发生了什么on@TymoteuszPaul我想你误解了我问题的重点。并不是我不理解代码在做什么。我不明白它为什么这么做。我正在寻找一个能帮助我理解语言实际工作原理的答案,这样我就能更好地对代码进行推理。不过谢谢你的建议,我很喜欢pdb。我甚至没有注意到预期的结果有
5
7
异常。这与Go在一个
for
循环中的局部变量非常相似,您必须将变量传递到一个具有新范围的新函数中,它将复制变量值。但是当我使用多个线程时,闭包外部范围中的变量丢失并引发
UnboundLocalError:赋值前引用的局部变量“值”