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 сам сопоставит атрибуты и
значения/переменные на основе их позиции.