Python 修饰异步函数的注释
Python annotations for decorated async functions
我在为防止 aiohttp 错误而修饰的协程上使用注释时遇到困难。
我有两个功能:
from typing import Callable, Awaitable, Optional
from os import sep
import aiofiles
import aiohttp
from asyncio.exceptions import TimeoutError
from aiohttp.client_exceptions import ClientError
def catch_aiohttp_errors(func: Callable[..., Awaitable]) -> Callable[..., Awaitable]:
async def wrapper(*args):
try:
return await func(*args)
except (TimeoutError, ClientError):
return None
return wrapper
@catch_aiohttp_errors
async def download(url: str, download_path: str, filename: str, suffix: str) -> Optional[str]:
async with aiohttp.ClientSession() as session:
async with session.get(url) as response:
async with aiofiles.open(download_path + sep + filename + '.' + suffix, 'wb') as file:
async for chunk in response.content.iter_chunked(1024):
await file.write(chunk) if chunk else await file.write(b'')
return download_path + sep + filename + '.' + suffix
实现装饰器功能的主要原因是我有几个使用 aiohttp 的异步函数,我不想在每个类似的函数中都写 try/except
语句。
我遇到的问题是我的第二个函数的正确注释。
如您所见,它 returns str
。但是如果会报错,会根据装饰器函数的try/except
部分returnNone
。
用 Optional[str]
注释这样的函数是否正确?
我建议使用 TypeVar
作为 Awaitable
类型参数以停止丢失有关装饰函数的信息:在您的示例中调用 download
的结果将是类型 Any
.此外,使用 ParamSpec
将有助于保留参数。最后,这样的事情应该可行(假设 python 3.10,否则将所有未知的 typing
导入替换为 typing_extensions
:
from typing import Callable, Awaitable, Optional, TypeVar, ParamSpec
from functools import wraps
_T = TypeVar('_T')
_P = ParamSpec('_P')
def catch_aiohttp_errors(func: Callable[_P, Awaitable[_T]]) -> Callable[_P, Awaitable[Optional[_T]]]:
@wraps(func)
async def wrapper(*args: _P.args, **kwargs: _P.kwargs) -> Optional[_T]:
try:
return await func(*args)
except Exception:
return None
return wrapper
@catch_aiohttp_errors
async def download(url: str, download_path: str, filename: str, suffix: str) -> str:
return 'foo'
现在download
有签名
def (url: builtins.str, download_path: builtins.str, filename: builtins.str, suffix: builtins.str) -> typing.Awaitable[Union[builtins.str, None]]
此外,您现在不必手动添加 Optional
- 装饰器即可。 Playground with this solution
我在为防止 aiohttp 错误而修饰的协程上使用注释时遇到困难。 我有两个功能:
from typing import Callable, Awaitable, Optional
from os import sep
import aiofiles
import aiohttp
from asyncio.exceptions import TimeoutError
from aiohttp.client_exceptions import ClientError
def catch_aiohttp_errors(func: Callable[..., Awaitable]) -> Callable[..., Awaitable]:
async def wrapper(*args):
try:
return await func(*args)
except (TimeoutError, ClientError):
return None
return wrapper
@catch_aiohttp_errors
async def download(url: str, download_path: str, filename: str, suffix: str) -> Optional[str]:
async with aiohttp.ClientSession() as session:
async with session.get(url) as response:
async with aiofiles.open(download_path + sep + filename + '.' + suffix, 'wb') as file:
async for chunk in response.content.iter_chunked(1024):
await file.write(chunk) if chunk else await file.write(b'')
return download_path + sep + filename + '.' + suffix
实现装饰器功能的主要原因是我有几个使用 aiohttp 的异步函数,我不想在每个类似的函数中都写 try/except
语句。
我遇到的问题是我的第二个函数的正确注释。
如您所见,它 returns str
。但是如果会报错,会根据装饰器函数的try/except
部分returnNone
。
用 Optional[str]
注释这样的函数是否正确?
我建议使用 TypeVar
作为 Awaitable
类型参数以停止丢失有关装饰函数的信息:在您的示例中调用 download
的结果将是类型 Any
.此外,使用 ParamSpec
将有助于保留参数。最后,这样的事情应该可行(假设 python 3.10,否则将所有未知的 typing
导入替换为 typing_extensions
:
from typing import Callable, Awaitable, Optional, TypeVar, ParamSpec
from functools import wraps
_T = TypeVar('_T')
_P = ParamSpec('_P')
def catch_aiohttp_errors(func: Callable[_P, Awaitable[_T]]) -> Callable[_P, Awaitable[Optional[_T]]]:
@wraps(func)
async def wrapper(*args: _P.args, **kwargs: _P.kwargs) -> Optional[_T]:
try:
return await func(*args)
except Exception:
return None
return wrapper
@catch_aiohttp_errors
async def download(url: str, download_path: str, filename: str, suffix: str) -> str:
return 'foo'
现在download
有签名
def (url: builtins.str, download_path: builtins.str, filename: builtins.str, suffix: builtins.str) -> typing.Awaitable[Union[builtins.str, None]]
此外,您现在不必手动添加 Optional
- 装饰器即可。 Playground with this solution