【问题标题】:How can I define algebraic data types in Python?如何在 Python 中定义代数数据类型?
【发布时间】:2013-04-21 22:23:48
【问题描述】:

如何在 Python(2 或 3)中定义代数数据类型?

【问题讨论】:

  • Python 首先有一个非常松散的类型系统;你到底想摆脱什么?
  • @Amber 没有松动。非常强大,但鸭子。
  • @Elazar 当我说“松散”时,我的意思是诸如没有特定类型签名的函数之类的东西。但你是对的,Python 不是弱类型的。
  • @Amber 我明白你的意思了。
  • @Amber 而不是“松散”,也许说它是动态类型而不是静态类型会更清楚。

标签: python algebraic-data-types


【解决方案1】:

这是一个相对 Pythonic 方式的 sum 类型的实现。

import attr


@attr.s(frozen=True)
class CombineMode(object):
    kind = attr.ib(type=str)
    params = attr.ib(factory=list)

    def match(self, expected_kind, f):
        if self.kind == expected_kind:
            return f(*self.params)
        else:
            return None

    @classmethod
    def join(cls):
        return cls("join")

    @classmethod
    def select(cls, column: str):
        return cls("select", params=[column])

打开一个解释器,你会看到熟悉的行为:

>>> CombineMode.join()
CombineMode(kind='join_by_entity', params=[])

>>> CombineMode.select('a') == CombineMode.select('b')
False

>>> CombineMode.select('a') == CombineMode.select('a')
True

>>> CombineMode.select('foo').match('select', print)
foo

注意:@attr.s 装饰器来自attrs library,它实现了__init____repr____eq__,但它也冻结了对象。我包含它是因为它减少了实现规模,但它也广泛可用且相当稳定。

Sum 类型有时称为标记联合。这里我使用kind 成员来实现标签。附加的每个变量参数通过列表实现。在真正的 Python 风格中,这在输入和输出端是鸭式的,但在内部没有严格执行。

我还包括了一个match 函数,它可以进行基本的模式匹配。类型安全也通过鸭子类型实现,如果传递的 lambda 函数签名与您尝试匹配的实际变体不一致,则会引发 TypeError

这些总和类型可以与乘积类型(listtuple)结合使用,并且仍然保留代数数据类型所需的许多关键功能。

问题

这并不严格限制变体集。

【讨论】:

    【解决方案2】:

    typing 模块提供了Union,它与 C 不同,是 sum 类型。您需要使用 mypy 进行静态类型检查,并且明显缺乏模式匹配,但结合元组(产品类型),这是两种常见的代数类型。

    from dataclasses import dataclass
    from typing import Union
    
    
    @dataclass
    class Point:
        x: float
        y: float
    
    
    @dataclass
    class Circle:
        x: float
        y: float
        r: float
    
    
    @dataclass
    class Rectangle:
        x: float
        y: float
        w: float
        h: float
    
    
    Shape = Union[Point, Circle, Rectangle]
    
    
    def print_shape(shape: Shape):
        if isinstance(shape, Point):
            print(f"Point {shape.x} {shape.y}")
        elif isinstance(shape, Circle):
            print(f"Circle {shape.x} {shape.y} {shape.r}")
        elif isinstance(shape, Rectangle):
            print(f"Rectangle {shape.x} {shape.y} {shape.w} {shape.h}")
    
    
    print_shape(Point(1, 2))
    print_shape(Circle(3, 5, 7))
    print_shape(Rectangle(11, 13, 17, 19))
    # print_shape(4)  # mypy type error
    

    【讨论】:

    • 嗨!如果您使用的是 mypy,您可以使用 assert_never 成语检查“模式匹配”的详尽性:github.com/python/typing/issues/735
    • 而 PEP 622(模式匹配)使得 sum 类型的使用更加类似于函数式语言。
    • 这并不是真正的 sum 类型,因为它只是依赖 RTTI 来识别类型。
    • 今天(2021 年 10 月 15 日)Python 3.10 发布,增加了对结构模式匹配的支持。 docs.python.org/3.10/whatsnew/…
    猜你喜欢
    • 1970-01-01
    • 2019-05-30
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-10-20
    • 1970-01-01
    相关资源
    最近更新 更多