Python позволяет использовать в pattern matching в качестве шаблонов объекты классов. Рассмотрим на примере:
class Person:
def __init__(self, name, age):
self.name = name
self.age = age
def print_person(person):
match person:
case Person(name="Tom", age=37):
print("Default Person")
case Person(name=name, age=37):
print(f"Name: {name}")
case Person(name="Tom", age=age):
print(f"Age: {age}")
case Person(name=name, age=age):
print(f"Name: {name} Age: {age}")
print_person(Person("Tom", 37)) # Default person
print_person(Person("Tom", 22)) # Age: 22
print_person(Person("Sam", 37)) # Name: Sam
print_person(Person("Bob", 41)) # Name: Bob Age: 41
Здесь определен класс Person, который через конструктор принимает значения для атрибутов self.name и self.age.
Функция print_person принимает параметр Person, который, как предполагается, представляет объект класса Person. И внутри функции конструкция match сравнивает значение параметра person с рядом шаблонов. Каждый шаблон представляет собой определение Person, где с каждым атрибутом сопоставляется некоторое значение. Например, первый шаблон строго определяет значения обоих атрибутов:
case Person(name="Tom", age=37):
print("Default Person")
Данный шаблон соответствует объекту Person, если у этого объекта атрибут name имеет значение "Tom", а атрибут age - значение 37.
Стоит отметить, что этот шаблон - это НЕ вызов конструктора Person. Шаблон просто устанавливает, как атрибуты сопоставляются со значениями.
Второй шаблон строго задает значение только для атрибута age:
case Person(name=name, age=37):
print(f"Name: {name}")
Для соответствия этому шаблону атрибут age должен быть равен 37. А атрибут name может иметь произвольное значение. И это значение передается
переменной name. А запись name=name расшифровывается как атрибут_объекта=переменная. А в вызове
print(f"Name: {name}") на консоль выводится значение переменной name, которая получила значение атрибута name.
В данном случае и атрибут, и переменная имеют одинаковое значение, но это необязательно, и для переменной можно было использовать другое значение, например:
case Person(name=person_name, age=37): # переменной person_name передается значение атрибута name
print(f"Name: {person_name}")
Третий шаблон соответствует объекту Person, у которого атрибут name равен строке "Tom". А значение атрибута age передается в переменную age:
case Person(name="Tom", age=age):
print(f"Age: {age}")
И в последнем шаблоне атрибуты name и age могут иметь произвольные значения. И эти значения передаются одноименным переменным:
case Person(name=name, age=age):
print(f"Name: {name} Age: {age}")
При этом нам необязательно использоваться все атрибуты объекта Person. Также мы можем применить паттерн _, если нам надо обработать случаи, которые не соответствуют ни одному шаблону:
class Person:
def __init__(self, name, age):
self.name = name
self.age = age
def print_person(person):
match person:
case Person(name="Tom"):
print("Default Person")
case Person(name=person_name): # получаем только атрибут name
print(f"Name: {person_name}")
case _:
print("Not a Person")
print_person(Person("Tom", 37)) # Default person
print_person(Person("Sam", 37)) # Name: Sam
print_person("Tom") # Not a Person
В данном случае второй шаблон Person(name=person_name) соответствует любому объекту Person, при этом значение атрибута name
передается переменной person_name
А последний шаблон обрабатывает случаи, когда передано значение, которое не представляет объект Person.
Также с помощью вертикальной черты можно определить набор значений, которые должен иметь атрибут:
def print_person(person):
match person:
case Person(name="Tom" | "Tomas" | "Tommy"):
print("Default Person")
case Person(name=person_name): # получаем только атрибут name
print(f"Name: {person_name}")
case _:
print("Not a Person")
print_person(Person("Tom", 37)) # Default person
print_person(Person("Tomas", 37)) # Default person
В данном случае первый шаблон соответствует объекту Person, у которого атрибут name имеет одно из трех значений: "Tom", "Tomas" или "Tommy".
Также можно задавать альтернативные значения для всего шаблона в том числе с помощью объектов других классов:
class Person:
def __init__(self, name, age):
self.name = name
self.age = age
class Student:
def __init__(self, name):
self.name = name
def print_person(person):
match person:
case Person(name="Tom") | Student(name="Tomas"):
print("Default Person/Student")
case Person(name=name) | Student(name=name): # получаем только атрибут name
print(f"Name: {name}")
case _:
print("Not a Person or Student")
print_person(Person("Tom", 37)) # Default Person/Student
print_person(Student("Tomas")) # Default Person/Student
print_person(Person("Bob", 41)) # Name: Bob
print_person(Student("Mike")) # Name: Mike
print_person("Tom") # Not a Person or Student
Здесь первый шаблон
case Person(name="Tom") | Student(name="Tomas")
соответствет любому объекту Person, у которого атрибут name = "Tom, и любому объекту Student, у которого атрибут name = "Tomas".
Второй шаблон - case Person(name=name) | Student(name=name) соответствует любому объекту Person и Student.
В примерах выше для определения атрибутов прописывалось их имя: case Person(name="Tom", age=37). Но если используется куча шаблонов, и в каждом
необходимо связать атрибуты объекта с некоторыми значениями или переменными, то постоянное упоминание атрибутов можно несколько раздуть код.
Но Python также позволяет использовать позиционные параметры:
class Person:
__match_args__ = ("name", "age")
def __init__(self, name, age):
self.name = name
self.age = age
def print_person(person):
match person:
case Person("Tom", 37):
print("Default Person")
case Person(person_name, 37):
print(f"Name: {person_name}")
case Person("Tom", person_age):
print(f"Age: {person_age}")
case Person(person_name, person_age):
print(f"Name: {person_name} Age: {person_age}")
print_person(Person("Tom", 37)) # Default person
print_person(Person("Tom", 22)) # Age: 22
print_person(Person("Sam", 37)) # Name: Sam
print_person(Person("Bob", 41)) # Name: Bob Age: 41
Обратите внимание в классе Person на вызов функции:
__match_args__ = ("name", "age")
Благодаря этому Python будет знать, что при указании атрибутов атрибут name будет идти первым, а атрибут age - вторым.
И таким образом, в шаблонов не нужно указывать имя атрибута: case Person("Tom", 37) - Python сам сопоставит атрибуты и
значения/переменные на основе их позиции.