python中的模式匹配
pyPMatch的Python项目详细描述
PypMatch
< Buff行情>这是正在进行的工作!预计会出现一些粗糙点,但仍会缺少一些功能。
pypmatch在python中提供模式匹配。它主要基于模式匹配 scala。它的主要目标是解构对象,从而检查是否有 给定的对象满足以特定方式解构的条件。
本文档对pypmatch及其功能进行了粗略的、未经润色的概述。可以找到更好的文档 在文档-文件夹中,特别是简介.下面还有一个常见问题解答。
PypMatch至少需要Python3.4。
示例
pypmatch最初是通过其抽象语法树(ast)为分析python代码而开发的。下面的例子
演示如何使用pypmatch的模式匹配来实现非常简单的代码优化程序。但是,没有什么
从pypmatch的角度来看,关于ast
-模块的特殊性,您可以将它与任何
否则,
importastfromastimportAdd,BinOp,Numdefsimplify(node):matchnode:caseBinOp(Num(x),Add(),Num(y)):returnNum(x+y)caseBinOp(Num(n=x),Sub(),Num(n=y)):returnNum(x-y)caseast.UnaryOp(ast.USub(),x@Num()):returnNum(-x.n)case_:returnnode
您将在"示例"文件夹中找到更多示例;只需运行"运行示例"即可。
文档文件夹中也有一些文档,特别是介绍
用法
安装PypMatch
要安装PypMatch库,请执行以下简单操作:
pip install pyPMatch
直接编译/执行代码
如果您只想在试驾时使用pypmatch,请使用下面所示的pyma_exec
。
frompmatchimportpama_execmy_code="""from random import randintmatch randint(0, 19): case 0: print("nothing") case 1 | 4 | 9 | 16: print("a square") case 2 | 3 | 5 | 7 | 11 | 13 | 17 | 19: print("a prime") case _: print("some other number")"""pama_exec(my_code)
同样可以使用函数pama_translate
:
frompmatchimportpama_translatemy_code="""from random import randintmatch randint(0, 19): case 0: print("nothing") case 1 | 4 | 9 | 16: print("a square") case 2 | 3 | 5 | 7 | 11 | 13 | 17 | 19: print("a prime") case _: print("some other number")"""code,match_code=pama_translate(my_code)print(code)# the translation of the original codeprint("="*80)print(match_code)# additional code for the actual matching
从python模块导入代码
安装自动导入钩子可能更方便,因此包/项目中的所有模块
使用pypmatch-编译器编译(如果它们包含case
语句,即)。已安装自动导入
直接通过导入启用自动导入
frompmatchimportenable_auto_importfromrandomimportrandintimportmy_modulemy_module.test_me(randint(0,19))
然后,my_module.py
的内容类似于:
deftest_me(arg):matcharg:case0:print("nothing")case1|4|9|16:print("a square")case2|3|5|7|11|13|17|19:print("a prime")caseint():print("some other number")case_:print("please provide an integer")
装饰功能
如果您不希望pypmatch干扰您的代码,您仍然可以使用函数形式的模式匹配 装饰师。你把这个图案作为一根绳子放进装饰器里。然后,函数本身接受 模式作为参数。
frompmatchimportcase@case("17")deftest_me():print("This is correct!")@case("11 | 13 | 17 | 19")deftest_me():print("At least, it's still a prime number")@case("i @ int()")deftest_me(i):print("The result",i,"is wrong")@case("x")deftest_me(x):print("Not even an integer?",x)test_me(sum([2,3,5,7]))
nb:毕竟,使用decorators对于这个库不是一个特别好的主意。原因是,在 与预编译模块不同,并非所有名称都可以正确解析。因此你可能会得到一些 惊喜,甚至崩溃。
如何编写模式
模式可以使用下面描述的元素来表示。
< Buff行情>如上所述:并不是所有的东西都得到了充分的实现和测试!特别是,只有有限的
目前支持a+b
。
foo()
匹配类的所有实例foo(a,b,c)
解构foo的实例,该实例必须产生三个值,然后必须与模式匹配
a
、b
和c
;分别是foo(egg=a,ham=b)
匹配foo的所有实例,其中属性
egg
和ham
匹配模式a
和b
分别;12
,'abc'
,true
和其他常量如果值等于常量,则匹配一个值;{a':a,'b':b}
如果值有元素'a'
,则匹配,并且元素'b'
匹配a
并且 分别是。值可以是dictionary,但不必是。您还可以查看 列表中的元素,例如,使用{2:a,5:b}
;{'re}
如果值是符合给定正则表达式的字符串,则匹配;{foo}
匹配字符串类型的任何值v,对于该值,v.isfoo()
的计算结果为true
。例如,{lower}
将匹配v.islower()
为真的任何字符串;a b c
如果至少有一种模式匹配,a
,b
,c
匹配;[a,b,c,…,d,e]
匹配前三个元素匹配a
,b
和c
的任何序列以及最后两个元素 元素分别匹配d
和e
。这还包括python常用的迭代器解包,例如[a,b,*c,d]
,解释为[a,b,c@…,d]
;a+b
如果字符串可以分解为a
和b
部分,则匹配该字符串。例如,'('+x+')'
匹配 任何在括号中包含一些文本的字符串,并将中间部分返回为x
;x@a
如果模式a
匹配,则匹配;如果整个匹配是 成功;是匹配所有内容的通配符;
*\u
和..
是序列中使用的通配符,通常具有完全相同的含义;x
是x@/code>的缩写,匹配所有内容,并将其绑定到
x
有些特殊情况和限制您应该注意:
- 任何变量
x
只能在单个模式(acase
语句)中绑定一次。再利用是合法的x
在不同的case
语句中,但不能有类似于foo(x,x)
的语句。如果你需要测试foo
中的值相等,如果x==y则改用foo(x,y);
- 不能将任何内容绑定到替代项中。因此,
a(x@b)c
是非法的; - 无法将任何内容绑定到通配符。虽然
\u
是python中的常规名称,但它有特殊的 在pypmatch模式中的含义。然而,类似于@a
的内容并不非法,而是等同于a()
; - 即使省略号
..
在python中是一个"正常值",但它在pypmatch中作为通配符有特殊的含义; - 如果您想确保您有一个带有特定键/值的字典,
{…}
不够。使用 语法dict({'key':值,…})
取而代之; - 您可以使用
{int}
或{float}
检查字符串值,而不是自己编写正则表达式。 分别包含int
或float
; - PypMatch不查看涉及的名称。如果名称后面有括号,如
foo()
所示,则取该名称 引用对其值进行测试的类/类型。否则,名称是与任何 价值。这意味着模式str
将匹配所有内容并覆盖流程中的变量str
, 而str()
将测试值是否为字符串; - 最后一条规则有几个例外。因为名称绑定在其他选项中是非法的,你可以写
a b c
作为a()b()c()
的缩写。此外,x@a
被解释为x@a()
,因为它使 将两个不同的变量绑定到完全相同的值; - 由于变量的形式不能是
a.b
,因此属性a.b
本身相当于a.b()
; - <代码>3…| 6是序列
3 4 5 6
的缩写。此语法可用于整数和字符 (单字符串)。因此,您还可以编写'a'…|"z"
,例如。注意,这里你需要 写省略号,不能使用其他等价的标记
路线图
- 完全支持正则表达式和字符串匹配
- 测试套件
- 文档、教程
case
语句的两个版本
case
语句的两个版本
有两种版本的case
语句。您可以在match
块中使用case
或作为独立的
声明。
在match
块中,与模式进行比较,由match
指定。
deffoo(x):matchx:case'a'|...|'z':print("Lowercase letter")case'0'|...|'9':print("Digit")case_:print("Something else")
同样的内容也可以在没有匹配的情况下编写。在这种情况下,需要指定要测试的值
模式。这是使用
作为
语法完成的。不过,这是有区别的。独立的case
语句将
所有这些都要经过测试,因此我们明确需要使用return
以避免对所有内容打印"其他内容"
。
deffoo(x):casexas'a'|...|'z':print("Lowercase letter")returncasexas'0'|...|'9':print("Digit")returncasexas_:print("Something else")
目前,您不能将独立的case
放在match
块中,当然,如果没有
指定a匹配块之外的值。
FAQ
我可以在我的项目中使用pypmatch吗?
是的,pypmatch是在Apache2.0许可下发布的,它应该允许您在您的 自己的项目。由于项目目前正在进行大量开发,模式匹配可能会在意外情况下失败 不过,是的。
为了为模式匹配提供这种新语法,pypmatch需要在python自己的代码之前翻译代码
解析器/编译器可以触摸它。但是,翻译过程的目的是只修改原始文档的最小值
python代码。不删除任何赞扬,不插入或删除任何行,也不重命名任何变量或函数。但自从
case
和match
已成为关键字,可能与现有代码不兼容。
除了case
和match
之外,PypMatch还引入了另外两个名称:分别是\u match
和\u matchvalue
。
但是,您的程序不太可能使用这两个名称中的任何一个。
为什么还有另一个模式匹配库/方案?
以前曾讨论过添加switch
语句,甚至与python匹配的模式(参见,例如,
pep 3103)。因此,pypmatch不是一个新的想法。与大多数
到目前为止,我知道这个项目的不同之处在于,我的重点不在于确切的语法,而在于
语义正确。最后,我只需要(或者说是"强烈要求")模式匹配
对于我正在进行的其他项目。
因此,pypmatch显示了完整模式匹配是如何与python集成的,但是没有任何声明 这里使用的语法是最好的选择。
为什么不直接使用正则表达式呢?如果你想匹配一个字符串,比如说,正则表达式是很好的。不过,我们在这里提供的模式匹配,
研究基因ral python对象,而不是字符串。它更类似于isinstance
或hasattr
用python进行测试。
如何检查值是否具有特定类型?
由于python的语法,为了指定s
应该是str
类型,像s:str
这样的东西将不起作用。
在python中通常要做的是isinstance(value,str)
,它直接转换为:
importastfromastimportAdd,BinOp,Numdefsimplify(node):matchnode:caseBinOp(Num(x),Add(),Num(y)):returnNum(x+y)caseBinOp(Num(n=x),Sub(),Num(n=y)):returnNum(x-y)caseast.UnaryOp(ast.USub(),x@Num()):returnNum(-x.n)case_:returnnode0
请确保将括号放在str
之后,因为这些括号告诉pypmatch应该是
要测试的类,而不是值的新名称。
如何检查值是否具有特定属性?
如果不关心对象的类或类型,而只关心其属性,请使用通配符
类名。然后,算法将省略isinstance
检查,并只测试对象的属性是否满足
给定的条件-在本例中,就是有一个属性egg
,它可以是任何东西。
importastfromastimportAdd,BinOp,Numdefsimplify(node):matchnode:caseBinOp(Num(x),Add(),Num(y)):returnNum(x+y)caseBinOp(Num(n=x),Sub(),Num(n=y)):returnNum(x-y)caseast.UnaryOp(ast.USub(),x@Num()):returnNum(-x.n)case_:returnnode1
上面的示例将被转换为对hasattr(value,'egg')
格式的简单测试
我可以嵌套match/case结构吗?
基本上,是的,你可以。这里唯一真正的限制是不能将匹配直接放在另一个
匹配
,而将匹配
放入案例中则没有问题。也就是说,以下操作将失败:
importastfromastimportAdd,BinOp,Numdefsimplify(node):matchnode:caseBinOp(Num(x),Add(),Num(y)):returnNum(x+y)caseBinOp(Num(n=x),Sub(),Num(n=y)):returnNum(x-y)caseast.UnaryOp(ast.USub(),x@Num()):returnNum(-x.n)case_:returnnode2
其原因是match
将表达式的值x
放入一个局部变量中(并且
记账)。第二个match
把这本书弄糟了,并用y
替换了x
,这样后续的测试就失败了。
另一方面,几乎没有任何理由可以解释为什么在另一个匹配项中的
匹配项应该有意义。
不过,目前还没有完全实现嵌套。只要把match/case结构分开 功能,永远不会有问题。
这个模式匹配库有效吗?
这个库的主要目标是正确性,而不是效率。一旦一切顺利,还有时间 担心提高图书馆的性能。但是,在效率方面有一些很强的限制 模式匹配可以在python中完成。
由于匹配算法必须分析各种对象和类,因此每次执行匹配时,都有
当然,模式匹配算法在python中可以提供的性能限制。如果你有什么东西
与下面的代码片段一样,如果my_value
是foo
的实例,则该算法必须进行测试(至少)
属性eggs
和ham
,如果属性eggs
的值是123
importastfromastimportAdd,BinOp,Numdefsimplify(node):matchnode:caseBinOp(Num(x),Add(),Num(y)):returnNum(x+y)caseBinOp(Num(n=x),Sub(),Num(n=y)):returnNum(x-y)caseast.UnaryOp(ast.USub(),x@Num()):returnNum(-x.n)case_:returnnode3
在静态编译语言中,如果类foo
具有属性,则只能测试一次(在编译期间)
鸡蛋
和火腿
。然而,在python中,甚至引用的类foo
也可能更改,因此我们需要测试所有内容
每次匹配时。
另一个限制是由于pypmatch试图最小化需要更改的代码量。这意味着
每个case
语句都是与所有其他语句隔离处理的,因此不可能排除
共同的部分。同样,当然还有进一步改进的余地,但这不是pypmatch的优先事项。
如果我使用case
和match
作为变量名,它会破坏我的代码吗?
当然,总是存在这样的危险,即pypmatch的编译器会错误地将某个变量标识为match
,
或case
语句。然而,我为了被识别为语句,关键字(case
,match
)必须是
一行中的第一个单词,不能后跟冒号或运算符(如赋值)。所以,如果你有
一个名为case
的函数,该函数调用case(…)
可以解释为一个case
语句,但是赋值
例如,case=…
,比如,will not.
为什么对名称绑定使用@
而不是:=
?
python 3.8将引入赋值表达式(请参见pep 572)。它会
因此,很自然地使用x:=a
而不是x@a
进行名称绑定。
事实上,我很高兴添加对:=
的完全支持。但是,在编写时,:=
还不是
蟒蛇。仅使用:=
意味着PypMatch至少需要Python3.8,而@
已经成为有效的
python 3.5中的operatorpep 465
为什么1…| 9
而不是更简单的1…9 < /代码>?
pypmatch中模式的整个语法都基于标准的python语法。即使模式是语义上的
胡说,它们在语法上是有效的。序列1…但是,9
在python中不是有效的序列,并且
发出语法错误。
希望模式成为有效的python语法有多种原因。其中之一就是皮普马奇 更不用说独立分析了。
除了这个语用学问题外,写作〈代码〉1…| 9对我来说似乎更清楚,因为1…9
也可能意味着
值必须是序列1、2、3、…、9
本身。然而,这是个人品味的问题,因此
值得商榷。
为什么有两种版本的case
语句?
模式匹配通常不仅以块的形式出现。有时,我们只想解构
单一价值。python已经通过a、b、*c=x
等赋值部分地支持这一点。使用独立的
在case
的版本中,您可以用case x的形式(a,b,*c):
来编写它。但是,case
语句可以做很多事情
不仅仅是python的赋值运算符。
另一方面,在开发这个库时,我想知道是否有可能赋予case
含义,即使在
匹配
块,以便使整个语法尽可能正交和灵活。
由于pypmatch是一种原型,最后,case
的独立变体可能无法生存,也无法使其成为
后续版本。目前,它仍在那里,以充分测试其有用性。
为什么match
不是scala中的表达式?
scala的语法和语义是基于表达式的,而python的语法和语义则不是。复合语句,如while
,
if
,for
等实际上从来不是python中的表达式,而是没有正确值的语句。
由于这里实现的match
和case
语句显然都是复合语句,因此感觉非常
python尝试并使它们成为表达式是错误的。
为什么我必须使用case而不是else
?
else
?pypmatchem>的实现侧重于最小化对任何python代码或模块的重写。它只会
translatecase
,andmatch
语句,其中可以确定这样的语句在第一个语句中
Place,让所有代码都不受影响。
如果我们要使用else
,这意味着我们将不得不在努力确保没有else
替换了它应该保留的位置,导致代码更长更复杂。此外,个别case
语句
在match
块中,实际上并没有链接,而是作为单独的语句。使用else
引发
因此,还需要回答一些有关语义的附加问题。
因此,简而言之:使用else
将导致更脆弱的语法,其中有相当多的角案例没有涉及。
一些适当的文档如何?
当前的首要任务是使库完全运行,并添加各种测试用例。曾经 完成后,文档将继续(毕竟,已经有一个相当长的包含大量信息的自述文件, 以及几个例子)。如果您有特定的问题或问题,请打开一个问题,或直接写信给我。