openlang

为什么 ClassVar 里面不能出现类型变量?

PEP-526(https://peps.python.org/pep-0526/#class-and-instance-variable-annotations)说

ClassVar 参数里不能包含任何类型变量,无论嵌套多深都不行。如果 T 是类型变量,ClassVar[T] 和 ClassVar[List[Set[T]]] 都无效。

为什么会有这个限制?PEP 本身没有解释,也许 Typing github 的历史记录里有答案……

我给一个带有两个成员的泛型基类加类型注解时遇到了这个问题,一个是保存特定类型实例的实例属性,另一个是保存该实例类型本身的类属性。

class _TransportBase[Conn: (httpx.Client, httpx.AsyncClient), Cred: httpx.Auth](ABC):
    base_url: str
    connection: Conn
    _credential: Cred
    _credential_type: ClassVar[Type]  # intended as ClassVar[Type[Cred]]

    @property
    def credential(self) -> Cred:
        return self._credential

    @credential.setter
    @abstractmethod
    def credential(self, incoming: Any):
        if not isinstance(incoming, self._credential_type):
            self._credential = self._credential_type(incoming)
        else:
            self._credential = incoming

由于 ClassVar 有这样的限制,我必须在明确声明这个属性属于类,和表达它的类型与 _credential 类型的关系之间做出选择。

这只是早期草稿,修改时设计肯定会变。欢迎提出建议,不过我在这里问的是 ClassVar 本身。

从英语翻译查看原文

后续讨论 2

顺着彼此的依据读下去
  1. 固定链接

    ClassVar 属于 class 本身,而不是每种泛型参数组合都各有一个。

    换个例子,假设实现一个泛型链表。

    @dataclass
    class Link[T]:
        item: T
        following: Self | None = None
    
    class Chain[T]:
        start: ClassVar[Link[T] | None] = None
    
        def __str__(self) -> str:
            owner = type(self)
            items = list[T]()
            cursor = owner.start
            while cursor:
                assert isinstance(cursor, Link)  # assist the typechecker
                items.append(cursor.item)
                cursor = cursor.following
            return f'{owner.__qualname__}({', '.join(map(repr, items))})'
    
    numbers = Chain[int]()
    words = Chain[str]()
    
    Chain[int].start = Link[int](3, Link[int](4))
    print(numbers)  # 3, 4
    Chain[str].start = Link[str]('hello', Link[str]('world'))
    print(words)  # 'hello', 'world'
    print(numbers)  # unexpectedly! 'hello', 'world'

    int 链表和 str 链表共用同一个 .start。它只有一份。

    所以在我看来,ClassVar 里面放一个 TypeVar 没有什么意义。

    从英语翻译查看原文
  2. 固定链接

    明白了,我想现在理解这个理由了。

    我原本想让 ABC 的实现把 ClassVar 重写为更具体的类型,而 ABC 中的声明主要起文档作用,因为 ABC 本身的这个属性永远不会有实际值。

    另一个让我困惑的地方是,authlib 用的是 httpx2,而我的代码用的是 httpx,名字完全一样,但技术上属于不同类型。更新这个依赖后,我就可以直接使用基类,不必再尝试在 ClassVar 注解中对它做参数化了。

    从英语翻译查看原文

同一个问题的其他角度

  1. 怎样继承另一个类里面的类?

    你好, 在一个我无法修改代码的包里,类的层次结构如下。 我想给 Inner 加个方便使用的包装,但因为 Inner 依赖外层实例,目前只做到把它放进外层类的子类里面。 可惜,在多个地方继承 Outer,就意味着每个新子类里都得重复这个方便使用的 ca…

    回复 1最近活动 scala
  2. 比较 Lean4 中的 have 和 let 绑定

    我发现下面两个表达式 以及 行为完全相同。证明定理时也是这样。 它们可以互换吗?区别在哪里?

    回复 3最近活动 lean-4
  3. 初始化以字符串数组为值的 TypeScript Map

    我在创建一个 Map,其中每个条目都保存字符串数组。怎样在一条语句中同时声明类型并完成初始化? 我(天真地)这样试了一下。 结果是下面这个错误。

    回复 2最近活动 typescript