pampy

Python 2025-08-21

pampy :Python的图案匹配

pampy很小(150行),相当快,并且通常使您的代码更可读,因此更容易推理。还有一个JavaScript版本,称为pampy .js。

你可以写很多模式

按照出现的顺序评估模式。

你可以写斐波那契

操作员的意思是“我没有想到的任何其他情况”。

pampy import match, _ def fibonacci(n): return match(n, 1, 1, 2, 1, _, lambda x: fibonacci(x-1) + fibonacci(x-2) )">
 from pampy import match , _

def fibonacci ( n ):
    return match ( n ,
        1 , 1 ,
        2 , 1 ,
        _ , lambda x : fibonacci ( x - 1 ) + fibonacci ( x - 2 )
    )

您可以用5行编写LISP计算器

pampy import match, REST, _ def lisp(exp): return match(exp, int, lambda x: x, callable, lambda x: x, (callable, REST), lambda f, rest: f(*map(lisp, rest)), tuple, lambda t: list(map(lisp, t)), ) plus = lambda a, b: a + b minus = lambda a, b: a - b from functools import reduce lisp((plus, 1, 2)) # => 3 lisp((plus, 1, (minus, 4, 2))) # => 3 lisp((reduce, plus, (range, 10))) # => 45">
 from pampy import match , REST , _

def lisp ( exp ):
    return match ( exp ,
        int ,                lambda x : x ,
        callable ,           lambda x : x ,
        ( callable , REST ),   lambda f , rest : f ( * map ( lisp , rest )),
        tuple ,              lambda t : list ( map ( lisp , t )),
    )

plus = lambda a , b : a + b
minus = lambda a , b : a - b
from functools import reduce

lisp (( plus , 1 , 2 ))                 	# => 3
lisp (( plus , 1 , ( minus , 4 , 2 )))     	# => 3
lisp (( reduce , plus , ( range , 10 )))       # => 45 

您可以匹配很多东西!

 match ( x ,
    3 ,              "this matches the number 3" ,

    int ,            "matches any integer" ,

    ( str , int ),     lambda a , b : "a tuple (a, b) you can use in a function" ,

    [ 1 , 2 , _ ],      "any list of 3 elements that begins with [1, 2]" ,

    { 'x' : _ },       "any dict with a key 'x' and any value associated" ,

    _ ,              "anything else"
)

您可以匹配[头部,尾巴]

pampy import match, HEAD, TAIL, _ x = [1, 2, 3] match(x, [1, TAIL], lambda t: t) # => [2, 3] match(x, [HEAD, TAIL], lambda h, t: (h, t)) # => (1, [2, 3]) ">
 from pampy import match , HEAD , TAIL , _

x = [ 1 , 2 , 3 ]

match ( x , [ 1 , TAIL ],     lambda t : t )            # => [2, 3]

match ( x , [ HEAD , TAIL ],  lambda h , t : ( h , t ))    # => (1, [2, 3])

尾巴和休息实际上是同一件事。

您可以筑巢列表和元组

pampy import match, _ x = [1, [2, 3], 4] match(x, [1, [_, 3], _], lambda a, b: [1, [a, 3], b]) # => [1, [2, 3], 4]">
 from pampy import match , _

x = [ 1 , [ 2 , 3 ], 4 ]

match ( x , [ 1 , [ _ , 3 ], _ ], lambda a , b : [ 1 , [ a , 3 ], b ])           # => [1, [2, 3], 4] 

您可以筑巢。您可以将_用作钥匙!

 pet = { 'type' : 'dog' , 'details' : { 'age' : 3 } }

match ( pet , { 'details' : { 'age' : _ } }, lambda age : age )        # => 3

match ( pet , { _ : { 'age' : _ } },        lambda a , b : ( a , b ))    # => ('details', 3)

感觉像是将多个内部dict不可能起作用。订购不是不能保证吗?但这是因为在Python 3.7中,DICE默认情况下保持插入密钥顺序

您可以匹配类层次结构

 class Pet :          pass
class Dog ( Pet ):     pass
class Cat ( Pet ):     pass
class Hamster ( Pet ): pass

def what_is ( x ):
    return match ( x ,
        Dog , 		'dog' ,
        Cat , 		'cat' ,
        Pet , 		'any other pet' ,
          _ , 		'this is not a pet at all' ,
    )

what_is ( Cat ())      # => 'cat'
what_is ( Dog ())      # => 'dog'
what_is ( Hamster ())  # => 'any other pet'
what_is ( Pet ())      # => 'any other pet'
what_is ( 42 )         # => 'this is not a pet at all' 

使用数据级

pampy支持Python 3.7数据级。您可以将操作员_作为参数传递,它将与这些字段匹配。

 @ dataclass
class Pet :
    name : str
    age : int

pet = Pet ( 'rover' , 7 )

match ( pet , Pet ( 'rover' , _ ), lambda age : age )                    # => 7
match ( pet , Pet ( _ , 7 ), lambda name : name )                        # => 'rover'
match ( pet , Pet ( _ , _ ), lambda name , age : ( name , age ))            # => ('rover', 7) 

使用键入

pampy支持打字注释。

 class Pet :          pass
class Dog ( Pet ):     pass
class Cat ( Pet ):     pass
class Hamster ( Pet ): pass

timestamp = NewType ( "year" , Union [ int , float ])

def annotated ( a : Tuple [ int , float ], b : str , c : E ) -> timestamp :
    pass

match (( 1 , 2 ), Tuple [ int , int ], lambda a , b : ( a , b ))             # => (1, 2)
match ( 1 , Union [ str , int ], lambda x : x )                          # => 1
match ( 'a' , Union [ str , int ], lambda x : x )                        # => 'a'
match ( 'a' , Optional [ str ], lambda x : x )                          # => 'a'
match ( None , Optional [ str ], lambda x : x )                         # => None
match ( Pet , Type [ Pet ], lambda x : x )                              # => Pet
match ( Cat , Type [ Pet ], lambda x : x )                              # => Cat
match ( Dog , Any , lambda x : x )                                    # => Dog
match ( Dog , Type [ Any ], lambda x : x )                              # => Dog
match ( 15 , timestamp , lambda x : x )                               # => 15
match ( 10.0 , timestamp , lambda x : x )                             # => 10.0
match ([ 1 , 2 , 3 ], List [ int ], lambda x : x )                        # => [1, 2, 3]
match ({ 'a' : 1 , 'b' : 2 }, Dict [ str , int ], lambda x : x )            # => {'a': 1, 'b': 2}
match ( annotated , 
    Callable [[ Tuple [ int , float ], str , Pet ], timestamp ], lambda x : x
)                                                               # => annotated

对于具有涉及通用的通用类型,实际值类型是根据第一个元素猜测的。

 match ([ 1 , 2 , 3 ], List [ int ], lambda x : x )                        # => [1, 2, 3]
match ([ 1 , "b" , "a" ], List [ int ], lambda x : x )                    # => [1, "b", "a"]
match ([ "a" , "b" , "c" ], List [ int ], lambda x : x )                  # raises MatchError
match ([ "a" , "b" , "c" ], List [ Union [ str , int ]], lambda x : x )      # ["a", "b", "c"]

match ({ "a" : 1 , "b" : 2 }, Dict [ str , int ], lambda x : x )            # {"a": 1, "b": 2}
match ({ "a" : 1 , "b" : "dog" }, Dict [ str , int ], lambda x : x )        # {"a": 1, "b": "dog"}
match ({ "a" : 1 , 1 : 2 }, Dict [ str , int ], lambda x : x )              # {"a": 1, 1: 2}
match ({ 2 : 1 , 1 : 2 }, Dict [ str , int ], lambda x : x )                # raises MatchError
match ({ 2 : 1 , 1 : 2 }, Dict [ Union [ str , int ], int ], lambda x : x )    # {2: 1, 1: 2}

具有疑问的仿制药也与其任何子类型匹配。

 match ([ 1 , 2 , 3 ], Iterable [ int ], lambda x : x )                     # => [1, 2, 3]
match ({ 1 , 2 , 3 }, Iterable [ int ], lambda x : x )                     # => {1, 2, 3}
match ( range ( 10 ), Iterable [ int ], lambda x : x )                     # => range(10)

match ([ 1 , 2 , 3 ], List [ int ], lambda x : x )                         # => [1, 2, 3]
match ({ 1 , 2 , 3 }, List [ int ], lambda x : x )                         # => raises MatchError
match ( range ( 10 ), List [ int ], lambda x : x )                         # => raises MatchError

match ([ 1 , 2 , 3 ], Set [ int ], lambda x : x )                          # => raises MatchError
match ({ 1 , 2 , 3 }, Set [ int ], lambda x : x )                          # => {1, 2, 3}
match ( range ( 10 ), Set [ int ], lambda x : x )                          # => raises MatchError

对于没有注释的任何arg,任何arg。

 def annotated ( a : int , b : int ) -> float :
    pass
    
def not_annotated ( a , b ):
    pass
    
def partially_annotated ( a , b : float ):
    pass

match ( annotated , Callable [[ int , int ], float ], lambda x : x )     # => annotated
match ( not_annotated , Callable [[ int , int ], float ], lambda x : x ) # => raises MatchError
match ( not_annotated , Callable [[ Any , Any ], Any ], lambda x : x )   # => not_annotated
match ( annotated , Callable [[ Any , Any ], Any ], lambda x : x )       # => raises MatchError
match ( partially_annotated , 
    Callable [[ Any , float ], Any ], lambda x : x
)                                                              # => partially_annotated

不支持TypeVar。

您可以匹配的所有事情

作为模式,您可以使用任何Python类型,任何类或任何Python值。

操作员_和内置类型(如int或str),提取传递给函数的变量。

类型和类是通过Instanceof(值,模式)匹配的。

具有所有元素递归匹配的图案。词典也是如此。

模式示例这意味着什么匹配的示例参数传递给功能不匹配的示例
“你好”只有字符串“ Hello”匹配“你好”没有什么任何其他值
没有任何只有一个没有任何没有什么任何其他值
int任何整数42 42任何其他值
漂浮任何浮点数2.35 2.35任何其他值
str任何字符串“你好” “你好”任何其他值
元组任何元组(1,2) (1,2)任何其他值
列表任何列表[1,2] [1,2]任何其他值
myllass myclass的任何实例。以及任何扩展myllass的对象。 myclass()那个实例任何其他对象
_任何对象(甚至没有)那个价值
任何与_相同那个价值
(int,int)由任何两个整数制成的元组(1,2) 1和2 (是的,错误)
[1,2,_]列表以1、2开头,以任何值结束[1,2,3] 3 [1,2,3,4]
[1,2,尾巴]以1、2开头的列表以任何顺序结束[1,2,3,4] [3,4] [1,7,7,7]
{'type':'狗',年龄:_}任何类型的dict:“狗”和一个年龄{“ type”:“ dog”,“ age”:3} 3 {“ type”:“ cat”,“ age”:2}
{'type':'狗',年龄:int}任何类型的dict:“狗”和int年龄{“ type”:“ dog”,“ age”:3} 3 {“ type”:“ dog”,“ age”:2.3}
re.compile('(\ w+) - (\ w+) - cat $')与正则表达式expr匹配的任何字符串“我的fuffy-cat” “我的”和“蓬松” “ Fuffy-Dog”
宠物(名称= _,年龄= 7)年龄== 7的任何宠物数据类宠物('Rover',7) ['Rover']宠物('Rover',8)
任何与_相同那个价值
联盟[int,float,无]任何整数或浮点数或无2.35 2.35任何其他值
可选[int]与联合[int,无]一样2 2任何其他值
输入[myclass] myllass的任何子类。以及任何扩展MyClass的课程。 myllass那个课任何其他对象
可呼叫[[int],float]任何可签名的可召唤def a(q:int) - > float:...该功能def a(q) - > float:...
元组[myclass,int,float]与(myclass,int,float)相同
映射[str,int]映射的任何子类型也可以接受用字符串键和整数值映射的任何映射或亚型{'a':2,'b':3}那是{'a':'b','b':'c'}
具有可接受的任何子类型也可以接受具有整数值的任何迭代或亚型范围(10)和[1,2,3]那是可以的['a','b','v']

使用默认值

默认情况下匹配()是严格的。如果没有模式匹配,它将提出MatchError。

取而代之的是,当没有匹配时,使用默认值提供后备值。

>>> match([1, 2], [1, 2, 3], "whatever")
MatchError: '_' not provided. This case is not handled: [1, 2]

>>> match([1, 2], [1, 2, 3], "whatever", default=False)
False

使用正则表达式

pampy支持Python的正则义务。您可以将编译的正则态度作为模式传递,并且pampy将运行模式。Search(),然后传递到操作函数.groups()的结果。

 def what_is ( pet ):
    return match ( pet ,
        re . compile ( '(\w+)-(\w+)-cat$' ),     lambda name , my : 'cat ' + name ,
        re . compile ( '(\w+)-(\w+)-dog$' ),     lambda name , my : 'dog ' + name ,
        _ ,                                  "something else"
    )

what_is ( 'fuffy-my-dog' )     # => 'dog fuffy'
what_is ( 'puffy-her-dog' )    # => 'dog puffy'
what_is ( 'carla-your-cat' )   # => 'cat carla'
what_is ( 'roger-my-hamster' ) # => 'something else' 

为Python3安装

pampy在Python中工作> = 3.6,因为DICT匹配只能在最新的Python中起作用。

安装它:

$ pip安装pampy

或$ pip3安装pampy

如果您确实必须使用Python2

pampy是Python3-优先,但是您可以通过Manuel Barkhau的此Backport在Python2中使用其大多数功能:

PIP安装Backports。 pampy

 from backports . pampy import match , HEAD , TAIL , _
下载源码

通过命令行克隆项目:

git clone https://github.com/santinic/pampy.git