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:赋值前引用的局部变量“值”
。