Pampy est assez petit (150 lignes), raisonnablement rapide et rend souvent votre code plus lisible et donc plus facile à raisonner. Il existe également une version JavaScript, appelée Pampy.js.
Les modèles sont évalués dans l’ordre dans lequel ils apparaissent.
L'opérateur _ signifie "tout autre cas auquel je n'ai pas pensé".
depuis la correspondance d'importation de pampy, _def fibonacci(n):return match(n,1, 1,2, 1,_, lambda x : fibonacci(x-1) + fibonacci(x-2) )
depuis pampy import match, REST, _def lisp(exp):return match(exp,int, lambda x: x,callable, lambda x: x, (appelable, REST), lambda f, rest : f(*map(lisp, rest)),tuple, lambda t : list(map(lisp, t)), )plus = lambda a, b : a + bminus = lambda a, b : a - bfrom functools import réduirelisp((plus, 1, 2)) # => 3lisp((plus, 1, (moins, 4, 2)) ) # => 3lisp((réduire, plus, (plage, 10))) # => 45
match(x,3, "cela correspond au nombre 3",int, "correspond à n'importe quel entier", (str, int), lambda a, b : "un tuple (a, b) que vous pouvez utiliser dans une fonction", [1, 2, _], "toute liste de 3 éléments commençant par [1, 2]", {'x' : _}, "n'importe quel dict avec une clé 'x' et toute valeur associée",_, "autre chose")
depuis 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])
TAIL
et REST
signifient en fait la même chose.
depuis pampy import match, _x = [1, [2, 3], 4]match(x, [1, [_, 3], _], lambda a, b: [1, [a, 3], b] ) # => [1, [2, 3], 4]
animal = { 'type' : 'chien', 'détails' : { 'âge' : 3 } }match(animal, { 'détails' : { 'âge' : _ } }, âge lambda : âge) # => 3match (animal de compagnie, { _ : { 'âge' : _ } }, lambda a, b : (a, b)) # => ('détails', 3)
On a l'impression que mettre plusieurs _ dans des dicts ne devrait pas fonctionner. La commande dans les dicts n'est-elle pas garantie ? Mais c'est le cas car dans Python 3.7, dict conserve l'ordre des clés d'insertion par défaut
class Pet: passclass Dog(Pet): passclass Cat(Pet): passclass Hamster(Pet): passdef what_is(x):return match(x,Dog, 'dog',Cat, 'cat',Pet, 'tout autre animal de compagnie ', _, 'ce n'est pas du tout un animal de compagnie', )what_is(Cat()) # => 'cat'what_is(Dog()) # => 'dog'what_is(Hamster()) # => 'tout autre animal'what_is(Pet()) # => 'any other pet'what_is(42) # => 'ce n'est pas du tout un animal de compagnie'
Pampy prend en charge les classes de données Python 3.7. Vous pouvez passer l'opérateur _
comme arguments et il correspondra à ces champs.
@dataclassclass Pet:name: strage: intpet = Pet('rover', 7)match(pet, Pet('rover', _), lambda age: age) # => 7match(pet, Pet(_, 7), nom lambda : nom) # => 'rover'match(pet, Pet(_, _), nom lambda, âge : (nom, âge)) # => ('rover', 7)
Pampy prend en charge la saisie d'annotations.
class Pet : passclass Dog(Pet) : passclass Cat(Pet) : passclass Hamster(Pet) : passtimestamp = NewType("year", Union[int, float])def annotated(a: Tuple[int, float], b: str, c: E) -> timestamp:passmatch((1, 2), Tuple[int, int], lambda a, b: (a, b)) # => (1, 2)match(1, Union[str, int], lambda x: x) # => 1match('a', Union[str, int], lambda x: x) # => 'a'match('a' , Facultatif[str], lambda x: x) # => 'a'match(Aucun, Facultatif[str], lambda x: x) # => Nonematch(Pet, Type[Pet], lambda x: x) # => Petmatch(Chat, Type[Animal], lambda x: x) # => Catmatch(Chien, Tout, lambda x: x) # => Dogmatch(Chien, Type[Tout], lambda x: x) # => Dogmatch(15, horodatage, lambda x : x) # => 15match(10.0, horodatage, lambda x : x) # => 10.0match([1, 2, 3], Liste[int], lambda x : x) # => [1, 2, 3]match({'a' : 1, 'b' : 2}, Dict[str , int], lambda x: x) # => {'a': 1, 'b': 2}match(annoté, Callable[[Tuple[int, float], str, Pet], horodatage], lambda x : x) # => annoté
Pour les génériques itérables, le type réel de valeur est deviné en fonction du premier élément.
match([1, 2, 3], Liste[int], lambda x: x) # => [1, 2, 3]match([1, "b", "a"], Liste[int], lambda x: x) # => [1, "b", "a"]match(["a", "b", "c"], List[int], lambda x: x) # déclenche MatchErrormatch([" une", "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": "chien"}, Dict[str, int] , lambda x : x) # {"a": 1, "b": "chien"}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) # déclenche MatchErrormatch({2 : 1, 1 : 2}, Dict[Union[str, int], int], lambda x : x) # {2 : 1, 1 : 2}
Les génériques itérables correspondent également à n'importe lequel de leurs sous-types.
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) # => déclenche MatchErrormatch(range(10) , List[int], lambda x: x) # => déclenche MatchErrormatch([1, 2, 3], Set[int], lambda x: x) # => déclenche MatchErrormatch({1, 2, 3}, Set[int], lambda x: x) # => {1, 2, 3}match(range(10), Set[int], lambda x: x) # => déclenche MatchError
Pour Callable, tout argument sans annotation est traité comme Any.
def annoté(a: int, b: int) -> float:passdef not_annotated(a, b):passdef partiellement_annoté(a, b: float):passmatch(annoté, Callable[[int, int], float], lambda x : x) # => annotatedmatch(not_annotated, Callable[[int, int], float], lambda x: x) # => relance MatchErrormatch(not_annotated, Callable[[Any, Any], Any], lambda x: x) # => not_annotatedmatch(annotated, Callable[[Any, Any], Any], lambda x: x) # => déclenche MatchErrormatch(partially_annotated , Callable[[Any, float], Any], lambda x: x) # => partiellement_annoté
TypeVar n'est pas pris en charge.
En tant que modèle, vous pouvez utiliser n'importe quel type Python, n'importe quelle classe ou n'importe quelle valeur Python.
L'opérateur _
et les types intégrés comme int
ou str
, extraient les variables qui sont transmises aux fonctions.
Les types et les classes correspondent via instanceof(value, pattern)
.
Les modèles Iterable
correspondent de manière récursive à tous leurs éléments. Il en va de même pour les dictionnaires.
Exemple de modèle | Ce que cela signifie | Exemple correspondant | Arguments passés à la fonction | Exemple NON correspondant |
---|---|---|---|---|
"hello" | seule la chaîne "hello" correspond | "hello" | rien | toute autre valeur |
None | seulement None | None | rien | toute autre valeur |
int | N'importe quel entier | 42 | 42 | toute autre valeur |
float | N'importe quel numéro flottant | 2.35 | 2.35 | toute autre valeur |
str | N'importe quelle chaîne | "hello" | "hello" | toute autre valeur |
tuple | N'importe quel tuple | (1, 2) | (1, 2) | toute autre valeur |
list | N'importe quelle liste | [1, 2] | [1, 2] | toute autre valeur |
MyClass | Toute instance de MyClass. Et tout objet qui étend MyClass. | MyClass() | cette instance | tout autre objet |
_ | N'importe quel objet (même Aucun) | cette valeur | ||
ANY | La même chose que _ | cette valeur | ||
(int, int) | Un tuple composé de deux entiers quelconques | (1, 2) | 1 et 2 | (Vrai, Faux) |
[1, 2, _] | Une liste qui commence par 1, 2 et se termine par n'importe quelle valeur | [1, 2, 3] | 3 | [1, 2, 3, 4] |
[1, 2, TAIL] | Une liste qui commence par 1, 2 et se termine par n'importe quelle séquence | [1, 2, 3, 4] | [3, 4] | [1, 7, 7, 7] |
{'type':'dog', age: _ } | N'importe quel dict avec type: "dog" et avec un âge | {"type":"dog", "age": 3} | 3 | {"type":"cat", "age":2} |
{'type':'dog', age: int } | N'importe quel dict de type: "dog" et avec un âge int | {"type":"dog", "age": 3} | 3 | {"type":"dog", "age":2.3} |
re.compile('(w+)-(w+)-cat$') | Toute chaîne correspondant à cette expression régulière expr | "my-fuffy-cat" | "my" et "puffy" | "fuffy-dog" |
Pet(name=_, age=7) | Toute classe de données Pet avec age == 7 | Pet('rover', 7) | ['rover'] | Pet('rover', 8) |
Any | La même chose que _ | cette valeur | ||
Union[int, float, None] | Tout nombre entier ou flottant ou Aucun | 2.35 | 2.35 | toute autre valeur |
Optional[int] | Identique à Union[int, None] | 2 | 2 | toute autre valeur |
Type[MyClass] | Toute sous-classe de MyClass. Et toute classe qui étend MyClass. | MyClass | cette classe | tout autre objet |
Callable[[int], float] | Tout appelable avec exactement cette signature | def a(q:int) -> float: ... | cette fonction | def a(q) -> float: ... |
Tuple[MyClass, int, float] | Identique à (MyClass, int, float) | |||
Mapping[str, int] Tout sous-type de Mapping est également acceptable | tout mappage ou sous-type de mappage avec des clés de chaîne et des valeurs entières | {'a': 2, 'b': 3} | ce dict | {'a': 'b', 'b': 'c'} |
Iterable[int] Tout sous-type d' Iterable est également acceptable | tout itérable ou sous-type d'itérable avec des valeurs entières | range(10) et [1, 2, 3] | que itérable | ['a', 'b', 'v'] |
Par défaut, match()
est strict. Si aucun modèle ne correspond, une MatchError
est générée.
Vous pouvez à la place fournir une valeur de secours en utilisant default
à utiliser lorsque rien ne correspond.
>>> 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 prend en charge le Regex de Python. Vous pouvez transmettre une expression régulière compilée en tant que modèle, et Pampy va exécuter pattern.search()
, puis transmettre à la fonction d'action le résultat de .groups()
.
def what_is(pet):return match(pet,re.compile('(w+)-(w+)-cat$'), nom lambda, mon : 'cat '+name,re.compile('(w+)-( w+)-dog$'), nom lambda, mon : 'chien '+nom,_, "quelque chose d'autre")what_is('fuffy-my-dog') # => 'chien fuffy'what_is('puffy-her-dog') # => 'chien puffy'what_is('carla-votre-chat') # => 'chat carla'what_is('roger-mon-hamster') # => ' autre chose'
Pampy fonctionne en Python >= 3.6 Parce que la correspondance de dict ne peut fonctionner que dans les derniers Pythons.
Pour l'installer :
$ pip install pampy
ou $ pip3 install pampy
Pampy est d'abord Python3, mais vous pouvez utiliser la plupart de ses fonctionnalités dans Python2 via ce rétroportage de Manuel Barkhau :
pip install backports.pampy
à partir de la correspondance d'importation backports.pampy, HEAD, TAIL, _