【问题标题】:Type hints for dataclass defined inside a class with generic types在具有泛型类型的类中定义的数据类的类型提示
【发布时间】:2021-01-06 18:06:30
【问题描述】:

我知道标题很混乱,所以我以二叉搜索树为例:

使用普通类定义

# This code passed mypy test
from typing import Generic, TypeVar

T = TypeVar('T')
class BST(Generic[T]):
    class Node:        
        def __init__(
            self,
            val: T,
            left: 'BST.Node',
            right: 'BST.Node'
        ) -> None:
            self.val = val
            self.left = left
            self.right = right

以上代码通过mypy测试。

使用dataclass

但是,当我尝试使用dataclass 来简化Node 的定义时,代码在mypy 测试中失败了。

# This code failed to pass mypy test
from dataclasses import dataclass
from typing import Generic, TypeVar

T = TypeVar('T')
class BST(Generic[T]):
    @dataclass
    class Node:
        val: T
        left: 'BST.Node'
        right: 'BST.Node'

mypy 给了我这个错误信息:(test_typing.py:8val: T 行)

test_typing.py:8: error: Type variable "test_typing.T" is unbound
test_typing.py:8: note: (Hint: Use "Generic[T]" or "Protocol[T]" base class to bind "T" inside a class)
test_typing.py:8: note: (Hint: Use "T" in function signature to bind "T" inside a function)

查明问题

# This code passed mypy test, suggest the problem is the reference to `T` in the dataclass definition
from dataclasses import dataclass
from typing import Generic, TypeVar

T = TypeVar('T')
class BST(Generic[T]):
    @dataclass
    class Node:
        val: int # chose `int` just for testing
        left: 'BST.Node'
        right: 'BST.Node'

上面的代码再次通过了测试,所以我认为问题在于数据类定义中对@9​​87654332@的引用。有谁知道将来如何解决这个问题以实现我最初的目标?

【问题讨论】:

  • 在 BST 中定义 Node 有什么原因吗?
  • @MisterMiyagi 只想将Node 设为私有。我认为这在 Java 中很常见。
  • 请注意,它根本不是私有的。它只会有一个稍长的全局名称,并且会受到类范围规则的影响(您刚刚发现了其中一个)。
  • 是的,我知道 python 不支持真正的私有化。不过还是谢谢提醒。

标签: python python-typing python-dataclasses


【解决方案1】:

让我们从PEP 484 中关于类型变量范围规则的内容开始:

嵌套在另一个泛型类中的泛型类不能使用相同类型的变量外部类的类型变量的范围不包括内部的

T = TypeVar('T')
S = TypeVar('S')

class Outer(Generic[T]):
   class Bad(Iterable[T]):       # Error
       ...
   class AlsoBad:
       x = None  # type: List[T] # Also an error

   class Inner(Iterable[S]):     # OK
       ...
   attr = None  # type: Inner[T] # Also OK

这就是为什么您的带有嵌套装饰类的示例不起作用的原因。

现在让我们回答为什么该示例适用于采用TypeVar 变量的__init__ 函数的问题。

这是因为方法 __init__ 被 mypy 视为具有独立 TypeVar 变量的泛型方法。例如reveal_type(BST[int].Node.__init__) 显示Revealed type is 'def [T, T] (self: main.BST.Node, val: T'-1, left: main.BST.Node, right: main.BST.Node)'。即T 在此处未绑定到int

【讨论】:

    【解决方案2】:

    嵌套类不能从其包含的类中隐式使用 TypeVar:嵌套类必须是 Generic 和未绑定的 TypeVar

    BT = TypeVar('BT')
    NT = TypeVar('NT')
    
    class BST(Generic[BT]):
        root: 'BST.Node[BT]'  # root note is of same type as search tree
    
        @dataclass
        class Node(Generic[NT]):  # generic node may be of any type
            val: NT
            left: 'BST.Node[NT]'
            right: 'BST.Node[NT]'
    

    这使得嵌套类在其包含类之外被引用时定义良好。根本问题是嵌套类与外部专家分开存在——推理只知道BST.NodeBST.Node[T],而不知道BST[T].Node


    由于嵌套不提供任何功能优势,通常更简单地定义单独的类重用相同的TypeVar

    T = TypeVar('T')
    
    class BST(Generic[T]):
        root: 'Node[T]'
    
    @dataclass
    class Node(Generic[T]):
        val: T
        left: 'Node[T]'
        right: 'Node[T]'
    

    【讨论】:

      猜你喜欢
      • 2020-10-24
      • 2020-08-26
      • 2015-11-14
      • 2015-07-09
      • 2020-03-27
      • 2020-11-06
      • 2019-07-21
      • 2019-10-02
      • 1970-01-01
      相关资源
      最近更新 更多