【问题标题】:Recursive data types in PythonPython中的递归数据类型
【发布时间】:2020-09-01 20:14:18
【问题描述】:

Python 中最接近 Haskell 中递归数据类型的可能是什么? (即在定义自身时使用类型自己的定义。)

编辑:

为了更具体地定义递归类型,下面是 Haskell 中的二叉树:

data Tree a             = Leaf a | Branch (Tree a) (Tree a) 

我的阅读方式如下:二叉树可以是叶子,也可以包含两个子树,它们又是类型树本身。

有关 Haskell 中递归类型的更多信息,您可以参考这里:https://www.haskell.org/tutorial/goodies.html

我真正想到的是将 Haskell 中的单词树定义转换为 Python。这是我的一个旧项目中WordTree 的定义:

data WordTree = Word String | Subword String [WordTree] | Root [WordTree]

WordTree 是一个 n-arytree 结构,其中单词的公共前缀存储在双亲中,其余部分以排序方式存储在树的叶子中。我相信这种类型定义有点类似于 Trie。然而,由于 Haskell 是一种函数式编程语言,它允许这种类型定义是递归的。对于这种类型的定义,在 Python 中(或者在一般的面向对象编程中)最接近的可能是什么?

【问题讨论】:

  • 也许是一个类,其中 init 有 self.self = self :)
  • 你能否在 Haskell 中举一个这样的例子,也许有人可以告诉你它是如何在 python 中完成的
  • 您能描述一个用例吗?
  • 鉴于所使用的类型系统截然不同,Python 中并没有什么非常相似的地方。或许可以使用合适的元类来实现 Haskell 风格的代数类型,但我认为这并不简单。
  • 这能回答你的问题吗? Defining a recursive type hint in Python?

标签: python haskell type-hinting algebraic-data-types recursive-datastructures


【解决方案1】:

由于 Python 是动态类型的,因此定义您需要的任何类都没有问题。

class Tree:
    left = None
    right = None
    def __init__(self, left, right):
        self.left = left
        self.right = right

即使您对键入这些定义感兴趣,您也可以像使用任何其他基于类的面向对象语言一样:

from typing import Union

class Tree:
    left: Union['Tree', int]
    right: Union['Tree', int]
    def __init__(self, left: Union['Tree', int], right: Union['Tree', int]) -> None:
        self.left = left
        self.right = right

注意类型名称使用字符串(在较新的 Python 版本中可以避免使用)。

在 mypy 中查看 open issue 以了解直接递归代数类型,例如

Tree = Union[Tuple['Tree', 'Tree'], int]

定义您描述的WordTree 的最常见(尽管不一定推荐)方式是使用超类和浅层次结构:

from typing import List, final

class WordTree: pass

@final
class Word(WordTree):
    word: str

@final
class Subword(WordTree):
    subword: str
    children: List[WordTree]

@final
class Root(WordTree):
    children: List[WordTree]

使用这样的实现可能需要使用isinstance 检查(尽管Python3.10 为您提供了nice sugar)。在这个例子中省略了构造函数以避免混乱;您可能想使用dataclass 轻松获取它们以及其他类型的行为。

迄今为止,Python 无法禁止不相关的类从 WordTree 继承,从而破坏了对此类程序进行静态推理的一些能力。

其他一些 OOP 语言,例如 Scala 和 Kotlin 以及(很快)Java,可以采用这样的定义(使用 sealed classes)并为您提供类似于函数式语言所提供的类型检查和句法结构比如 Haskell。


据我所知,这种设计通常只推荐用于纯数据类,例如 AST。它不太适合定义面向用户的容器,例如 trie,因为它暴露了数据结构的内部工作。因此,即使您采用该设计,您也可能希望将其用作实现细节,并使用另一个类Trie,供客户端代码通过定义良好的 API 使用。该类可以有一个WordTree 字段,或任何其他实现相同逻辑的方式。

IMO 这对于面向对象设计与功能设计的区别至关重要。后者侧重于数据流和静态推理,而前者侧重于 API、可扩展性和解耦。我认为在语言和环境之间移植时注意这一点很有帮助 - 尽管如上所述,一些语言试图同时启用这两种设计方法。

【讨论】:

    【解决方案2】:

    这是 Python 3.10 中 Haskell 二叉树的等效实现。可以使用mypy 进行静态类型检查。

    from __future__ import annotations
    from dataclasses import dataclass
    from typing import Generic, TypeVar
    
    T = TypeVar("T")
    
    @dataclass
    class Branch(Generic[T]):
        value: T
        left: Tree[T]
        right: Tree[T]
    
    @dataclass
    class Leaf(Generic[T]):
        value: T
    
    Tree = Branch[T] | Leaf [T]
    

    您可以这样使用它(注意contains 函数中的模式匹配——Python 3.10 的新特性):

    def contains(tree: Tree[T], value: T):
        match tree:
            case Leaf(x):
                return x == value
            case Branch(x, left, right):
                return x == value or contains(left, value) or contains(right, value)
    
    tree = Branch(
        1,
        Branch(2, Leaf(3), Leaf(4)),
        Branch(5, Leaf(6), Branch(4, Leaf(7), Leaf(8)))
    )
    
    assert contains(tree, 1)
    assert contains(tree, 5)
    assert contains(tree, 8)
    

    要实现您的 WordTree,您需要执行以下操作:

    from __future__ import annotations
    from dataclasses import dataclass
    
    @dataclass
    class Word:
        value: str
    
    @dataclass
    class Subword:
        value: str
        trees: list[WordTree]
    
    @dataclass
    class Root:
        trees: list[WordTree]
    
    WordTree = Word | Subword | Root
    

    关于导入的说明:

    • from __future__ import annotations 允许您使用尚未定义的类型的名称进行注释。
    • @dataclass 自动为你定义了一个构造函数。

    【讨论】:

      猜你喜欢
      • 2014-01-18
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2016-11-15
      • 1970-01-01
      • 2016-09-20
      • 2019-05-06
      相关资源
      最近更新 更多