Pampy довольно мал (150 строк), достаточно быстр и часто делает ваш код более читабельным и, следовательно, более понятным. Существует также версия JavaScript под названием Pampy.js.
Шаблоны оцениваются в порядке их появления.
Оператор _ означает «любой другой случай, о котором я не подумал».
из совпадения импорта pampy, _def fibonacci(n):return match(n,1, 1,2, 1,_, лямбда x: fibonacci(x-1) + fibonacci(x-2) )
из совпадения импорта pampy, REST, _def lisp(exp):return match(exp,int, лямбда x: x,callable, лямбда x: x, (вызываемый, REST), лямбда f, rest: f(*map(lisp, rest)),tuple, лямбда t: list(map(lisp, t)), )plus = лямбда a, b: a + bminus = лямбда a, b: a - bfrom functools import сокращениеlisp((plus, 1, 2)) # => 3lisp((plus, 1, (минус, 4, 2)) ) # => 3lisp((уменьшить, плюс, (диапазон, 10))) # => 45
match(x,3, "это соответствует числу 3", int, "соответствует любому целому числу", (str, int), лямбда a, b: "кортеж (a, b), который можно использовать в функции", [1, 2, _], "любой список из 3 элементов, начинающийся с [1, 2]", {'x': _}, "любой словарь с ключом 'x' и любым связанным значением",_, "что-нибудь еще")
из pampy import match, HEAD, TAIL, _x = [1, 2, 3]match(x, [1, TAIL], лямбда t: t) # => [2, 3]match(x, [HEAD, TAIL] , лямбда h, t: (h, t)) # => (1, [2, 3])
TAIL
и REST
на самом деле означают одно и то же.
из совпадения импорта pampy, _x = [1, [2, 3], 4]match(x, [1, [_, 3], _], лямбда a, b: [1, [a, 3], b] ) # => [1, [2, 3], 4]
pet = { 'type': 'dog', 'details': { 'age': 3 } }match(pet, { 'details': { 'age': _ } }, лямбда age: age) # => 3match (pet, { _ : { 'age': _ } }, лямбда a, b: (a, b)) # => ('details', 3)
Такое ощущение, что размещение нескольких _ внутри диктовок не должно работать. Разве заказ в dicts не гарантирован? Но это так, потому что в Python 3.7 dict по умолчанию поддерживает порядок вставки ключей.
class Pet: passclass Dog(Pet): passclass Cat(Pet): passclass Hamster(Pet): passdef what_is(x):return match(x,Dog, 'dog',Cat, 'cat',Pet, 'любое другое домашнее животное ', _, 'это вообще не домашнее животное', )what_is(Cat()) # => 'cat'what_is(Dog()) # => 'dog'what_is(Hamster()) # => 'любое другое домашнее животное'what_is(Pet()) # => 'любой другой питомец'what_is(42) # => 'это вообще не питомец'
Pampy поддерживает классы данных Python 3.7. Вы можете передать оператор _
в качестве аргумента, и он будет соответствовать этим полям.
@dataclassclass Pet:name: strage: intpet = Pet('rover', 7)match(pet, Pet('rover', _), лямбда age: age) # => 7match(pet, Pet(_, 7), лямбда-имя: имя) # => 'rover'match(pet, Pet(_, _), лямбда-имя, возраст: (имя, возраст)) # => ('ровер', 7)
Pampy поддерживает ввод аннотаций.
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) -> метка времени:passmatch((1, 2), Tuple[int, int], лямбда a, b: (a, b)) # => (1, 2)match(1, Union[ ул, int], лямбда x: x) # => 1match('a', Union[str, int], лямбда x: x) # => 'a'match('a', Необязательный[str], лямбда x: x ) # => 'a'match(None, Необязательный[str], лямбда x: x) # => Nonematch(Pet, Type[Pet], лямбда x: x) # => Petmatch(Cat, Type[Pet], лямбда x: x) # => Catmatch(Dog, Any, лямбда x: x) # => Dogmatch(Dog, Type[Any], лямбда x: x) # => Dogmatch(15, timestamp, лямбда x: x) # => 15match(10.0, timestamp, лямбда x: x) # => 10.0match([1, 2, 3], List[int], лямбда x: x) # => [1, 2, 3]match({'a': 1, 'b': 2}, Dict[str, int], лямбда x: x) # => {'a': 1, 'b' : 2}match(annotated, Callable[[Tuple[int, float], str, Pet], timestamp], лямбда x: x) # => аннотировано
Для итерируемых дженериков фактический тип значения угадывается на основе первого элемента.
match([1, 2, 3], List[int], лямбда x: x) # => [1, 2, 3]match([1, "b", "a"], List[int], лямбда x: x) # => [1, "b", "a"]match(["a", "b", "c"], List[int], лямбда x: x) # вызывает MatchErrormatch([" а», «б», «в»], List[Union[str, int]], лямбда x: x) # ["a", "b", "c"]match({"a": 1, "b": 2}, Dict[str, int ], лямбда x: x) # {"a": 1, "b": 2}match({"a": 1, "b": "dog"}, Dict[str, int], лямбда x: x ) # {"а": 1, "b": "dog"}match({"a": 1, 1: 2}, Dict[str, int], лямбда x: x) # {"a": 1, 1: 2}match( {2: 1, 1: 2}, Dict[str, int], лямбда x: x) # вызывает MatchErrormatch({2: 1, 1: 2}, Dict[Union[str, int], int], лямбда x:x)#{2:1, 1:2}
Итерируемые дженерики также соответствуют любому из их подтипов.
match([1, 2, 3], Iterable[int], лямбда x: x) # => [1, 2, 3]match({1, 2, 3}, Iterable[int], лямбда x: x) # => {1, 2, 3}match(range(10), Iterable[int], лямбда x: x) # => range(10)match([1, 2, 3], List[int], лямбда x: x) # => [1, 2, 3]match({1, 2, 3}, List[int], лямбда x: x) # => вызывает MatchErrormatch(range(10), List[int] , лямбда x: x) # => вызывает MatchErrormatch([1, 2, 3], Set[int], лямбда x: x) # => вызывает MatchErrormatch({1, 2, 3}, Set[int], лямбда x: x) # => {1, 2, 3}match(range(10), Set[int], лямбда x: x) # => вызывает MatchError
Для Callable любой аргумент без аннотации рассматривается как Any.
def annotated(a: int, b: int) -> float:passdef not_annotated(a, b):passdef частично_annotated(a, b: float):passmatch(annotated, Callable[[int, int], float], лямбда x : x) # => annotatedmatch(not_annotated, Callable[[int, int], float], лямбда x: x) # => вызывает MatchErrormatch(not_annotated, Callable[[Any, Any], Any], лямбда x: x) # => not_annotatedmatch(annotated, Callable[[Any, Any], Any], лямбда x: x) # => вызывает MatchErrormatch(partially_annotated, Callable[[ Any, float], Any], лямбда x: x) # => частично_аннотировано
TypeVar не поддерживается.
В качестве шаблона вы можете использовать любой тип Python, любой класс или любое значение Python.
Оператор _
и встроенные типы, такие как int
или str
, извлекают переменные, которые передаются функциям.
Типы и классы сопоставляются посредством instanceof(value, pattern)
.
Iterable
шаблоны рекурсивно сопоставляют все свои элементы. То же самое касается словарей.
Пример шаблона | Что это значит | Соответствующий пример | Аргументы, передаваемые в функцию | НЕ совпадающий пример |
---|---|---|---|---|
"hello" | соответствует только строка "hello" | "hello" | ничего | любое другое значение |
None | только None | None | ничего | любое другое значение |
int | Любое целое число | 42 | 42 | любое другое значение |
float | Любое число с плавающей запятой | 2.35 | 2.35 | любое другое значение |
str | Любая строка | "hello" | "hello" | любое другое значение |
tuple | Любой кортеж | (1, 2) | (1, 2) | любое другое значение |
list | Любой список | [1, 2] | [1, 2] | любое другое значение |
MyClass | Любой экземпляр MyClass. И любой объект, расширяющий MyClass. | MyClass() | этот экземпляр | любой другой объект |
_ | Любой объект (даже нет) | это значение | ||
ANY | То же, что _ | это значение | ||
(int, int) | Кортеж, состоящий из любых двух целых чисел | (1, 2) | 1 и 2 | (Правда, Ложь) |
[1, 2, _] | Список, который начинается с 1, 2 и заканчивается любым значением. | [1, 2, 3] | 3 | [1, 2, 3, 4] |
[1, 2, TAIL] | Список, который начинается с 1, 2 и заканчивается любой последовательностью. | [1, 2, 3, 4] | [3, 4] | [1, 7, 7, 7] |
{'type':'dog', age: _ } | Любой словарь type: "dog" и возраста. | {"type":"dog", "age": 3} | 3 | {"type":"cat", "age":2} |
{'type':'dog', age: int } | Любой словарь type: "dog" и с int возрастом. | {"type":"dog", "age": 3} | 3 | {"type":"dog", "age":2.3} |
re.compile('(w+)-(w+)-cat$') | Любая строка, соответствующая этому регулярному выражению expr. | "my-fuffy-cat" | "my" и "puffy" | "fuffy-dog" |
Pet(name=_, age=7) | Любой класс данных Pet с age == 7 | Pet('rover', 7) | ['rover'] | Pet('rover', 8) |
Any | То же, что _ | это значение | ||
Union[int, float, None] | Любое целое число или число с плавающей запятой или нет | 2.35 | 2.35 | любое другое значение |
Optional[int] | То же, что и Union[int, None] | 2 | 2 | любое другое значение |
Type[MyClass] | Любой подкласс MyClass. И любой класс, расширяющий MyClass. | MyClass | этот класс | любой другой объект |
Callable[[int], float] | Любой вызываемый объект с именно этой подписью | def a(q:int) -> float: ... | эта функция | def a(q) -> float: ... |
Tuple[MyClass, int, float] | То же, что (MyClass, int, float) | |||
Mapping[str, int] Допустим также любой подтип Mapping | любое сопоставление или подтип сопоставления со строковыми ключами и целочисленными значениями | {'a': 2, 'b': 3} | это изречение | {'a': 'b', 'b': 'c'} |
Iterable[int] Допустим также любой подтип Iterable . | любая итерация или подтип итерации с целочисленными значениями | range(10) и [1, 2, 3] | этот повторяемый | ['a', 'b', 'v'] |
По умолчанию match()
является строгим. Если ни один шаблон не соответствует, возникает MatchError
.
Вместо этого вы можете указать резервное значение, используя default
, которое будет использоваться, когда ничего не соответствует.
>>> 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 поддерживает Regex Python. Вы можете передать скомпилированное регулярное выражение как шаблон, и Pampy запустит pattern.search()
, а затем передаст функции действия результат .groups()
.
def what_is(pet):return match(pet,re.compile('(w+)-(w+)-cat$'), лямбда-имя, my: 'cat '+name,re.compile('(w+)-( w+)-dog$'), лямбда-имя, my: 'dog '+name,_, "что-то еще")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') # => ' что-то еще'
Pampy работает в Python >= 3.6, поскольку сопоставление диктов может работать только в последних версиях Python.
Чтобы установить его:
$ pip install pampy
или $ pip3 install pampy
Pampy является первым Python3, но вы можете использовать большинство его функций в Python2 с помощью этого резервного копирования Мануэля Баркхау:
pip install backports.pampy
из совпадения импорта backports.pampy, HEAD, TAIL, _