Structural Pattern Matching Haim Michael September 20th , 2022 All logos, trade marks and brand names used in this presentation belong to the respective owners. https://youtu.be/xgRYrs_jy5E life michael
© 2021 Haim Michael All Rights Reserved 2 Introduction  As of Python 3.10 we can use patterns matching. There are three PEPs (Python Enhancement Proposals) describing this feature: PEP 634: The Specification https://www.python.org/dev/peps/pep-0634 PEP 635: Motivation and Rationale https://www.python.org/dev/peps/pep-0635 PEP 636: Patterns Matching Tutorial https://www.python.org/dev/peps/pep-0636
© 2021 Haim Michael All Rights Reserved 3 Switch Statement on Steroids  We can consider patterns matching as a switch statement on steroids.
© 2021 Haim Michael All Rights Reserved 4 Matching Specific Values  The simplest form of patterns matching in Python would be a simple use case similar to switch statement. command = "+" match command: case '+': print('plus...') case '-': print('minus') case '*': print('multiply') case '/': print('divide')
© 2021 Haim Michael All Rights Reserved 5 The _ Wildcard  We can use the _ character as a wild card. We shall place the wild card as the last case possibility. If none of the other cases matches, the wild card will. command = "%" match command: case '+': print('plus...') case '-': print('minus') case '/': print('divide') case _: print('soething else')
© 2021 Haim Michael All Rights Reserved 6 Matching Sequences  We can match a sequence with possible sequences composed of literals or variables. operation = ("deposit",1200,123222) operation = ("transfer",800,123222,101888) match operation: case operation,sum,account_id: print("%s %f %d" % (operation,sum,account_id)) case operation, sum, from_id, to_id: print("%s %f from %d to %d" % (operation, sum, from_id, to_id))
© 2021 Haim Michael All Rights Reserved 7 Matching Sequences operation = ["playing","pacman"] match operation: case ["eat",food]: print("eating %s is great" % (food,)) case ["watching",show]: print("enjoy watching %s" % (show,)) case ["playing",game]: print("playing %s is awesome" % (game,)) case _: print("your operation cannot be recognized")
© 2021 Haim Michael All Rights Reserved 8 Matching Multiple Values  We can use the packing capability similarly to the way we use it in assignments.
© 2021 Haim Michael All Rights Reserved 9 Matching Multiple Values operation = ("delete","Hello.py","Calcu.py","Demo.py") match operation: case "delete",*files: print("the following files will be deleted:") for file in files: print(file) case "makedir",*directories: print("the following directories will be created:") for directory in directories: print(directory) case _: print("the requesed operation is not recognized")
© 2021 Haim Michael All Rights Reserved 10 Composing Patters  We can compose new patterns composed from others. Patterns that don't include other patterns can be any of the following: capture patterns, literal patterns, and the wildcard pattern _.
© 2021 Haim Michael All Rights Reserved 11 Composing Patters operation = ("update_mark",("mosh",234234),103444,88) match operation: case "register_course",student,course_id: print("registering to course") print(student) print(course_id) case "update_mark",(student_name,student_id),course_id,mark: print("updating mark in specific course") print("student name: %s", student_name) print("student id: %d" % (student_id,)) print("course id: %d" % (course_id,)) print("mark: %.2f" % (mark,))
© 2021 Haim Michael All Rights Reserved 12 Or Patterns  We can use the | operator (AKA the or operator) in order to create a case that includes more than one pattern. Matching any of the patterns will be considered as matching the entire case.
© 2021 Haim Michael All Rights Reserved 13 Or Patterns command = ("del","temp.txt") match command: case ("delete",file) | ("remove",file) | ("del",file): print("deleting file %s" % (file,)) case ("copy",file,newfile): print("copying file %s to %s" % (file,newfile))
© 2021 Haim Michael All Rights Reserved 14 The as Pattern  We can use the or operator for creating a sub pattern composed of multiple possibilities. We can use the as pattern for finding which is the exact possibility that match.
© 2021 Haim Michael All Rights Reserved 15 The as Pattern data = ("del","temp.txt") match data: case (("delete" | "remove" | "del") as command, file): print("deleting file %s" % (file,)) print("command=%s" % (command,)) case ("copy",file,newfile): print("copying file %s to %s" % (file,newfile))
© 2021 Haim Michael All Rights Reserved 16 Adding Conditions  We can add a condition to specific cases. The condition includes the use of the if keyword followed by expression that its value is of the type bool.
© 2021 Haim Michael All Rights Reserved 17 Adding Conditions data = ("del","temp.txt") files = ["readme.txt", "temp.txt", "index.txt"] match data: case (("delete" | "remove" | "del") as command, file) if file in files: print("deleting file %s" % (file,)) print("command=%s" % (command,)) case (("delete" | "remove" | "del") as command, file) if file not in files: print("the file %s cannot be deleted" % file) case ("copy",file,newfile): print("copying file %s to %s" % (file,newfile))
© 2021 Haim Michael All Rights Reserved 18 Matching Objects Types  When having an object we need to examine its type and its attributes we can avoid the use of isinstance() and we can avoid checking its attributes.
© 2021 Haim Michael All Rights Reserved 19 Matching Objects Types class Dog: def hau(self): return "hau hau" class Cat: def __init__(self,predator): self.predator = predator def miaou(self): return "miaou miaou" class Cow: def moo(self): return "moo moo" ob = Cat(True)
© 2021 Haim Michael All Rights Reserved 20 Matching Objects Types match ob: case Dog(): print(ob.hau()) case Cat(predator=False): print(ob.miaou()) case Cat(predator=True): print("%s grrrrr" % ob.miaou()) case Cow(): print(ob.moo())
© 2021 Haim Michael All Rights Reserved 21 Matching Attributes By Position  When using objects that were instantiated from a class marked as dataclass we can describe the matched attributes by position.
© 2021 Haim Michael All Rights Reserved 22 Matching Attributes By Position from dataclasses import dataclass from math import pow, sqrt @dataclass class Point: x: int y: int @dataclass class Line: p1: Point p2: Point ob = Line(Point(3, 4), Point(6, 8)) match ob: case Line(p1, p2): print(sqrt(pow(p2.x - p1.x, 2) + pow(p2.y - p1.y, 2))) case Point(x, y): print(sqrt(pow(x, 2) + pow(y, 2)))
© 2021 Haim Michael All Rights Reserved 23 The __match_args__ Attribute  When using objects that were not instantiated from a class marked as dataclass and were not instantiated from the tuple type we can still describe the matched attributes by position if we add the __match_args__ attribute to the class definition.  The __match_args__ special attribute defines an explicit order for the attributes.
© 2021 Haim Michael All Rights Reserved 24 The __match_args__ Attribute from math import pow, sqrt class Point: __match_args__ = ("x", "y") def __init__(self, x, y): self.x = x self.y = y class Line: __match_args__ = ("p1", "p2") def __init__(self, p1, p2): self.p1 = p1 self.p2 = p2 ob = Line(Point(3, 4), Point(6, 8))
© 2021 Haim Michael All Rights Reserved 25 The __match_args__ Attribute match ob: case Line(p1, p2): print(sqrt(pow(p2.x - p1.x, 2) + pow(p2.y - p1.y, 2))) case Point(x, y): print(sqrt(pow(x, 2) + pow(y, 2)))
© 2021 Haim Michael All Rights Reserved 26 Enums Matching  We can match agains possible values that were defined as part of an Enum object. from enum import Enum class Color(Enum): BLUE = 1 GREEN = 2 RED = 3 BLACK = 4 WHITE = 5 class Car: __match_args__ = ("brand", "id", "color") def __init__(self, brand, id, color): self.brand = brand self.id = id self.color = color
© 2021 Haim Michael All Rights Reserved 27 Enums Matching ob = Car("Mazda 6", 2342343, Color.GREEN) match ob: case Car(car_brand, car_id, Color.WHITE): print("white cars are simpler to handle") case Car(car_brand, car_id, Color.BLACK): print("black cars aborb the heat") case Car(car_brand, car_id, _): print("colorful cars are more fun")
© 2021 Haim Michael All Rights Reserved 28 Mappings Matching  We can match an expression against a dict object. The matching will be based on the keys.
© 2021 Haim Michael All Rights Reserved 29 Mappings Matching  We can match an expression against a dict object. The matching will be based on the keys. command = { 'action': 'getbalance', 'account': { 'accountid': 204714, 'owners_ids': (24537564, 51634545) }}
© 2021 Haim Michael All Rights Reserved 30 Mappings Matching match command: case {'action': 'getbalance', 'account': account}: print("getting balance") print("account id: %s" % (account['accountid'],)) print("account owners are:") print(account['owners_ids']) case {'action': 'deposit', 'sum': sum, 'account': account}: print("deposit %f" % sum) print("account id: %s" % (account,)) print("account owners are:") print(account['owners_ids'])
© 2021 Haim Michael All Rights Reserved 31 Mappings Matching  We can use the dict packing capability when matching a dict object with additional key value pairs that don't match any of the pattern's parts. command = { 'action': 'getbalance', 'account': { 'accountid': 204714, 'owners_ids': (24537564, 51634545) }, 'date': {'day':12,'month':1,'year':1980}, 'time': {'hour': 14, 'minutes': 20} }
© 2021 Haim Michael All Rights Reserved 32 Mappings Matching match command: case {'action': 'getbalance', 'account': account, **ob}: print("getting balance") print("account id: %s" % (account['accountid'],)) print("account owners are:") print(account['owners_ids']) print(ob) case {'action': 'deposit', 'sum': sum, 'account': account, **ob}: print("deposit %f" % sum) print("account id: %s" % (account,)) print("account owners are:") print(account['owners_ids']) print(ob)
Matching Builtin Types  We can use builtin types for validating the types of specific parts in the expression we check. data = { 'action': 'getbalance', 'sum':800, 'account': { 'accountid': 204714, 'owners_ids': (24537564, 51634545) } }
Matching Builtin Types match data: case {'action': str() as action, 'account': account}: print(action) print("account id: %s" % (account['accountid'],)) print("account owners are:") print(account['owners_ids']) case _: print(data)
© 2021 Haim Michael All Rights Reserved 35 Questions & Answers Thanks for Your Time! Haim Michael haim.michael@lifemichael.com +972+3+3726013 ext:700 life michael

Structural Pattern Matching in Python

  • 1.
    Structural Pattern Matching HaimMichael September 20th , 2022 All logos, trade marks and brand names used in this presentation belong to the respective owners. https://youtu.be/xgRYrs_jy5E life michael
  • 2.
    © 2021 HaimMichael All Rights Reserved 2 Introduction  As of Python 3.10 we can use patterns matching. There are three PEPs (Python Enhancement Proposals) describing this feature: PEP 634: The Specification https://www.python.org/dev/peps/pep-0634 PEP 635: Motivation and Rationale https://www.python.org/dev/peps/pep-0635 PEP 636: Patterns Matching Tutorial https://www.python.org/dev/peps/pep-0636
  • 3.
    © 2021 HaimMichael All Rights Reserved 3 Switch Statement on Steroids  We can consider patterns matching as a switch statement on steroids.
  • 4.
    © 2021 HaimMichael All Rights Reserved 4 Matching Specific Values  The simplest form of patterns matching in Python would be a simple use case similar to switch statement. command = "+" match command: case '+': print('plus...') case '-': print('minus') case '*': print('multiply') case '/': print('divide')
  • 5.
    © 2021 HaimMichael All Rights Reserved 5 The _ Wildcard  We can use the _ character as a wild card. We shall place the wild card as the last case possibility. If none of the other cases matches, the wild card will. command = "%" match command: case '+': print('plus...') case '-': print('minus') case '/': print('divide') case _: print('soething else')
  • 6.
    © 2021 HaimMichael All Rights Reserved 6 Matching Sequences  We can match a sequence with possible sequences composed of literals or variables. operation = ("deposit",1200,123222) operation = ("transfer",800,123222,101888) match operation: case operation,sum,account_id: print("%s %f %d" % (operation,sum,account_id)) case operation, sum, from_id, to_id: print("%s %f from %d to %d" % (operation, sum, from_id, to_id))
  • 7.
    © 2021 HaimMichael All Rights Reserved 7 Matching Sequences operation = ["playing","pacman"] match operation: case ["eat",food]: print("eating %s is great" % (food,)) case ["watching",show]: print("enjoy watching %s" % (show,)) case ["playing",game]: print("playing %s is awesome" % (game,)) case _: print("your operation cannot be recognized")
  • 8.
    © 2021 HaimMichael All Rights Reserved 8 Matching Multiple Values  We can use the packing capability similarly to the way we use it in assignments.
  • 9.
    © 2021 HaimMichael All Rights Reserved 9 Matching Multiple Values operation = ("delete","Hello.py","Calcu.py","Demo.py") match operation: case "delete",*files: print("the following files will be deleted:") for file in files: print(file) case "makedir",*directories: print("the following directories will be created:") for directory in directories: print(directory) case _: print("the requesed operation is not recognized")
  • 10.
    © 2021 HaimMichael All Rights Reserved 10 Composing Patters  We can compose new patterns composed from others. Patterns that don't include other patterns can be any of the following: capture patterns, literal patterns, and the wildcard pattern _.
  • 11.
    © 2021 HaimMichael All Rights Reserved 11 Composing Patters operation = ("update_mark",("mosh",234234),103444,88) match operation: case "register_course",student,course_id: print("registering to course") print(student) print(course_id) case "update_mark",(student_name,student_id),course_id,mark: print("updating mark in specific course") print("student name: %s", student_name) print("student id: %d" % (student_id,)) print("course id: %d" % (course_id,)) print("mark: %.2f" % (mark,))
  • 12.
    © 2021 HaimMichael All Rights Reserved 12 Or Patterns  We can use the | operator (AKA the or operator) in order to create a case that includes more than one pattern. Matching any of the patterns will be considered as matching the entire case.
  • 13.
    © 2021 HaimMichael All Rights Reserved 13 Or Patterns command = ("del","temp.txt") match command: case ("delete",file) | ("remove",file) | ("del",file): print("deleting file %s" % (file,)) case ("copy",file,newfile): print("copying file %s to %s" % (file,newfile))
  • 14.
    © 2021 HaimMichael All Rights Reserved 14 The as Pattern  We can use the or operator for creating a sub pattern composed of multiple possibilities. We can use the as pattern for finding which is the exact possibility that match.
  • 15.
    © 2021 HaimMichael All Rights Reserved 15 The as Pattern data = ("del","temp.txt") match data: case (("delete" | "remove" | "del") as command, file): print("deleting file %s" % (file,)) print("command=%s" % (command,)) case ("copy",file,newfile): print("copying file %s to %s" % (file,newfile))
  • 16.
    © 2021 HaimMichael All Rights Reserved 16 Adding Conditions  We can add a condition to specific cases. The condition includes the use of the if keyword followed by expression that its value is of the type bool.
  • 17.
    © 2021 HaimMichael All Rights Reserved 17 Adding Conditions data = ("del","temp.txt") files = ["readme.txt", "temp.txt", "index.txt"] match data: case (("delete" | "remove" | "del") as command, file) if file in files: print("deleting file %s" % (file,)) print("command=%s" % (command,)) case (("delete" | "remove" | "del") as command, file) if file not in files: print("the file %s cannot be deleted" % file) case ("copy",file,newfile): print("copying file %s to %s" % (file,newfile))
  • 18.
    © 2021 HaimMichael All Rights Reserved 18 Matching Objects Types  When having an object we need to examine its type and its attributes we can avoid the use of isinstance() and we can avoid checking its attributes.
  • 19.
    © 2021 HaimMichael All Rights Reserved 19 Matching Objects Types class Dog: def hau(self): return "hau hau" class Cat: def __init__(self,predator): self.predator = predator def miaou(self): return "miaou miaou" class Cow: def moo(self): return "moo moo" ob = Cat(True)
  • 20.
    © 2021 HaimMichael All Rights Reserved 20 Matching Objects Types match ob: case Dog(): print(ob.hau()) case Cat(predator=False): print(ob.miaou()) case Cat(predator=True): print("%s grrrrr" % ob.miaou()) case Cow(): print(ob.moo())
  • 21.
    © 2021 HaimMichael All Rights Reserved 21 Matching Attributes By Position  When using objects that were instantiated from a class marked as dataclass we can describe the matched attributes by position.
  • 22.
    © 2021 HaimMichael All Rights Reserved 22 Matching Attributes By Position from dataclasses import dataclass from math import pow, sqrt @dataclass class Point: x: int y: int @dataclass class Line: p1: Point p2: Point ob = Line(Point(3, 4), Point(6, 8)) match ob: case Line(p1, p2): print(sqrt(pow(p2.x - p1.x, 2) + pow(p2.y - p1.y, 2))) case Point(x, y): print(sqrt(pow(x, 2) + pow(y, 2)))
  • 23.
    © 2021 HaimMichael All Rights Reserved 23 The __match_args__ Attribute  When using objects that were not instantiated from a class marked as dataclass and were not instantiated from the tuple type we can still describe the matched attributes by position if we add the __match_args__ attribute to the class definition.  The __match_args__ special attribute defines an explicit order for the attributes.
  • 24.
    © 2021 HaimMichael All Rights Reserved 24 The __match_args__ Attribute from math import pow, sqrt class Point: __match_args__ = ("x", "y") def __init__(self, x, y): self.x = x self.y = y class Line: __match_args__ = ("p1", "p2") def __init__(self, p1, p2): self.p1 = p1 self.p2 = p2 ob = Line(Point(3, 4), Point(6, 8))
  • 25.
    © 2021 HaimMichael All Rights Reserved 25 The __match_args__ Attribute match ob: case Line(p1, p2): print(sqrt(pow(p2.x - p1.x, 2) + pow(p2.y - p1.y, 2))) case Point(x, y): print(sqrt(pow(x, 2) + pow(y, 2)))
  • 26.
    © 2021 HaimMichael All Rights Reserved 26 Enums Matching  We can match agains possible values that were defined as part of an Enum object. from enum import Enum class Color(Enum): BLUE = 1 GREEN = 2 RED = 3 BLACK = 4 WHITE = 5 class Car: __match_args__ = ("brand", "id", "color") def __init__(self, brand, id, color): self.brand = brand self.id = id self.color = color
  • 27.
    © 2021 HaimMichael All Rights Reserved 27 Enums Matching ob = Car("Mazda 6", 2342343, Color.GREEN) match ob: case Car(car_brand, car_id, Color.WHITE): print("white cars are simpler to handle") case Car(car_brand, car_id, Color.BLACK): print("black cars aborb the heat") case Car(car_brand, car_id, _): print("colorful cars are more fun")
  • 28.
    © 2021 HaimMichael All Rights Reserved 28 Mappings Matching  We can match an expression against a dict object. The matching will be based on the keys.
  • 29.
    © 2021 HaimMichael All Rights Reserved 29 Mappings Matching  We can match an expression against a dict object. The matching will be based on the keys. command = { 'action': 'getbalance', 'account': { 'accountid': 204714, 'owners_ids': (24537564, 51634545) }}
  • 30.
    © 2021 HaimMichael All Rights Reserved 30 Mappings Matching match command: case {'action': 'getbalance', 'account': account}: print("getting balance") print("account id: %s" % (account['accountid'],)) print("account owners are:") print(account['owners_ids']) case {'action': 'deposit', 'sum': sum, 'account': account}: print("deposit %f" % sum) print("account id: %s" % (account,)) print("account owners are:") print(account['owners_ids'])
  • 31.
    © 2021 HaimMichael All Rights Reserved 31 Mappings Matching  We can use the dict packing capability when matching a dict object with additional key value pairs that don't match any of the pattern's parts. command = { 'action': 'getbalance', 'account': { 'accountid': 204714, 'owners_ids': (24537564, 51634545) }, 'date': {'day':12,'month':1,'year':1980}, 'time': {'hour': 14, 'minutes': 20} }
  • 32.
    © 2021 HaimMichael All Rights Reserved 32 Mappings Matching match command: case {'action': 'getbalance', 'account': account, **ob}: print("getting balance") print("account id: %s" % (account['accountid'],)) print("account owners are:") print(account['owners_ids']) print(ob) case {'action': 'deposit', 'sum': sum, 'account': account, **ob}: print("deposit %f" % sum) print("account id: %s" % (account,)) print("account owners are:") print(account['owners_ids']) print(ob)
  • 33.
    Matching Builtin Types We can use builtin types for validating the types of specific parts in the expression we check. data = { 'action': 'getbalance', 'sum':800, 'account': { 'accountid': 204714, 'owners_ids': (24537564, 51634545) } }
  • 34.
    Matching Builtin Types matchdata: case {'action': str() as action, 'account': account}: print(action) print("account id: %s" % (account['accountid'],)) print("account owners are:") print(account['owners_ids']) case _: print(data)
  • 35.
    © 2021 HaimMichael All Rights Reserved 35 Questions & Answers Thanks for Your Time! Haim Michael haim.michael@lifemichael.com +972+3+3726013 ext:700 life michael