带有asynccontextmanager的Python abstractmethod

时间:2019-01-15 18:39:06

标签: python python-3.x python-asyncio mypy

我有几个速率限制器类(一个未显示),我想为其创建ABC。 request方法是异步上下文管理器。使用下面显示的代码,我得到了

  

与超类型“ RateLimiterInterface”不兼容的“请求”签名

如果我尝试用@asynccontextmanager装饰抽象方法,则会出现输入错误:

  

“ asynccontextmanager”的参数1具有不兼容的类型“ Callable [[RateLimiterInterface],Coroutine [Any,Any,AsyncIterator [Any]]]”;预期为“ Callable [...,AsyncIterator []]”

我该怎么做?

class RateLimiterInterface(abc.ABC):
    @abc.abstractmethod
    async def request(self) -> AsyncIterator:
        pass

class LeakyBucketRateLimiter(RateLimiterInterface):
    def __init__(self, max_tokens: Optional[int] = None, rate: float = 60) -> None:
        self.max_tokens = max_tokens
        self.rate = rate
        self._bucket = max_tokens
        self._last_added_at = time.time()

    @contextlib.asynccontextmanager
    async def request(self) -> AsyncIterator:
        if self._bucket is None:
            yield
            return
        while not self._bucket:
            await asyncio.sleep(0)
            self._add_tokens(int((time.time() - self._last_added_at) * self.rate))
        self._bucket -= 1
        yield
        return

    def _add_tokens(self, num_tokens: int) -> None:
        if num_tokens == 0:
            return
        self._bucket += num_tokens
        if self._bucket > self.max_tokens:
            self._bucket = self.max_tokens
        self._last_added_at = time.time()

2 个答案:

答案 0 :(得分:1)

我无法重现问题。下面的代码对我来说运行正常(Python 3.7.1)。

请测试它在您的环境中是否运行良好,是否存在其他问题,以提供最少的可复制示例。

import asyncio
import abc
import contextlib
from typing import AsyncIterator


class Test(abc.ABC):
    @abc.abstractmethod
    async def request(self) -> AsyncIterator:
        pass


class SubTest(Test):
    @contextlib.asynccontextmanager
    async def request(self) -> AsyncIterator:
        await asyncio.sleep(1)
        yield 1
        await asyncio.sleep(1)


async def main():
    obj = SubTest()
    async with obj.request() as res:
        print(res)


asyncio.run(main())

答案 1 :(得分:0)

我在输入时遇到了同样的问题,并以此方式解决了该问题:

import abc
import contextlib
import asyncio


class TestAbstract(metaclass=abc.ABCMeta):
    @contextlib.asynccontextmanager
    @abc.abstractmethod
    # Here the trick: you must declare an  asynchronous generator function,
    # not a regular coroutine function so it have to explicitly yield.
    async def foo(self):
        yield


class SubTest(TestAbstract):
    @contextlib.asynccontextmanager
    async def foo(self):
        yield


async def test_coro():
    async with SubTest().foo():
        print('OK')


asyncio.run(test_coro())