使用python-asyncio为绑定方法创建临时异步计时器回调

pum*_*aus 7 python weak-references timer python-asyncio

我正在尝试async使用asyncio事件循环创建一种对绑定方法的计时器回调.现在的问题是绑定的异步方法不应该保存对实例的强引用,否则后者永远不会被删除.定时器回调应该只与父实例一样长.我找到了一个解决方案,但我认为它并不漂亮:

import asyncio
import functools
import weakref

class ClassWithTimer:
    def __init__(self):
        asyncio.ensure_future(
            functools.partial(
                ClassWithTimer.update, weakref.ref(self)
            )()
        )

    def __del__(self):
        print("deleted ClassWithTimer!")

    async def update(self):
        while True:
            await asyncio.sleep(1)
            if self() is None: break
            print("IN update of object " + repr(self()))

async def run():
    foo = ClassWithTimer()
    await asyncio.sleep(5)
    del foo

loop = asyncio.get_event_loop()
loop.run_until_complete(run())
Run Code Online (Sandbox Code Playgroud)

有没有更好,更pythonic的方式来做到这一点?计时器回调确实需要异步.没有asyncio,weakref.WeakMethod可能是要走的路.但是asyncio.ensure_future需要一个协程对象,所以在这种情况下它不起作用.

pum*_*aus 1

根据 Huazuo Gau 和 germn 的答案,我实现ensure_weakly_binding_future的基本上与绑定方法的实例相同ensure_future,但没有保留对绑定方法实例的强引用。它不会修改整体绑定(就像基于装饰器的解决方案所做的那样),并在删除父实例时正确取消未来:

import asyncio
import weakref

def ensure_weakly_binding_future(method):
    class Canceller:
        def __call__(self, proxy):
            self.future.cancel()

    canceller = Canceller()
    proxy_object = weakref.proxy(method.__self__, canceller)
    weakly_bound_method = method.__func__.__get__(proxy_object)
    future = asyncio.ensure_future(weakly_bound_method())
    canceller.future = future

class ClassWithTimer:
    def __init__(self):
        ensure_weakly_binding_future(self.update)

    def __del__(self):
        print("deleted ClassWithTimer!", flush=True)

    async def update(self):
        while True:
            await asyncio.sleep(1)
            print("IN update of object " + repr(self), flush=True)

async def run():
    foo = ClassWithTimer()
    await asyncio.sleep(5.5)
    del foo
    await asyncio.sleep(2.5)

loop = asyncio.get_event_loop()
loop.run_until_complete(run())
Run Code Online (Sandbox Code Playgroud)