在 TypeVar 上绑定 TypeVar 的解决方法?

Workaround for TypeVar bound on a TypeVar?

提问人:joel 提问时间:8/21/2019 最后编辑:joel 更新时间:11/17/2022 访问量:2285

问:

有没有办法用 Python 的类型提示来表达这个 Scala 代码?

trait List[A] {
  def ::[B >: A](x: B): List[B]
}

我正在努力实现这种事情

class X: pass
class Y(X): pass
class Z(X): pass

xs = MyList(X(), X())  # inferred as MyList[X]
ys = MyList(Y(), Y())  # inferred as MyList[Y]

_ = xs.extended_by(X())  # inferred as MyList[X]
_ = xs.extended_by(Y())  # inferred as MyList[X]

_ = ys.extended_by(X())  # inferred as MyList[X]
_ = ys.extended_by(Y())  # inferred as MyList[Y]
_ = ys.extended_by(Z())  # inferred as MyList[X]

请注意,该类型初始化为 ,它的类型可以是任何内容。 是不可变的。有关更多详细信息,请参阅注释。MyListextended_byMyList

我试过了什么

from __future__ import annotations
from typing import TypeVar, Generic

B = TypeVar('B')
A = TypeVar('A', bound=B)


class MyList(Generic[A]):
    def __init__(*o: A):
        ...

    def extended_by(self, x: B) -> MyList[B]:
        ...

但我明白了(上面是 main.py)

main.py:5:错误:键入变量“main.B“是未绑定
的 main.py:5:注意:(提示:使用”Generic[B]“或”Protocol[B]“基类将”B“绑定到类内) main.py:5:注意:(提示:使用函数签名中的”B“将”B“绑定到函数内部)

Afaict,不允许绑定在.在这种情况下是否有解决方法?TypeVar

Python 类型 提示类型 边界

评论

1赞 Uli Sotschok 8/21/2019
你能描述一下你不使用 Scala Code 的情况下想要什么吗?如果不了解 Scala,就很难找到答案
2赞 a_guest 7/4/2020
@joelb 所以澄清一下,应该用某种类型初始化,比如说,然后它的方法必须用 的祖先调用(即 MRO;它不能是它自己)?MyListAextended_byAA
3赞 joel 7/4/2020
@a_guest和扩展它的类型(称为 )可以是任何内容。Scala 签名很聪明,因为任何两种类型都会有一个共同的祖先,这里 .如果 是 的子类,则 ,它变得微不足道。如果 是 的超类,则 是 。真正聪明的一点是,当既不是超类也不是子类时。然后成为共同的祖先。您已经扩展了它,但 Scala 将其视为其超类的实例。ACBCABACABCCBCB
1赞 joel 7/4/2020
@a_guest因此,在回答您的问题时,它们可以是任何类型
2赞 joel 7/7/2020
@szatkus它是一个静态类型检查的东西。运行时在很大程度上无关紧要

答:

-2赞 Cyrille Pontvieux 4/19/2022 #1
from __future__ import annotations

from typing import (
    TYPE_CHECKING,
    Generic,
    TypeVar,
)

B = TypeVar('B')
A = TypeVar('A')


class MyList(Generic[A]):
    def __init__(*o: A):
        ...

    def extended_by(self, x: B) -> MyList[B]:
        ...


class Y:
    ...


class X:
    ...


ys = MyList(Y(), Y())
xs = ys.extended_by(X())
if TYPE_CHECKING:
    reveal_locals()

这会产生:

test.py:32: note: Revealed local types are:
test.py:32: note:     xs: test.MyList[test.X*]
test.py:32: note:     ys: test.MyList[test.Y*]

我不明白和 之间的联系。你能举一个例子吗,比如类,这样我就可以更新我的答案了?ABYX

评论

0赞 joel 4/19/2022
谢谢,但你错过了界限,这是问题的核心。具体来说,with 和 正如你所定义的那样,应该推断为YXxsMyList[object]
0赞 joel 4/19/2022
有关和AB
0赞 joel 4/19/2022
我确实明白了你为什么从我说“我正在努力实现这种事情”的部分来到这个解决方案。这是这种边界作用的一个例子,但它缺少 scala trait 的其他一些关键功能
0赞 joel 4/19/2022
我在问题中添加了一个示例
0赞 Cyrille Pontvieux 4/20/2022
因此,如果我理解正确,该方法应该将结果转换为其参数和泛型 A 之间最常见的祖先?我不知道 1.如何在 Python 中做到这一点, 2.用例。extended_by
2赞 Dodezv 11/17/2022 #2

您正在尝试指定 这是 的超类型。但是,与其指定 应该是 的超类型,不如说它是任何类型要容易得多,然后 Union 是您需要的超类型。BABABA|BA

from typing import TypeVar, Generic
A = TypeVar("A", covariant=True)
B = TypeVar("B")

class MyList(Generic[A]):
    def __init__(*objects: A) -> None: ...
    def extended_by(self, other: B) -> MyList[A | B]: ...

class X: pass
class Y(X): pass
class Z(X): pass


xs = MyList(X(), X())  # inferred as MyList[X]
ys = MyList(Y(), Y())  # inferred as MyList[Y]

reveal_type(xs.extended_by(X())) # inferred as MyList[X]
reveal_type(xs.extended_by(Y())) # inferred as MyList[X]

reveal_type(ys.extended_by(X()))  # inferred as MyList[X]
reveal_type(ys.extended_by(Y()))  # inferred as MyList[Y]
reveal_type(ys.extended_by(Z()))  # inferred as MyList[Y|Z]
# MyList[Y|Z] is a subtype of MyList[X]

如果是不可变的,则其类型变量很可能是协变的。这意味着它是 的子类。我冒昧地使您的泛型参数协变,这也将使该方法的实现更容易。MyListMyList[X]MyList[Y]extended_by

注意:如果您必须使用 Python<3.10,请确保替换为 .|Union

注 2:这与方法相同,例如,请参阅 Typeshed 中的代码。list.__add__