{"id":13468814,"url":"https://github.com/santinic/pampy","last_synced_at":"2025-05-14T04:10:21.842Z","repository":{"id":39759841,"uuid":"156706494","full_name":"santinic/pampy","owner":"santinic","description":"Pampy: The Pattern Matching for Python you always dreamed of.","archived":false,"fork":false,"pushed_at":"2025-01-16T12:13:52.000Z","size":298,"stargazers_count":3528,"open_issues_count":23,"forks_count":126,"subscribers_count":61,"default_branch":"master","last_synced_at":"2025-05-13T03:07:46.628Z","etag":null,"topics":["functional","lisp-interpreter","pattern-matching","python","python3"],"latest_commit_sha":null,"homepage":"","language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/santinic.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null}},"created_at":"2018-11-08T12:51:17.000Z","updated_at":"2025-05-07T17:49:47.000Z","dependencies_parsed_at":"2025-04-10T22:38:38.435Z","dependency_job_id":null,"html_url":"https://github.com/santinic/pampy","commit_stats":null,"previous_names":[],"tags_count":3,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/santinic%2Fpampy","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/santinic%2Fpampy/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/santinic%2Fpampy/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/santinic%2Fpampy/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/santinic","download_url":"https://codeload.github.com/santinic/pampy/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":254070112,"owners_count":22009559,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["functional","lisp-interpreter","pattern-matching","python","python3"],"created_at":"2024-07-31T15:01:19.623Z","updated_at":"2025-05-14T04:10:16.821Z","avatar_url":"https://github.com/santinic.png","language":"Python","funding_links":[],"categories":["Python","Topics Index","General Utilities","📦 Additional Python Libraries","Awesome Functional Python"],"sub_categories":["QoL Libraries","Miscellaneous","Libraries"],"readme":"![Pampy in Star Wars](https://raw.githubusercontent.com/santinic/pampy/master/imgs/pampy.png \"Pampy in Star Wars\")\n\n# Pampy: Pattern Matching for Python\n[![Coverage Status](https://coveralls.io/repos/github/santinic/pampy/badge.svg?branch=master)](https://coveralls.io/github/santinic/pampy?branch=master)\n[![PyPI version](https://badge.fury.io/py/pampy.svg)](https://badge.fury.io/py/pampy)\n\nPampy is pretty small (150 lines), reasonably fast, and often makes your code more readable\nand hence easier to reason about. [There is also a JavaScript version, called Pampy.js](https://github.com/santinic/pampy.js).\n\n\u003ckbd\u003e\n  \u003cimg src=\"https://raw.githubusercontent.com/santinic/pampy/master/imgs/slide1.png\" width=\"700\"\u003e\n\u003c/kbd\u003e\n\n## You can write many patterns\n\nPatterns are evaluated in the order they appear.\n\n\u003ckbd\u003e\n  \u003cimg src=\"https://raw.githubusercontent.com/santinic/pampy/master/imgs/slide2.png\" width=\"700\"\u003e\n\u003c/kbd\u003e\n\n\n## You can write Fibonacci\nThe operator _ means \"any other case I didn't think of\".\n\n```python\nfrom pampy import match, _\n\ndef fibonacci(n):\n    return match(n,\n        1, 1,\n        2, 1,\n        _, lambda x: fibonacci(x-1) + fibonacci(x-2)\n    )\n```\n\n## You can write a Lisp calculator in 5 lines\n\n```python\nfrom pampy import match, REST, _\n\ndef lisp(exp):\n    return match(exp,\n        int,                lambda x: x,\n        callable,           lambda x: x,\n        (callable, REST),   lambda f, rest: f(*map(lisp, rest)),\n        tuple,              lambda t: list(map(lisp, t)),\n    )\n\nplus = lambda a, b: a + b\nminus = lambda a, b: a - b\nfrom functools import reduce\n\nlisp((plus, 1, 2))                 \t# =\u003e 3\nlisp((plus, 1, (minus, 4, 2)))     \t# =\u003e 3\nlisp((reduce, plus, (range, 10)))       # =\u003e 45\n```\n\n## You can match so many things!\n\n```python\nmatch(x,\n    3,              \"this matches the number 3\",\n\n    int,            \"matches any integer\",\n\n    (str, int),     lambda a, b: \"a tuple (a, b) you can use in a function\",\n\n    [1, 2, _],      \"any list of 3 elements that begins with [1, 2]\",\n\n    {'x': _},       \"any dict with a key 'x' and any value associated\",\n\n    _,              \"anything else\"\n)\n```\n\n## You can match [HEAD, TAIL]\n\n```python\nfrom pampy import match, HEAD, TAIL, _\n\nx = [1, 2, 3]\n\nmatch(x, [1, TAIL],     lambda t: t)            # =\u003e [2, 3]\n\nmatch(x, [HEAD, TAIL],  lambda h, t: (h, t))    # =\u003e (1, [2, 3])\n\n```\n`TAIL` and `REST` actually mean the same thing.\n\n## You can nest lists and tuples\n\n```python\nfrom pampy import match, _\n\nx = [1, [2, 3], 4]\n\nmatch(x, [1, [_, 3], _], lambda a, b: [1, [a, 3], b])           # =\u003e [1, [2, 3], 4]\n```\n\n## You can nest dicts. And you can use _ as key!\n\n```python\n\npet = { 'type': 'dog', 'details': { 'age': 3 } }\n\nmatch(pet, { 'details': { 'age': _ } }, lambda age: age)        # =\u003e 3\n\nmatch(pet, { _ : { 'age': _ } },        lambda a, b: (a, b))    # =\u003e ('details', 3)\n```\n\nIt feels like putting multiple _ inside dicts shouldn't work. Isn't ordering in dicts not guaranteed ?\nBut it does because\n[in Python 3.7, dict maintains insertion key order by default](https://mail.python.org/pipermail/python-dev/2017-December/151283.html)\n\n## You can match class hierarchies\n\n```python\nclass Pet:          pass\nclass Dog(Pet):     pass\nclass Cat(Pet):     pass\nclass Hamster(Pet): pass\n\ndef what_is(x):\n    return match(x,\n        Dog, \t\t'dog',\n        Cat, \t\t'cat',\n        Pet, \t\t'any other pet',\n          _, \t\t'this is not a pet at all',\n    )\n\nwhat_is(Cat())      # =\u003e 'cat'\nwhat_is(Dog())      # =\u003e 'dog'\nwhat_is(Hamster())  # =\u003e 'any other pet'\nwhat_is(Pet())      # =\u003e 'any other pet'\nwhat_is(42)         # =\u003e 'this is not a pet at all'\n```\n\n## Using Dataclasses\nPampy supports Python 3.7 dataclasses. You can pass the operator `_` as arguments and it will match those fields.\n\n```python\n@dataclass\nclass Pet:\n    name: str\n    age: int\n\npet = Pet('rover', 7)\n\nmatch(pet, Pet('rover', _), lambda age: age)                    # =\u003e 7\nmatch(pet, Pet(_, 7), lambda name: name)                        # =\u003e 'rover'\nmatch(pet, Pet(_, _), lambda name, age: (name, age))            # =\u003e ('rover', 7)\n```\n\n## Using typing\nPampy supports typing annotations.\n\n```python\n\nclass Pet:          pass\nclass Dog(Pet):     pass\nclass Cat(Pet):     pass\nclass Hamster(Pet): pass\n\ntimestamp = NewType(\"year\", Union[int, float])\n\ndef annotated(a: Tuple[int, float], b: str, c: E) -\u003e timestamp:\n    pass\n\nmatch((1, 2), Tuple[int, int], lambda a, b: (a, b))             # =\u003e (1, 2)\nmatch(1, Union[str, int], lambda x: x)                          # =\u003e 1\nmatch('a', Union[str, int], lambda x: x)                        # =\u003e 'a'\nmatch('a', Optional[str], lambda x: x)                          # =\u003e 'a'\nmatch(None, Optional[str], lambda x: x)                         # =\u003e None\nmatch(Pet, Type[Pet], lambda x: x)                              # =\u003e Pet\nmatch(Cat, Type[Pet], lambda x: x)                              # =\u003e Cat\nmatch(Dog, Any, lambda x: x)                                    # =\u003e Dog\nmatch(Dog, Type[Any], lambda x: x)                              # =\u003e Dog\nmatch(15, timestamp, lambda x: x)                               # =\u003e 15\nmatch(10.0, timestamp, lambda x: x)                             # =\u003e 10.0\nmatch([1, 2, 3], List[int], lambda x: x)                        # =\u003e [1, 2, 3]\nmatch({'a': 1, 'b': 2}, Dict[str, int], lambda x: x)            # =\u003e {'a': 1, 'b': 2}\nmatch(annotated, \n    Callable[[Tuple[int, float], str, Pet], timestamp], lambda x: x\n)                                                               # =\u003e annotated\n```\nFor iterable generics actual type of value is guessed based on the first element. \n```python\nmatch([1, 2, 3], List[int], lambda x: x)                        # =\u003e [1, 2, 3]\nmatch([1, \"b\", \"a\"], List[int], lambda x: x)                    # =\u003e [1, \"b\", \"a\"]\nmatch([\"a\", \"b\", \"c\"], List[int], lambda x: x)                  # raises MatchError\nmatch([\"a\", \"b\", \"c\"], List[Union[str, int]], lambda x: x)      # [\"a\", \"b\", \"c\"]\n\nmatch({\"a\": 1, \"b\": 2}, Dict[str, int], lambda x: x)            # {\"a\": 1, \"b\": 2}\nmatch({\"a\": 1, \"b\": \"dog\"}, Dict[str, int], lambda x: x)        # {\"a\": 1, \"b\": \"dog\"}\nmatch({\"a\": 1, 1: 2}, Dict[str, int], lambda x: x)              # {\"a\": 1, 1: 2}\nmatch({2: 1, 1: 2}, Dict[str, int], lambda x: x)                # raises MatchError\nmatch({2: 1, 1: 2}, Dict[Union[str, int], int], lambda x: x)    # {2: 1, 1: 2}\n```\nIterable generics also match with any of their subtypes.\n```python\nmatch([1, 2, 3], Iterable[int], lambda x: x)                     # =\u003e [1, 2, 3]\nmatch({1, 2, 3}, Iterable[int], lambda x: x)                     # =\u003e {1, 2, 3}\nmatch(range(10), Iterable[int], lambda x: x)                     # =\u003e range(10)\n\nmatch([1, 2, 3], List[int], lambda x: x)                         # =\u003e [1, 2, 3]\nmatch({1, 2, 3}, List[int], lambda x: x)                         # =\u003e raises MatchError\nmatch(range(10), List[int], lambda x: x)                         # =\u003e raises MatchError\n\nmatch([1, 2, 3], Set[int], lambda x: x)                          # =\u003e raises MatchError\nmatch({1, 2, 3}, Set[int], lambda x: x)                          # =\u003e {1, 2, 3}\nmatch(range(10), Set[int], lambda x: x)                          # =\u003e raises MatchError\n```\nFor Callable any arg without annotation treated as Any. \n```python\ndef annotated(a: int, b: int) -\u003e float:\n    pass\n    \ndef not_annotated(a, b):\n    pass\n    \ndef partially_annotated(a, b: float):\n    pass\n\nmatch(annotated, Callable[[int, int], float], lambda x: x)     # =\u003e annotated\nmatch(not_annotated, Callable[[int, int], float], lambda x: x) # =\u003e raises MatchError\nmatch(not_annotated, Callable[[Any, Any], Any], lambda x: x)   # =\u003e not_annotated\nmatch(annotated, Callable[[Any, Any], Any], lambda x: x)       # =\u003e raises MatchError\nmatch(partially_annotated, \n    Callable[[Any, float], Any], lambda x: x\n)                                                              # =\u003e partially_annotated\n```\nTypeVar is not supported.\n\n## All the things you can match\n\nAs Pattern you can use any Python type, any class, or any Python value.\n\nThe operator `_` and built-in types like `int` or `str`, extract variables that are passed to functions.\n\nTypes and Classes are matched via `instanceof(value, pattern)`.\n\n`Iterable` Patterns match recursively through all their elements.  The same goes for dictionaries.\n\n| Pattern Example | What it means | Matched Example |  Arguments Passed to function | NOT Matched Example |\n| --------------- | --------------| --------------- | ----------------------------- | ------------------ |\n| `\"hello\"` |  only the string `\"hello\"` matches | `\"hello\"` | nothing | any other value |\n| `None` | only `None` | `None` | nothing | any other value |\n| `int` | Any integer | `42` | `42` | any other value |\n| `float` | Any float number | `2.35` | `2.35` | any other value |\n| `str` | Any string | `\"hello\"` | `\"hello\"` | any other value |\n| `tuple` | Any tuple | `(1, 2)` | `(1, 2)` | any other value |\n| `list` | Any list | `[1, 2]` | `[1, 2]` | any other value |\n| `MyClass` | Any instance of MyClass. **And any object that extends MyClass.** | `MyClass()` | that instance | any other object |\n| `_` | Any object (even None) |  | that value | |\n| `ANY` | The same as `_` | | that value | |\n| `(int, int)` | A tuple made of any two integers | `(1, 2)` | `1` and `2` | (True, False) |\n| `[1, 2, _]`  | A list that starts with 1, 2 and ends with any value | `[1, 2, 3]` | `3` | `[1, 2, 3, 4]` |\n| `[1, 2, TAIL]` | A list that start with 1, 2 and ends with any sequence | `[1, 2, 3, 4]`| `[3, 4]` | `[1, 7, 7, 7]` |\n| `{'type':'dog', age: _ }` | Any dict with `type: \"dog\"` and with an age | `{\"type\":\"dog\", \"age\": 3}` | `3` | `{\"type\":\"cat\", \"age\":2}` |\n| `{'type':'dog', age: int }` | Any dict with `type: \"dog\"` and with an `int` age | `{\"type\":\"dog\", \"age\": 3}` | `3` | `{\"type\":\"dog\", \"age\":2.3}` |\n| `re.compile('(\\w+)-(\\w+)-cat$')` | Any string that matches that regular expression expr | `\"my-fuffy-cat\"` | `\"my\"` and `\"puffy\"` | `\"fuffy-dog\"` | \n| `Pet(name=_, age=7)` | Any Pet dataclass with `age == 7` | `Pet('rover', 7)` | `['rover']` | `Pet('rover', 8)` |\n| `Any` | The same as `_` | | that value | |\n| `Union[int, float, None]` | Any integer or float number or None | `2.35` | `2.35` | any other value |\n| `Optional[int]` | The same as `Union[int, None]` | `2` | `2` | any other value |\n| `Type[MyClass]` | Any subclass of MyClass. **And any class that extends MyClass.** | `MyClass` | that class | any other object |\n| `Callable[[int], float]` | Any callable with exactly that signature | `def a(q:int) -\u003e float: ...` | that function | `def a(q) -\u003e float: ...` |\n| `Tuple[MyClass, int, float]` | The same as `(MyClass, int, float)` | | | |\n| `Mapping[str, int]` Any subtype of `Mapping` acceptable too | any mapping or subtype of mapping with string keys and integer values | `{'a': 2, 'b': 3}` | that dict | `{'a': 'b', 'b': 'c'}` |\n| `Iterable[int]` Any subtype of `Iterable` acceptable too | any iterable or subtype of iterable with integer values | `range(10)` and `[1, 2, 3]` | that iterable | `['a', 'b', 'v']` |\n\n\n## Using default\n\nBy default `match()` is strict. If no pattern matches, it raises a `MatchError`.\n\nYou can instead provide a fallback value using `default` to be used when nothing matches.\n\n```\n\u003e\u003e\u003e match([1, 2], [1, 2, 3], \"whatever\")\nMatchError: '_' not provided. This case is not handled: [1, 2]\n\n\u003e\u003e\u003e match([1, 2], [1, 2, 3], \"whatever\", default=False)\nFalse\n```\n\n## Using Regular Expressions\nPampy supports Python's Regex. You can pass a compiled regex as pattern, and Pampy is going to run `pattern.search()`, and then pass to the action function the result of `.groups()`.\n\n```python \ndef what_is(pet):\n    return match(pet,\n        re.compile('(\\w+)-(\\w+)-cat$'),     lambda name, my: 'cat '+name,\n        re.compile('(\\w+)-(\\w+)-dog$'),     lambda name, my: 'dog '+name,\n        _,                                  \"something else\"\n    )\n\nwhat_is('fuffy-my-dog')     # =\u003e 'dog fuffy'\nwhat_is('puffy-her-dog')    # =\u003e 'dog puffy'\nwhat_is('carla-your-cat')   # =\u003e 'cat carla'\nwhat_is('roger-my-hamster') # =\u003e 'something else'\n```\n\n## Install for Python3\n\nPampy works in Python \u003e= 3.6 [Because dict matching can work only in the latest Pythons](https://mail.python.org/pipermail/python-dev/2017-December/151283.html).\n\nTo install it:\n\n```$ pip install pampy```\n\nor\n```$ pip3 install pampy```\n\n## If you really must use Python2\nPampy is Python3-first, but you can use most of its features in Python2 via [this backport](https://pypi.org/project/backports.pampy/) by Manuel Barkhau:\n\n```pip install backports.pampy```\n\n```python\nfrom backports.pampy import match, HEAD, TAIL, _\n```\n\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsantinic%2Fpampy","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fsantinic%2Fpampy","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsantinic%2Fpampy/lists"}