Pampy ist ziemlich klein (150 Zeilen), einigermaßen schnell und macht Ihren Code oft besser lesbar und somit einfacher zum Nachdenken. Es gibt auch eine JavaScript-Version namens Pampy.js.
Muster werden in der Reihenfolge ihres Auftretens ausgewertet.
Der Operator _ bedeutet „jeder andere Fall, an den ich nicht gedacht habe“.
from 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, 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 + bminus = lambda a, b: a - bfrom functools import Reducelisp((plus, 1, 2)) # => 3lisp((plus, 1, (minus, 4, 2)) ) # => 3lisp((reduce, plus, (range, 10))) # => 45
match(x,3, „dies entspricht der Zahl 3“,int, „entspricht einer beliebigen Ganzzahl“, (str, int), Lambda a, b: „ein Tupel (a, b), das Sie in einer Funktion verwenden können“, [1, 2, _], „jede Liste von 3 Elementen, die mit [1, 2] beginnt“, {'x': _}, „jedes Diktat mit einem Schlüssel ‚x‘ und einem beliebigen damit verbundenen Wert“,_, „irgendetwas anderes“)
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])
TAIL
und REST
bedeuten eigentlich dasselbe.
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) # => 3match (pet, { _ : { 'age': _ } }, lambda a, b: (a, b)) # => ('details', 3)
Es fühlt sich so an, als ob es nicht funktionieren sollte, mehrere _ in Diktate einzufügen. Ist die Reihenfolge in Diktaten nicht garantiert? Dies ist jedoch der Fall, da dict in Python 3.7 standardmäßig die Reihenfolge der Einfügeschlüssel beibehält
class Pet: passclass Dog(Pet): passclass Cat(Pet): passclass Hamster(Pet): passdef what_is(x):return match(x,Dog, 'dog',Cat, 'cat',Pet, 'any other pet ', _, 'das ist überhaupt kein Haustier', )what_is(Cat()) # => 'cat'what_is(Dog()) # => 'dog'what_is(Hamster()) # => 'any other pet'what_is(Pet()) # => 'any other pet'what_is(42) # => 'das ist überhaupt kein Haustier'
Pampy unterstützt Python 3.7-Datenklassen. Sie können den Operator _
als Argumente übergeben und er stimmt mit diesen Feldern überein.
@dataclassclass Pet:name: strage: intpet = Pet('rover', 7)match(pet, Pet('rover', _), lambda age: age) # => 7match(pet, Pet(_, 7), Lambda-Name: Name) # => 'rover'match(pet, Pet(_, _), Lambda-Name, Alter: (Name, Alter)) # => ('Rover', 7)
Pampy unterstützt die Eingabe von Anmerkungen.
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', Optional[str], lambda x: x) # => 'a'match(None, Optional[str], lambda x: x) # => Nonematch(Pet, Type[Pet], lambda x: x) # => Petmatch(Cat, Type[Pet], lambda x: x) # => Catmatch(Dog, Any, lambda x: x) # => Dogmatch(Dog, Type[Any], lambda x: x) # => Dogmatch(15, timestamp, lambda x: x) # => 15match(10.0, timestamp, lambda x: x) # => 10.0match([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
Bei iterierbaren Generika wird der tatsächliche Werttyp basierend auf dem ersten Element geschätzt.
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 MatchErrormatch([" 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) # löst MatchErrormatch({2: 1, 1: 2}, Dict[Union[str, int], int], lambda x: x) # {2: 1, 1: 2}
Iterierbare Generika stimmen auch mit jedem ihrer Untertypen überein.
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) # => löst MatchErrormatch(range(10) aus , List[int], lambda x: x) # => erhöht MatchErrormatch([1, 2, 3], Set[int], lambda x: x) # => erhöht MatchErrormatch({1, 2, 3}, Set[int], lambda x: x) # => {1, 2, 3}match(range(10), Set[int], lambda x: x) # => löst MatchError aus
Für Callable werden alle Argumente ohne Anmerkung als „Any“ behandelt.
def annotated(a: int, b: int) -> float:passdef not_annotated(a, b):passdef teilweise_annotated(a, b: float):passmatch(annotated, Callable[[int, int], float], lambda x : x) # => annotatedmatch(not_annotated, Callable[[int, int], float], lambda x: x) # => erhöht MatchErrormatch(not_annotated, Callable[[Any, Any], Any], lambda x: x) # => not_annotatedmatch(annotated, Callable[[Any, Any], Any], lambda x: x) # => löst MatchErrormatch(partially_annotated , Callable[[Any, float], Any], lambda x: x) # => teilweise_annotiert
TypeVar wird nicht unterstützt.
Als Muster können Sie jeden Python-Typ, jede Klasse oder jeden Python-Wert verwenden.
Der Operator _
und integrierte Typen wie int
oder str
extrahieren Variablen, die an Funktionen übergeben werden.
Typen und Klassen werden über instanceof(value, pattern)
abgeglichen.
Iterable
Muster stimmen rekursiv über alle ihre Elemente hinweg überein. Das Gleiche gilt für Wörterbücher.
Musterbeispiel | Was es bedeutet | Passendes Beispiel | An die Funktion übergebene Argumente | NICHT übereinstimmendes Beispiel |
---|---|---|---|---|
"hello" | Nur die Zeichenfolge "hello" stimmt überein | "hello" | Nichts | jeder andere Wert |
None | nur None | None | Nichts | jeder andere Wert |
int | Eine beliebige Ganzzahl | 42 | 42 | jeder andere Wert |
float | Eine beliebige Gleitkommazahl | 2.35 | 2.35 | jeder andere Wert |
str | Beliebige Zeichenfolge | "hello" | "hello" | jeder andere Wert |
tuple | Beliebiges Tupel | (1, 2) | (1, 2) | jeder andere Wert |
list | Irgendeine Liste | [1, 2] | [1, 2] | jeder andere Wert |
MyClass | Jede Instanz von MyClass. Und jedes Objekt, das MyClass erweitert. | MyClass() | dieser Fall | irgendein anderes Objekt |
_ | Jedes Objekt (auch keins) | dieser Wert | ||
ANY | Das Gleiche wie _ | dieser Wert | ||
(int, int) | Ein Tupel aus zwei beliebigen ganzen Zahlen | (1, 2) | 1 und 2 | (Wahr, Falsch) |
[1, 2, _] | Eine Liste, die mit 1, 2 beginnt und mit einem beliebigen Wert endet | [1, 2, 3] | 3 | [1, 2, 3, 4] |
[1, 2, TAIL] | Eine Liste, die mit 1, 2 beginnt und mit einer beliebigen Sequenz endet | [1, 2, 3, 4] | [3, 4] | [1, 7, 7, 7] |
{'type':'dog', age: _ } | Jedes Diktat mit type: "dog" und einem Alter | {"type":"dog", "age": 3} | 3 | {"type":"cat", "age":2} |
{'type':'dog', age: int } | Jedes Diktat mit type: "dog" und einem int -Alter | {"type":"dog", "age": 3} | 3 | {"type":"dog", "age":2.3} |
re.compile('(w+)-(w+)-cat$') | Jede Zeichenfolge, die mit diesem regulären Ausdruck expr übereinstimmt | "my-fuffy-cat" | "my" und "puffy" | "fuffy-dog" |
Pet(name=_, age=7) | Jede Pet-Datenklasse mit age == 7 | Pet('rover', 7) | ['rover'] | Pet('rover', 8) |
Any | Das Gleiche wie _ | dieser Wert | ||
Union[int, float, None] | Eine beliebige Ganzzahl oder Gleitkommazahl oder Keine | 2.35 | 2.35 | jeder andere Wert |
Optional[int] | Das Gleiche wie Union[int, None] | 2 | 2 | jeder andere Wert |
Type[MyClass] | Jede Unterklasse von MyClass. Und jede Klasse, die MyClass erweitert. | MyClass | diese Klasse | irgendein anderes Objekt |
Callable[[int], float] | Jeder Callable mit genau dieser Signatur | def a(q:int) -> float: ... | diese Funktion | def a(q) -> float: ... |
Tuple[MyClass, int, float] | Das Gleiche wie (MyClass, int, float) | |||
Mapping[str, int] Jeder Untertyp von Mapping ist ebenfalls akzeptabel | jede Zuordnung oder jeder Untertyp einer Zuordnung mit Zeichenfolgenschlüsseln und ganzzahligen Werten | {'a': 2, 'b': 3} | dieses Diktat | {'a': 'b', 'b': 'c'} |
Iterable[int] Jeder Subtyp von Iterable ist ebenfalls akzeptabel | jede Iterable oder jeder Subtyp von Iterable mit ganzzahligen Werten | range(10) und [1, 2, 3] | das iterierbar | ['a', 'b', 'v'] |
Standardmäßig ist match()
streng. Wenn kein Muster übereinstimmt, wird ein MatchError
ausgelöst.
Stattdessen können Sie mithilfe default
einen Fallback-Wert angeben, der verwendet wird, wenn nichts übereinstimmt.
>>> 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 unterstützt Pythons Regex. Sie können einen kompilierten regulären Ausdruck als Muster übergeben, und Pampy führt dann pattern.search()
aus und übergibt dann das Ergebnis von .groups()
an die Aktionsfunktion.
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') # => ' etwas anderes'
Pampy funktioniert in Python >= 3.6, da der Diktatabgleich nur in den neuesten Pythons funktionieren kann.
Um es zu installieren:
$ pip install pampy
oder $ pip3 install pampy
Pampy ist Python3-first, aber Sie können die meisten seiner Funktionen in Python2 über diesen Backport von Manuel Barkhau nutzen:
pip install backports.pampy
from backports.pampy import match, HEAD, TAIL, _