为什么python asyncio loop.call\u很快就会覆盖数据?

为什么python asyncio loop.call\u很快就会覆盖数据?,python,python-asyncio,event-loop,Python,Python Asyncio,Event Loop,我在代码中创建了一个难以追踪的bug,但不明白它为什么会发生。当多次推动同一异步函数以尽快调用时,就会出现问题。同步函数不会发生这种情况 以下是该问题的运行示例: import asyncio import sys class TestObj(object): def __init__(self): self.test_data = {'a': 1, 'b': 2, 'c': 3} self.loop = asyncio.get_event_loop

我在代码中创建了一个难以追踪的bug,但不明白它为什么会发生。当多次推动同一异步函数以尽快调用时,就会出现问题。同步函数不会发生这种情况

以下是该问题的运行示例:

import asyncio
import sys

class TestObj(object):

    def __init__(self):

        self.test_data = {'a': 1, 'b': 2, 'c': 3}
        self.loop = asyncio.get_event_loop()
        self.loop.call_later(1, lambda: asyncio.ensure_future(self.calling_func()))
        self.loop.call_later(2, self.calling_func_sync)
        self.loop.call_later(4, sys.exit)
        self.loop.run_forever()

    async def do_something(self, k, v):
        print("Values", k, v)

    async def calling_func(self):
        for k, v in self.test_data.items():
            print("Sending", k, v)
            self.loop.call_soon(lambda: asyncio.ensure_future(self.do_something(k, v)))

    def do_something_sync(self, k, v):
        print("Values_sync", k, v)

    def calling_func_sync(self):
        for k, v in self.test_data.items():
            print("Sending_sync", k, v)
            self.loop.call_soon(self.do_something_sync, k, v)


if __name__ == "__main__":
    a = TestObj()
输出为:

Sending a 1
Sending b 2
Sending c 3
Values c 3
Values c 3
Values c 3
Sending_sync a 1
Sending_sync b 2
Sending_sync c 3
Values_sync a 1
Values_sync b 2
Values_sync c 3

为什么会发生这种情况,为什么?只有异步函数正在被踩踏。我本以为每次调用call_都会很快将一个新指针推到堆栈上,但似乎有一个指向self.do_的指针正在被覆盖。

这与异步代码无关,但与循环中创建的lambda有关。当您编写
lambda:asyncio.sure_future(self.do_something(k,v))
时,您正在创建一个闭包,从封闭的名称空间访问变量
k
v
(以及
self
,但这不是问题)。调用lambda函数时,它将使用调用时外部作用域中由这些名称绑定的值,而不是定义lambda时的值。由于
k
v
在循环的每次迭代中都会更改值,因此所有lambda函数都会看到相同的值(最后的值)

避免此问题的常见方法是将变量的当前值设为lambda函数参数的默认值:

self.loop.call_soon(lambda k=k, v=v: asyncio.ensure_future(self.do_something(k, v)))

这与异步代码无关,但与在循环中创建的
lambda
有关。当您编写
lambda:asyncio.sure_future(self.do_something(k,v))
时,您正在创建一个闭包,从封闭的名称空间访问变量
k
v
(以及
self
,但这不是问题)。调用lambda函数时,它将使用调用时外部作用域中由这些名称绑定的值,而不是定义lambda时的值。由于
k
v
在循环的每次迭代中都会更改值,因此所有lambda函数都会看到相同的值(最后的值)

避免此问题的常见方法是将变量的当前值设为lambda函数参数的默认值:

self.loop.call_soon(lambda k=k, v=v: asyncio.ensure_future(self.do_something(k, v)))

您的问题实际上与
asyncio
无关。
lambda:asyncio中的
k
v
。确保未来(self.do\u something(k,v))
仍然引用外部范围中的变量。在调用函数时,它们的值会发生变化:

i = 1
f = lambda: print(i)

f()  # 1
i = 2
f()  # 2
常见的解决方案是定义函数,并(ab)使用默认参数创建函数的局部变量,该变量在创建函数时保存
i
的值,而不是调用:

i = 1
f = lambda i=i: print(i)

f()  # 1
i = 2
f()  # 1

如果命名让您感到困惑,您可以使用
f=lambda x=i:print(x)

您的问题实际上与
异步IO
无关。
lambda:asyncio中的
k
v
。确保未来(self.do\u something(k,v))
仍然引用外部范围中的变量。在调用函数时,它们的值会发生变化:

i = 1
f = lambda: print(i)

f()  # 1
i = 2
f()  # 2
常见的解决方案是定义函数,并(ab)使用默认参数创建函数的局部变量,该变量在创建函数时保存
i
的值,而不是调用:

i = 1
f = lambda i=i: print(i)

f()  # 1
i = 2
f()  # 1

如果命名让您感到困惑,您可以使用
f=lambda x=i:print(x)

除了其他人对
lambda
中错误的正确解释之外,还请注意,您甚至不需要
lambda
。由于
dou\u something
是一个协同程序,因此在事件循环的下一次迭代之前,仅调用它不会执行其任何代码,因此您很快就会自动获得
调用的效果。(这类似于调用生成器函数直到开始使用返回的迭代器才开始执行它。)

换句话说,您可以替换

self.loop.call_soon(lambda: asyncio.ensure_future(self.do_something(k, v)))
用更简单的

self.loop.create_task(self.do_something(k, v))

create\u task
在处理协同程序时确保未来的

除了其他人对
lambda
中错误的正确解释外,还要注意,您甚至不需要
lambda
。由于
dou\u something
是一个协同程序,因此在事件循环的下一次迭代之前,仅调用它不会执行其任何代码,因此您很快就会自动获得
调用的效果。(这类似于调用生成器函数直到开始使用返回的迭代器才开始执行它。)

换句话说,您可以替换

self.loop.call_soon(lambda: asyncio.ensure_future(self.do_something(k, v)))
用更简单的

self.loop.create_task(self.do_something(k, v))
create_task
确保未来
处理协同程序时的任务