Классы в pattern matching

Последнее обновление: 01.02.2022

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

Помощь сайту
Юмани:
410011174743222
Перевод на карту
Номер карты:
4048415020898850