使用与另一个通用类绑定的类型var通用类

发布于 2025-02-12 10:05:29 字数 3414 浏览 0 评论 0原文

如果foot中是通用存储一个foo的实例,然后为了避免写作,

class Bar(Generic[T, U, V]): 
    def __init__(self, foo: Foo[T, U], v: V): 
        ...

我们可以定义foot = typevar(“ foot”,foo [any,any]) 然后写入

class Bar(Generic[FooT, V]): 
    def __init__(self, foo: FooT, v: V): 
         pass

然后实例化bar变为bar [foo [int,str],bool]例如bar [int,str,bool]。对我来说,这是代码清晰度和可维护性方面的改进。但是,即使有一个更简单的玩具示例,我也遇到了这种模式的问题:

from typing import Generic, TypeVar, Union, TYPE_CHECKING

ResultT = TypeVar("ResultT")

class Query(Generic[ResultT]):
    def __init__(self, r: ResultT) -> None:
        self.r = r

QueryT = TypeVar("QueryT", bound=Query[Any])

class Client(Generic[QueryT]):
    def __init__(self, q: QueryT) -> None:
        self.q = q
        self.r = q.r

c = Client[Query[int]](Query[int](2))
if TYPE_CHECKING:
    reveal_type(c.q)
    reveal_type(c.q.r)
    reveal_type(c.r)
print(id(c.r) == id(c.q.r))  # true

----
mypytest.py:17: note: Revealed type is "mypytest.Query[builtins.int]"
mypytest.py:18: note: Revealed type is "builtins.int"
mypytest.py:19: note: Revealed type is "Any"
Success: no issues found in 1 source file

我很惊讶Mypy在这种情况下推断cqrcr 。 >是相同的。基本上,它假设crresultt的上限,在这种情况下是任何,而不是通过实例化的实例化所隐含类型查询[int]。但是令我困扰的是,该类型是根据访问模式正确解决的……

其他一些有趣的修改(这可能与我相对较新的方式与Python的仿制药一起工作有关),请根据我是否更改了queryt定义并运行mypy带有- strict

QueryT = TypeVar("QueryT", bound=Query[Union[int, str]])

---
mypytest.py:22: error: Value of type variable "QueryT" of "Client" cannot be "Query[int]"
mypytest.py:24: note: Revealed type is "mypytest.Query[builtins.int]"
mypytest.py:25: note: Revealed type is "builtins.int"
mypytest.py:26: note: Revealed type is "Union[builtins.int, builtins.str]"

这基本上是与上述相同的行为,但是现在上限是union [int,str] 正如预期的那样(但是,为什么现在的类型现在缩小到int?)。我在严格的模式下也会遇到错误 - 我认为允许此任务?

QueryT = TypeVar("QueryT", bound=Union[Query[int], Query[str]])

---
mypytest.py:18: note: Revealed type is "mypytest.Query[builtins.int]"
mypytest.py:19: note: Revealed type is "builtins.int"
mypytest.py:20: note: Revealed type is "Union[builtins.int, builtins.str]"

通过考虑Union来解决错误。我敢肯定,说明很明显,只需要考虑一下即可。

QueryT = TypeVar("QueryT", Query[int], Query[str])

---
mypytest.py:24: error: Need type annotation for "q"
mypytest.py:25: error: Need type annotation for "r"
mypytest.py:29: note: Revealed type is "Any"
mypytest.py:30: note: Revealed type is "Any"
mypytest.py:31: note: Revealed type is "Any"

将TypeVar限制为两种类型的简单案例似乎是最简单的建议,但是它给了类型的检查器最大的问题。为什么在这里需要明确的注释以避免推断 ?

通过仅跳过中间queryt 类型,一切都在严格的情况下起作用:没有任何错误:

class Client(Generic[ResultT]):
    def __init__(self, q: Query[ResultT]) -> None:
        ...
c = Client[int](Query[int](2))
...

---
mypytest.py:39: note: Revealed type is "mypytest.Query[builtins.int]"
mypytest.py:40: note: Revealed type is "builtins.int"
mypytest.py:41: note: Revealed type is "builtins.int"

缺乏对最佳实践的进展/建议,我可能只会收集并重复所有原始通用类型用于课程; Mypy已经迫使您为继承而这样做,因此对于构图来说可能没什么大不了的。

If Foo is generic in T, U and Bar is generic in V but stores an instance of Foo, then to avoid writing

class Bar(Generic[T, U, V]): 
    def __init__(self, foo: Foo[T, U], v: V): 
        ...

we could define FooT = TypeVar("FooT", Foo[Any, Any]) and write

class Bar(Generic[FooT, V]): 
    def __init__(self, foo: FooT, v: V): 
         pass

Then instantiating Bar becomes Bar[Foo[int, str], bool] for example instead of Bar[int, str, bool]. To me this is an improvement in terms of code clarity and maintainability. But even with a simpler toy example, I run into problems with this pattern:

from typing import Generic, TypeVar, Union, TYPE_CHECKING

ResultT = TypeVar("ResultT")

class Query(Generic[ResultT]):
    def __init__(self, r: ResultT) -> None:
        self.r = r

QueryT = TypeVar("QueryT", bound=Query[Any])

class Client(Generic[QueryT]):
    def __init__(self, q: QueryT) -> None:
        self.q = q
        self.r = q.r

c = Client[Query[int]](Query[int](2))
if TYPE_CHECKING:
    reveal_type(c.q)
    reveal_type(c.q.r)
    reveal_type(c.r)
print(id(c.r) == id(c.q.r))  # true

----
mypytest.py:17: note: Revealed type is "mypytest.Query[builtins.int]"
mypytest.py:18: note: Revealed type is "builtins.int"
mypytest.py:19: note: Revealed type is "Any"
Success: no issues found in 1 source file

I am surprised mypy is inferring Any in this situation when c.q.r and c.r are identical. Basically it is assuming c.r is the upper bound of ResultT which is Any in this case, instead of the type implied by instantiation of Query[int]. But what bothers me is the type is correctly resolved depending on the access pattern...

Some other interesting modifications (which probably speak to my relatively newness to working with Python's generics), based on if I change the QueryT definitions and run mypy with --strict:

QueryT = TypeVar("QueryT", bound=Query[Union[int, str]])

---
mypytest.py:22: error: Value of type variable "QueryT" of "Client" cannot be "Query[int]"
mypytest.py:24: note: Revealed type is "mypytest.Query[builtins.int]"
mypytest.py:25: note: Revealed type is "builtins.int"
mypytest.py:26: note: Revealed type is "Union[builtins.int, builtins.str]"

This is basically the same behavior as above, but now the upper bound is Union[int, str] as expected (but again, why is the type now narrowed to int?). I also get an error in strict mode -- I thought this assignment would be allowed?

QueryT = TypeVar("QueryT", bound=Union[Query[int], Query[str]])

---
mypytest.py:18: note: Revealed type is "mypytest.Query[builtins.int]"
mypytest.py:19: note: Revealed type is "builtins.int"
mypytest.py:20: note: Revealed type is "Union[builtins.int, builtins.str]"

The error is resolved by factoring out the Union. I'm sure the explanation is fairly obvious, just need to think about it.

QueryT = TypeVar("QueryT", Query[int], Query[str])

---
mypytest.py:24: error: Need type annotation for "q"
mypytest.py:25: error: Need type annotation for "r"
mypytest.py:29: note: Revealed type is "Any"
mypytest.py:30: note: Revealed type is "Any"
mypytest.py:31: note: Revealed type is "Any"

This simple case of restricting the TypeVar to two types seems the simplest to reason about, yet it gives the type checker the most problems. Why does it need an explicit annotation here to avoid inferring Any?

By just skipping the intermediate QueryT type, everything works w/ no errors under strict:

class Client(Generic[ResultT]):
    def __init__(self, q: Query[ResultT]) -> None:
        ...
c = Client[int](Query[int](2))
...

---
mypytest.py:39: note: Revealed type is "mypytest.Query[builtins.int]"
mypytest.py:40: note: Revealed type is "builtins.int"
mypytest.py:41: note: Revealed type is "builtins.int"

Absent some insight into what's going on / advice on best practices, I will probably just collect and repeat all the primitive generic types in use for a class; mypy already forces you to do this for inheritance, so probably not a big deal for composition.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。
列表为空,暂无数据
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文