Python模式匹配:防护和或-模式可能不能很好地组合

2020-07-03 00:53:55

PEP622建议向Python添加模式匹配构造。模式匹配允许程序员使用反映构造语法的语法来分解数据。该提案使Python与许多其他现代编程语言保持一致,如Haskell、OCaml和Rust。然而,提案中包含的两个特性(或-模式和卫士)以一种可能令人惊讶的方式交互-请参阅本文以了解这种交互的解释,因为它与OCaml相关,OCaml是另一种同时具有或-模式和卫士的语言。

您可以在cpython的这个分支上尝试这个PEP的正在进行的实现:https://github.com/brandtbucher/cpython/tree/patma.。

在本文的其余部分,我将简要总结模式匹配,定义或模式和保护,并在Python上下文中展示令人惊讶的行为。

在提议之前,您可以编写一个函数,根据一对动物的种类对它们进行分类。

类狗:def__init__(Self,Name):Self。name=name类猫:def__init__(self,age):self。年龄=年龄定义分类(宠物):如果是实例(宠物,猫):打印(";一只年龄为{}岁的猫。格式(宠物。年龄))Elif is instance(宠物,狗):打印(";一只名叫{}";的狗。格式(宠物。名字))分类(狗(#34;Fido&34;))#打印:一只名叫菲多的狗分类(猫(3))#打印:一只3岁的猫。

定义分类(宠物):匹配宠物:案例猫(年龄=年龄):打印(";一只已经{}岁的猫。格式(年龄))案例狗(名称=名称):打印(";一只名为{}";的狗。格式(名称))。

换句话说,模式匹配允许您检查一段数据,同时匹配其形状并将其字段的值绑定到变量。在上面的代码中,Cat(age=age)是一个与Cat值匹配的模式,并绑定到名为age的变量。

该提案给出了该构造的语义的非正式描述,但是非正式描述没有解释引入的两个特性之间的交互:OR-模式和模式保护。事实证明,正如目前实现的那样,这些特性之间的交互有些令人惊讶,如果这个PEP最终被接受,可能会产生至少一个高分的堆栈溢出问题。

下面是一个或模式的示例:猫(年龄=3岁)|狗(名称=";FIDO";)。这个图案与三岁大的猫或狗Fido相匹配。在实践中:

#我有一只三岁的猫和一只叫Fido的狗。#如果宠物可以是我的,这个函数返回true。def couldBeMyPet(宠物):匹配宠物:Case Cat(年龄=3)|Dog(Name=";FIDO";):Return True Case_:Return False Print(couldBeMyPet(Cat(3)#Prints True Print(couldBeMyPet(Dog(";Rover";)#Prints False。

模式保护是可以包含在case分支中的布尔表达式;仅当模式保护的计算结果为true时才采用case分支。

定义分类(宠物):匹配宠物:案例猫(age=age),如果年龄%2==0:打印(";这只猫的年龄是偶数!";)案例狗(name=name)如果排序(Name)==list(Name):print(";这只狗的名字按字母顺序!";)case_:print(";我对这只宠物没什么好说的。";)分类(猫(4))#打印";这只猫的年龄是偶数!";分类(狗(";阿贝&34;))#打印";这只狗的名字是按字母顺序排列的!";分类(狗(";菲多&34;))#打印";关于这只宠物我没什么好说的。

在上面的示例中,如果age%2==0和if sorted(Name)==list(Name)是模式保护,则仅当布尔表达式的计算结果为True时才允许采用该案例。

def do EitherCatHaveAnEvenAge(pet1,pet2):Match(pet1,pet2):case(Cat(age=age),_)|(_,Cat(age=age))if age%2==0:返回True case_:返回假打印(Do EitherCatHaveAnEvenAge(Cat(2),Cat(4)#打印True Print(Do EitherCataveAnEvenAge(Cat(2),Cat(4))。cat(4)#打印错误(?)。

最后一个(是否EitherCatHaveAnEvenAge(Cat(5),Cat(4)似乎应该计算为True。毕竟,or-模式中的一个模式似乎与第二只猫相匹配,这是一只年龄为偶数的猫。

如果年龄%2==0,(Cat(5),Cat(4))是否与(Cat(age=age),_)|(_,Cat(age=age))匹配?要回答这个问题,您头脑中的语义可能如下所示:

那么,(Cat(5),Cat(4))确实与OR模式的第一个模式(Cat(age=age),_)匹配。此匹配将值5绑定到变量age。

现在,如果年龄%2==0,我们检查防护。由于5%2==0的计算结果为FALSE,因此此检查失败。

返回到or模式,看看是否有其他方法可以匹配它。在这种情况下,(Cat(5),Cat(4))也与OR模式的第二个模式(_,Cat(age=age))匹配。这会将值4绑定到变量age。

现在,如果年龄%2==0,则保护成功,因为4%2==0的计算结果为True。

这是完全合理的,但实现并不是这样做的。该实现的语义更类似于以下内容:

因为警卫失败了,所以案子没人接!取而代之的是,下一个案件将开庭审理。此案例(case_:)总是成功,因此我们返回false。

换句话说,模式匹配没有回溯语义,即使一个值可以以多种不同的方式匹配或模式。

我喜欢模式匹配,我很高兴看到它可能会被像Python这样的流行语言采用。但这种特殊的功能交互可能会让新手感到困惑。让我们比较一下其他语言的方法。

在这里,OCaml具有与Python相同的语义,但是OCaml编译器可以发出警告(因为编译器有足够的类型信息来判断或模式的组成模式是否可能以不同的方式匹配相同的值)。

当x=2->;print_endline";:)";|_->;print_endline";:(";

警告57:受保护的模棱两可的变量或模式变量;变量x可能匹配不同的参数。(请参阅手册第9.5节)。

Rust具有模式匹配和保护,并具有实验性的或_模式特性。与Python不同,它实现了回溯语义。这本身就很奇怪,特别是在存在副作用的情况下。请看一下这个节目:

#![Feature(Or_Patterns)]//提示用户是否接受匹配Pub FN检查(x:I32)->;bool{use std::IO::{stdin};println!(";检查此匹配:{}。我们应该接受吗?y/n";,x);让mut s=string::new();stdin().read_line(&;mut s)。期望(";没有输入正确的字符串";);返回s.trim()==";y";}pub fn main(){Match(1,2){(x,_)|(_,x)if check(X)=>;{println!(";:)";)}_=>;{println!(";::(";)}。

$Cargo+夜间运行检查此匹配:1.我们应该接受它吗?Y/nn检查这场比赛:2。我们应该接受吗?Y/NY:)。

我不确定Python的正确决策是什么。或者-图案和护卫可能就是构图不好。