在 Ocaml/Haskell/... 等函数式语言中,我可以输入类似以下内容:
type expr =
| Nb of float
| Add of expr * expr
| Soust of expr * expr
| Mult of expr * expr
| Div of expr * expr
| Opp of expr
let rec eval x = match x with
| Nb n -> n
| Add (e1, e2) -> (eval e1) +. (eval e2)
| Soust (e1, e2) -> (eval e1) -. (eval e2)
| Mult (e1, e2) -> (eval e1) *. (eval e2)
| Div (e1, e2) -> (eval e1) /. (eval e2)
| Opp n -> -. (eval n)
一旦我的代码编译完毕,我将保证对于任何
x
类型的 expr
,eval x
将始终产生 float
类型的输出。这明显意味着我的模式匹配不会忘记任何情况,因此如果后来我在 expr
类型中添加一个新项目,它将无法编译,直到我在模式匹配中添加这个新情况。
遗憾的是,我在 python 中找不到任何可以提供如此强有力保证的东西,包括 Python 10 的打字系统……我错过了什么吗?
我很惊讶地发现这个功能实际上是可能的! (至少使用python 3.11,之前没有尝试过)
这要归功于 https://peps.python.org/pep-0622/#exhaustiveness-checks,它提供了必须通过类型检查的
match
构造。
使用
$ python foo.py
执行以下代码,并使用 $ mypy --strict foo.py
运行类型检查。
演示:
### Define our types
# See https://github.com/python/mypy/issues/17139 to get a nicer
# syntax once the bug is solved
class Mult:
# Expr is not yet defined, so we use forward references
# https://peps.python.org/pep-0484/#forward-references
left: 'Expr'
right: 'Expr'
def __init__(self, left:'Expr', right:'Expr'):
self.left = left
self.right = right
class Add:
# Expr is not yet defined, so we use forward references
# https://peps.python.org/pep-0484/#forward-references
left: 'Expr'
right: 'Expr'
def __init__(self, left:'Expr', right:'Expr'):
self.left = left
self.right = right
class Const:
val: int
def __init__(self, val:int):
self.val = val
Expr = Const | Add | Mult
### Define our functions
def my_eval(e : Expr) -> int:
match e:
case Const():
return e.val
case Add():
return my_eval(e.left) * my_eval(e.right)
case Mult():
return my_eval(e.left) + my_eval(e.right)
### Use them
print(my_eval(Const(42)))
print(my_eval(Add(Const(42),Const(45))))
$ python foo.py
42
1890
$ mypy --strict foo.py
Success: no issues found in 1 source file
如果您现在决定删除一项,例如通过评论
Add()
案例,您会收到错误(我必须承认,不是很清楚):
$ mypy --strict foo2.py
foo2.py:37: error: Missing return statement [return]
Found 1 error in 1 file (checked 1 source file)
待办事项 找到一个更简单的接口来定义类型会很棒,特别是我发现
class Mult(tuple[int, int]):
pass
但到目前为止它似乎在内部不起作用
mypy
https://github.com/python/mypy/issues/17139