46

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

4

2 回答 2

17

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
于 2020-10-28T18:07:00.560 回答
-3

这是一个相对 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 库,它实现了__init____repr____eq__,但它也冻结了对象。我包含它是因为它减少了实现规模,但它也广泛可用且相当稳定。

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

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

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

问题

这并不严格限制变体集。

于 2019-03-25T21:22:43.947 回答