通过子类别更改Python的类型参数
Python的打字系统允许在类中进行通用:
class A(Generic[T]):
def get_next(self) -> T
这非常方便。但是,即使在3.11中使用自我类型,我也找不到一种方法来更改类型参数(t
),而无需指定类名。这是PEP 673的推荐用法: https://peps.pys.python.org/pep -0673/a
class Container(Generic[T]):
def foo(
self: Container[T],
) -> Container[str]:
# maybe implementing something like:
return self.__class__([str(x) for x in self])
问题是如果我想进行亚类容器:
class SuperContainer(Container[T]):
def time_travel(self): ...
然后,如果我有一个超本机的实例并在上面调用foo,那么打字将是错误的,并且认为它是一个不是超级城市的容器。
sc = SuperContainer([1, 2, 3])
sc2 = sc.foo()
reveal_type(sc2) # mypy: Container[str]
sc2.time_travel() # typing error: only SuperContainers can time-travel
isinstance(sc2, SuperContainer) # True
是否有一种公认的方法可以允许程序更改超级类中的类型参数,以保留子类的键入?
Python's typing system allows for generics in classes:
class A(Generic[T]):
def get_next(self) -> T
which is very handy. However, even in 3.11 with the Self type, I cannot find a way to change the type argument (the T
) without specifying the class name. Here's the recommended usage from PEP 673: Self Type: https://peps.python.org/pep-0673/a
class Container(Generic[T]):
def foo(
self: Container[T],
) -> Container[str]:
# maybe implementing something like:
return self.__class__([str(x) for x in self])
The problem is if I want to subclass container:
class SuperContainer(Container[T]):
def time_travel(self): ...
And then if I have an instance of SuperContainer and call foo on it, the typing will be wrong, and think that it's a Container not SuperContainer.
sc = SuperContainer([1, 2, 3])
sc2 = sc.foo()
reveal_type(sc2) # mypy: Container[str]
sc2.time_travel() # typing error: only SuperContainers can time-travel
isinstance(sc2, SuperContainer) # True
Is there an accepted way to allow a program to change the type argument in the superclass that preserves the typing of the subclass?
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(2)
要解决此问题,您需要第二个通用类型参数,以表示
foo
的返回类型。任何
都可以。我们稍后会看到。到目前为止,一切都很好。让我们定义
容器
:请注意,我们必须忽略
foo
中的类型。这是因为Mypy错误地推断出类型(self)
的类型错误。它认为type(self)
返回容器[...]
(或一个子类),但实际上它返回container
(或子类)。当我们运行此代码时,您会看到。接下来,我们需要一些创建容器的方法。我们希望该类型看起来像
容器[T,容器[STR,Container [str,...]]]
。在类声明的第一行中,我们使该类的第二个类型参数是
selfstr
,它本身就是容器[str,any any]
。这意味着selfstr
的定义应与container [str,selfstr]
有限,因此我们应该获得container的上限
containe [str,container [str [str,str,str, ...]]
我们想要。这起作用:它将仅允许我们的递归类型(或子类)或任何
。不幸的是,Mypy不会推断递归通用类型,报告test.container [hindins.int,< nothing>]
,因此我们必须进行繁重的举重。时间✨魔术✨。_containerstr
别名将为我们提供签名的递归部分。然后,我们将公开containerComplete
,例如我们可以用作构造函数,例如:很棒!但是子类呢?我们只需要再次做同样的事情,因为我们的子类:
全部完成!现在让我们演示:
将所有内容放在一起,我们得到:
输出看起来像这样(您需要最近的Mypy版本):
您可以使用Metaclasses删除很多样板。这具有继承的额外优势。如果您覆盖
__调用__
,您甚至可以获取isInstance
正常工作(它不适用于通用类型别名 *完整
,它仍然有效可以自己上课)。请注意,这仅在Pycharm中部分起作用:
To solve this, you need a second generic type argument, to represent the return type of
foo
.The
Any
is okay. We'll see that later.So far so good. Let's define the
Container
:Note that we had to ignore the types in
foo
. This is because mypy has inferred the type oftype(self)
incorrectly. It thinks thattype(self)
returnsContainer[...]
(or a subclass), but in fact it returnsContainer
(or a subclass). You'll see that when we get to running this code.Next, we need some way of creating a container. We want the type to look like
Container[T, Container[str, Container[str, ...]]]
.In the first line of the class declaration, we made the second type-parameter of the class be a
SelfStr
, which is itselfContainer[str, Any]
. This means that definition ofSelfStr
should become bounded toContainer[str, SelfStr]
, so we should get an upper bound ofContainer[str, Container[str, ...]]
as we wanted. This works: it will only allow our recursive type (or subclasses) orAny
. Unfortunately, mypy won't do inference on recursive generic types, reportingtest.Container[builtins.int, <nothing>]
, so we have to do the heavy lifting. Time for some ✨ magic ✨.The
_ContainerStr
alias will give us the recursive part of the signature. We then exposeContainerComplete
, which we can use as a constructor, for example:Awesome! But what about subclasses? We just have to do the same thing again, for our subclass:
All done! Now let's demonstrate:
Putting everything together, we get:
And the output looks like this (you need a recent version of mypy):
You can remove a lot of the boilerplate using metaclasses. This has the added advantage that it's inherited. If you override
__call__
, you can even getisinstance
working properly (it doesn't work with the generic type aliases *Complete
, it still works fine for the classes themselves).Note that this only partially works in PyCharm:
我不知道它是如何工作的,但是我也可以使用
type [t]
来使用。我真的无法解释此代码,所以我只是要复制和粘贴,比我能告诉你的更好的人为什么。Mypy和Python都对此代码感到满意,因此我猜它有效(TM)。
I have no clue how this works, but I made it work with
Type[T]
too. I genuinely cannot explain this code so I'm just gonna copy and paste, better people than me can tell you why.Mypy and Python are both happy with this code so I guess It Works (TM).