英文:
Properly type a callable returning a Generator expression
问题
我有以下的代码片段
```python
from contextlib import _GeneratorContextManager, contextmanager
GoodWrapperType = Callable[[int, str], _GeneratorContextManager[None]]
BadWrapperType = Callable[[int, str], Generator[None, None, None]]
def wrapper() -> GoodWrapperType:
@contextmanager
def inner(some_int: int, some_str: str) -> Generator[None, None, None]:
# 对 some_int 和 some_str 进行操作
yield
return inner
我想在pytest
测试套件的上下文中执行此操作,在这个上下文中,inner
会注入一些fixture。
我有上面的GoodWrapperType
和BadWrapperType
。Pylance告诉我不能将BadWrapperType
分配为wrapper
的返回类型。我已经找到了一种使用_GeneratorContextManager
的解决方法,但由于它以下划线开头,我不应该导入它。
是否有更好的方法?正确的方法是什么?
我在想是否没有直接的解决方案(至少我没有找到),这是否表明我不应该在Python中这样做。
<details>
<summary>英文:</summary>
I have the following snippet of code
```python
from contextlib import _GeneratorContextManager, contextmanager
GoodWrapperType = Callable[[int, str], _GeneratorContextManager[None]]
BadWrapperType = Callable[[int, str], Generator[None, None, None]]
def wrapper() -> GoodWrapperType:
@contextmanager
def inner(some_int: int, some_str: str) -> Generator[None, None, None]:
# Do things with some_int, some_str
yield
return inner
I want to do this in the context of a pytest
testing suite, where the wrapper is getting some fixture injected into the inner
.
I have above the GoodWrapperType
, and the BadWrapperType
. Pylance is telling me that I can't assign the BadWrapperType
as the return type of my wrapper
. I have found a solution with the GoodWrapperType
, using _GeneratorContextManager
, but since it's prefixed with an _
, I am not supposed to be importing it.
Is there a better way ? What's the proper way of doing this ?
I was wondering if the fact that there's no straight-forward solution (that I've found anyway), might be a hint that I shouldn't do this in Python.
答案1
得分: 2
wrapper
不返回一个返回生成器函数的可调用对象,它返回一个返回上下文管理器的可调用对象,这是一个具有 __enter__
和 __exit__
方法的类。
@contextmanager
装饰器会从应用到生成器函数的地方构建这个类,但最终结果不是一个生成器。
因此,我们希望将 inner
类型化为返回生成器函数,但将 wrapper
类型化为返回一个可调用对象,该可调用对象返回应用装饰器后的上下文管理器。
以下是经过类型检查的版本:
from contextlib import contextmanager, AbstractContextManager
from typing import Callable, Iterator, ContextManager
WrapperType = Callable[[int, str], AbstractContextManager[None]]
def wrapper() -> WrapperType:
@contextmanager
def inner(some_int: int, some_str: str) -> Iterator[None]:
# 对 some_int 和 some_str 进行操作
yield
return inner
如果我们不使用生成器的 send
和 return
特性,可以简化其类型为 Iterator[<yield type>]
,如上所示。
AbstractContextManager[<enter return type>]
是上下文管理器的泛型类型。(注意:在 Python 3.9 之前,您应该使用 from typing import ContextManager
)
由于我们的内部函数不产生任何内容,因此在这两种情况下的类型参数都是 None
。
然而,contextmanager
返回一个"上下文装饰器",这是一个具有双重用途的类,既可以作为上下文管理器,也可以作为装饰器使用。使用 AbstractContextManager
进行类型化将会隐藏结果的装饰器特性,使其无法通过下游类型检查来识别。
如果您需要这个特性,那么您最初的方法使用 _GeneratorContextManager
可能是最好的方式。(有一个公共的 contextlib.ContextDecorator
基类,但是 mypy 不支持交集类型,因此只使用公开导出的类型来同时将某物类型化为 AbstractContextManager
和 ContextDecorator
并不方便)。
英文:
wrapper
doesn't return callable returning a generator function, it returns a callable returning a context manager, which is a class with __enter__
and __exit__
methods.
@contextmanager
decorator builds this class from the generator function it is applied to, but the end result is not a generator.
So we want to type inner
as returning a generator function, but wrapper
as returning a callable which returns the context manager that is the result of applying the decorator.
Here is a version which type-checks:
from contextlib import contextmanager, AbstractContextManager
from typing import Callable, Iterator, ContextManager
WrapperType = Callable[[int, str], AbstractContextManager[None]]
def wrapper() -> WrapperType:
@contextmanager
def inner(some_int: int, some_str: str) -> Iterator[None]:
# Do things with some_int, some_str
yield
return inner
If we're not using the send and return features of the generator we can simplify its type to just Iterator[<yield type>]
as above.
AbstractContextManager[<enter return type>]
is a generic type for context managers. (Note: pre Python 3.9 you should use from typing import ContextManager
instead)
Since our inner function yields nothing, the type param in both cases is None
.
However contextmanager
returns a "context decorator", a dual-purpose class that can function as either a context manager or a decorator. Typing with AbstractContextManager
will hide the decorator-ness of the result from downstream type checks.
If you need that property then your original approach with _GeneratorContextManager
is probably the best way. (There is a public contextlib.ContextDecorator
base class but mypy lacks intersection types so there's no convenient way to type something as both an AbstractContextManager
& ContextDecorator
using only the publicly exported types).
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论