function pattern/predicate matching in python

  • Last Update :
  • Techknowledgy :

Please feel free to check it out. It's on PyPI and you can install it using:

pip install genericfuncs

Here's a solution adjusted from @Yam's to fit your syntax, and to be used as a library. The decision (which is a common one) is that the first predicate wins:

class guarded:
   def __init__(self,
      default):
   self.funcs = []
self.default =
   default

def when(self, pred):
   def add(func):
   self.funcs.append((pred, func))
return func
return add

def __call__(self, * args, ** kwargs):
   for pred, func in self.funcs:
   try:
   match = pred( * args, ** kwargs)
except Exception:
   match = False
if match:
   return func( * args, ** kwargs)
return self.default( * args, ** kwargs)

User code:

@guarded
def f(param):
   raise TypeError('Illegal input')

@f.when(lambda param: param > 0)
def f_when_param_positive(param):
   return 'param_positive'

@f.when(lambda param: all(isinstance(item, str) for item in param))
def f_when_param_iterable_of_strings(param):
   return 'param_iterable_of_strings'

Trying it, we get something like:

>>> print(f(123))
param_positive
   >>>
   print(f(['a', 'b']))
param_iterable_of_strings
   >>>
   print(f(-123))
Traceback(most recent call last):
   ...
   TypeError: Illegal input

I don't know of a library, but here's a basic implementation skeleton. The real problem which prevents this from being a practical solution, in my opinion, is that I have no idea how to make specialized resolution work here1. When that's the case, it will probably lead to a lot of maintenance hardships.

#!/usr/bin/python3

class when(object):
   funcs = {}

def __init__(self, pred):
   self.pred = pred

def __call__(self, func):
   if func.__qualname__ not in when.funcs:
   when.funcs[func.__qualname__] = {}

when.funcs[func.__qualname__][self.pred] = func

return lambda * args, ** kwargs: when.__match(func, * args, ** kwargs)

@staticmethod
def __match(f, * args, ** kwargs):
   for pred, func in when.funcs[f.__qualname__].items():
   if pred( * args, ** kwargs):
   return func( * args, ** kwargs)
raise NotImplementedError()

@when(lambda x: x < 0)
def my_func(x):
   return "smaller!"

@when(lambda x: x > 0)
def my_func(x):
   return "greater!"

print(my_func(-123))
print(my_func(123))

you can use isintance and combined it with a ABC to check the characteristic of the input like this:

from collections.abc
import Iterable

def foo(param):
   if isinstance(param, int) and param > 0:
   #do something
elif isinstance(param, Iterable) and all(isinstance(item, str) for item in param):
   # do something
   else
   else:
      raise TypeError('Illegal input.')

Suggestion : 2

Support ellipsis-like syntax to match anything in the rest of the list or tuple.,Support ellipsis-like syntax to match anything in the rest of the list or tuple. Consider using quote(*args) to mean zero or more elements. Elements are bound to args:,Add “when” clause like match(expr, when(predicate)),If second argument is a tuple, does that work like a character class and match any of the stated objects? How then to match a tuple? Put it in another tuple!

class Many(object):
   def __init__(self, name, count = slice(None), values = (Anything, )):
   self.count = count
self.values = values
self.name = name
def __getitem__(self, index):
   ""
"Index may be any of:
name
twople~(name, slice)
threeple~(name, slice, value - spec)
""
"
# todo

many = Many()
bind.many(1, (0, 1), 'zeroorone')
bind.many(1, (bind.many(: , int), bind.many(: , float)), 'intsorfloats')
def set_predicate(matcher, value, pattern):
   return isinstance(pattern, Set)

def set_action(matcher, value, pattern):
   value_sequence = tuple(value)
for permutation in itertools.permutations(pattern):
   try:
   matcher.names.push()
matcher.visit(value_sequence, permutation)
matcher.names.pull()
return
except Mismatch:
   matcher.names.undo()
else:
   raise Mismatch
import operator
from collections
import Sequence

def make_operators(attrs):
   "Add operators to attributes dictionary."
def method(function):
   return lambda self, that: BinaryOperator(self, function, that)
def rmethod(function):
   return lambda self, that: BinaryOperator(that, function, self)
for term in ['add', 'sub', 'mul', 'div']:
   function = getattr(operator, term)
attrs['__%s__' % term] = method(function)
attrs['__r%s__' % term] = rmethod(function)

class MetaTypeOperators(type):
   "Metaclass to add operators to type of types."
def __new__(cls, name, base, attrs):
   make_operators(attrs)
return super(MetaTypeOperators, cls).__new__(cls, name, base, attrs)

class MetaOperators(type):
   "Metaclass to add operators to types."
__metaclass__ = MetaTypeOperators
def __new__(cls, name, base, attrs):
   make_operators(attrs)
return super(MetaOperators, cls).__new__(cls, name, base, attrs)
def __repr__(self):
   return self.__name__

class Record(object):
   __metaclass__ = MetaOperators
__slots__ = ()
def __init__(self, * args):
   assert len(self.__slots__) == len(args)
for field, value in zip(self.__slots__, args):
   setattr(self, field, value)
def __getitem__(self, index):
   return getattr(self, self.__slots__[index])
def __len__(self):
   return len(self.__slots__)
def __eq__(self, that):
   if not isinstance(that, type(self)):
   return NotImplemented
return all(item == iota
   for item, iota in zip(self, that))
def __repr__(self):
   args = ', '.join(repr(item) for item in self)
return '%s(%s)' % (type(self).__name__, args)
# pickle support
def __getstate__(self):
   return tuple(self)
def __setstate__(self, state):
   self.__init__( * state)

Sequence.register(Record)

class BinaryOperator(Record):
   __slots__ = 'left', 'operator', 'right'

class Constant(Record):
   __slots__ = 'value',

   class Variable(Record):
   __slots__ = 'name',

   class Term(Record):
   __slots__ = 'value',
   def __match__(self, matcher, value):
   return matcher.visit(value, self.value)

zero = Constant(0)
one = Constant(1)
x = Variable('x')

from patternmatching
import *

assert match(zero + one, Constant + Constant)
assert match(zero * Variable, zero * anyone)

alpha = Term(bind.alpha)

assert match(zero + zero, alpha + alpha)
  def bind(object, expression):
     ""
  "Attempt to bind object to expression.
  Expression may contain `bind.name` - style attributes which will bind the `name` in the callers context.
  ""
  "
  pass # todo

  What
  if just returned a mapping with the bindings and something
  like bind.result was available to capture the latest expression.
  For nested calls, bind.results could be a stack.Then the `like`
  function
  call could just
  return a Like object which `bind`
  recognized specially.
  Alternately `bind.results`
  could work using `with`
  statement to create
  the nested scope.

Suggestion : 3

Literal patterns are a convenient way for imposing constraints on the value of a subject, rather than its type or structure. They also allow you to emulate a switch statement using pattern matching.,The notion of handling recursive data structures with pattern matching immediately gave rise to the idea of handling more general recursive ‘patterns’ (i.e. recursion beyond recursive data structures) with pattern matching. Pattern matching would thus also be used to define recursive functions such as:,A cornerstone of pattern matching is the possibility of arbitrarily nesting patterns. The nesting allows expressing deep tree structures (for an example of nested class patterns, see the motivation section above) as well as alternatives.,A sequence pattern cannot just iterate through any iterable object. The consumption of elements from the iteration would have to be undone if the overall pattern fails, which is not feasible.

if isinstance(x, tuple) and len(x) == 2:
   host, port = x
mode = "http"
elif isinstance(x, tuple) and len(x) == 3:
   host, port, mode = x
# Etc.
match x:
   case host, port:
   mode = "http"
case host, port, mode:
   pass
# Etc.
if (isinstance(node, BinOp) and node.op == "+"
   and isinstance(node.right, BinOp) and node.right.op == "*"):
   a, b, c = node.left, node.right.left, node.right.right
# Handle a + b * c
match node:
   case BinOp("+", a, BinOp("*", b, c)):
   # Handle a + b * c
match json_pet:
   case {
      "type": "cat",
      "name": name,
      "pattern": pattern
   }:
   return Cat(name, pattern)
case {
   "type": "dog",
   "name": name,
   "breed": breed
}:
return Dog(name, breed)
case _:
   raise ValueError("Not a suitable pet")
match expression:
   case pattern_1:
   ...
   case pattern_2:
   ...

Suggestion : 4

Last Updated : 19 Dec, 2021,GATE CS 2021 Syllabus

Syntax:

filter(predicate, list)

Output:

10
2
4
56
32
56
32
-- -- -- -- -- --
3
21
59

Suggestion : 5

There's a ton of pattern matching libraries available for python, all with varying degrees of maintenance and usability; also since Python 3.10 there is the PEP-634 match statement. However, this library still offers functionality that PEP-634 doesn't offer, as well as pattern matching for python versions before 3.10. A detailed comparison of PEP-634 and apm is available.,As the name indicates the "terse" style is terse. It is inspired by the pampy pattern matching library and mimics some of its behavior. Despite a slim surface area it also comes with some simplifications:,Transforms the currently looked at value by applying function on it and matches the result against pattern. In Haskell and other languages this is known as a view pattern.,Patterns can be composed using &, |, and ^, or via their more explicit counterparts AllOf, OneOf, and Either . Since patterns are objects, they can be stored in variables and be reused.

pip install awesome - pattern - matching

apm defines patterns as objects which are composable and reusable. Pieces can be matched and captured into variables, much like pattern matching in Haskell or Scala (a feature which most libraries actually lack, but which also makes pattern matching useful in the first place - the capability to easily extract data). Here is an example:

from apm
import *

if result: = match([1, 2, 3, 4, 5], [1, '2nd'
      @ _, '3rd'
      @ _, 'tail'
      @ Remaining(...)
   ]):
   print(result['2nd']) # 2
print(result['3rd']) # 3
print(result['tail']) #[4, 5]

# If you find it more readable, '>>'
can be used instead of '@'
to capture a variable
match([1, 2, 3, 4, 5], [1, _ >> '2nd', _ >> '3rd', Remaining(...) >> 'tail'])

Patterns can be composed using &, |, and ^, or via their more explicit counterparts AllOf, OneOf, and Either . Since patterns are objects, they can be stored in variables and be reused.

positive_integer = InstanceOf(int) & Check(lambda x: x >= 0)

For matching and selecting from multiple cases, choose your style:

from apm
import *

value = 7

# The simple style
if match(value, Between(1, 10)):
   print("It's between 1 and 10")
elif match(value, Between(11, 20)):
   print("It's between 11 and 20")
else:
   print("It's not between 1 and 20")

# The expression style
case (value)\
.of(Between(1, 10), lambda: print("It's between 1 and 10"))\
   .of(Between(11, 20), lambda: print("It's between 11 and 20"))\
   .otherwise(lambda: print("It's not between 1 and 20"))

# The statement style
try:
match(value)
except Case(Between(1, 10)):
   print("It's between 1 and 10")
except Case(Between(11, 20)):
   print("It's between 11 and 20")
except Default:
   print("It's not between 1 and 20")

# The declarative style
@case_distinction
def f(n: Match(Between(1, 10))):
   print("It's between 1 and 10")

@case_distinction
def f(n: Match(Between(11, 20))):
   print("It's between 11 and 20")

@case_distinction
def f(n):
   print("It's not between 1 and 20")

f(value)

# The terse(pampy) style
match(value,
   Between(1, 10), lambda: print("It's between 1 and 10"),
   Between(11, 20), lambda: print("It's between 11 and 20"),
   _, lambda: print("It's not between 1 and 20"))

Patterns are applied recursively, such that nested structures can be matched arbitrarily deep. This is super useful for extracting data from complicated structures:

from apm
import *

sample_k8s_response = {
   "containers": [{
      "args": [
         "--cert-dir=/tmp",
         "--secure-port=4443",
         "--kubelet-preferred-address-types=InternalIP,ExternalIP,Hostname",
         "--kubelet-use-node-status-port"
      ],
      "image": "k8s.gcr.io/metrics-server/metrics-server:v0.4.1",
      "imagePullPolicy": "IfNotPresent",
      "name": "metrics-server",
      "ports": [{
         "containerPort": 4443,
         "name": "https",
         "protocol": "TCP"
      }]
   }]
}

if result: = match(sample_k8s_response, {
      "containers": Each({
         "image": 'image'
         @ _,
         "name": 'name'
         @ _,
         "ports": Each({
            "containerPort": 'port'
            @ _
         }),
      })
   }):
   print(f "Image: {result['image']}, Name: {result['name']}, Port: {result['port']}")

Suggestion : 6

The function exists() returns true if a match for the given pattern exists in the graph, or if the specified property exists in the node, relationship or map. null is returned if the input argument is null.,The function single() returns true if the predicate holds for exactly one of the elements in the given list. null is returned if the list is null or all of its elements are null.,The function isEmpty() returns true if the given list or map contains no elements or if the given string contains no characters.,The function none() returns true if the predicate does not hold for any element in the given list. null is returned if the list is null or all of its elements are null.

MATCH p = (a) - [ * 1. .3] - > (b)
WHERE
a.name = 'Alice'
AND b.name = 'Daniel'
AND all(x IN nodes(p) WHERE x.age > 30)
RETURN p
MATCH(n)
WHERE any(color IN n.liked_colors WHERE color = 'yellow')
RETURN n
MATCH(n)
WHERE n.name IS NOT NULL
RETURN
n.name AS name,
   exists((n) - [: MARRIED] - > ()) AS is_married
MATCH
   (a),
   (b)
WHERE
exists(a.name)
AND NOT exists(b.name)
OPTIONAL MATCH(c: DoesNotExist)
RETURN
a.name AS a_name,
   b.name AS b_name,
   exists(b.name) AS b_has_name,
   c.name AS c_name,
   exists(c.name) AS c_has_name
ORDER BY a_name, b_name, c_name
LIMIT 1
MATCH(n)
WHERE NOT isEmpty(n.liked_colors)
RETURN n
MATCH(n)
WHERE isEmpty(properties(n))
RETURN n