Понимание вариантов использования сопоставления с образцом
Сопоставление с образцом наконец-то поддерживается в Python начиная с версии 3.10, и эта функция, общая для многих функциональных языков программирования, была безболезненно перенесена в Python.
Однако тем из нас, кто новичок в Pattern Matching, сложно определить правильные варианты использования, и поэтому код, который мы пишем, мало чем отличается от if-else
, с немного большим синтаксическим сахаром в коде if-else
, чтобы обеспечить более детальное определение переменных.
Чтобы понять реальный вариант использования сопоставления с образцом, нам нужно просмотреть параграф в PEP 635.
Большая часть возможностей сопоставления с образцом заключается в вложении подшаблонов. Таким образом, краеугольным камнем дизайна является то, что успех сопоставления с образцом напрямую зависит от успеха подшаблона.
Вложенность подшаблонов звучит немного абстрактно. Каковы практические примеры этого свойства?
Самая простая для понимания структура данных — это дерево.
Если взять в качестве примера двоичное дерево, пройдя сверху вниз, каждый узел фактически удовлетворяет следующим свойствам.
class TreeNode:
def __init__(self, value):
self.value = value
self.left = None
self.right = None
Каждый узел будет иметь собственный атрибут, а также дочерние элементы слева и справа, и это типичная вложенная структура. Поэтому один из наиболее практичных примеров — поиск узлов дерева, соответствующих шаблону.
AST Дерево
Дерево AST — это очень распространенный метод, используемый для проверки синтаксиса или проверки.
Предположим, у нас есть требование проверить, есть ли в исходном коде прямой вызов print
, тогда мы можем использовать if
, чтобы сравнить условия и выяснить, нарушает ли узел, удовлетворяющий условию, правило проверки.
for node in ast.walk(tree):
if (
isinstance(node, ast.Call) # It's a call
and isinstance(node.func, ast.Name) # It directly invokes a name
and node.func.id == 'print' # That name is `print`
):
sys.exit(0)
sys.exit(1)
Из приведенного выше кода мы знаем, что хотим сопоставить узел ast.Call
, и это функция вызова, а имя функции — print, всего три условия.
Теперь мы знаем условия шаблона, которые хотим сравнить, давайте перепишем его следующим образом, используя сопоставление с образцом.
for node in ast.walk(tree):
match node:
# If it's a call to `print`
case ast.Call(func=ast.Name(id='print')):
sys.exit(0)
sys.exit(1)
С помощью сопоставления с образцом мы можем сделать условия более простыми, сравнив их с определением class
, которое есть у нас в голове.
Из кода мы видим, что сопоставление с образцом упрощает сравнение условий, но в этом не вся сила Pattern Matching, поскольку мы еще не сравнивали вложенные структуры. Давайте посмотрим на следующий пример.
Парсинг дерева
Предположим, теперь я хочу найти в двоичном дереве несколько шаблонов различных цветов и форм.
Имея такое дерево, я хочу знать:
- Есть ли на дереве зеленый квадрат с красным кружком, прикрепленным к левому узлу?
- Имеет ли дерево форму любого цвета с желтым треугольником, прикрепленным к левому узлу, и синим прямоугольником, прикрепленным к правому узлу?
- Дерево имеет желтый круг с синим прямоугольником, прикрепленным к левому узлу. Для синего прямоугольника левый узел соединен с красным треугольником, а правый узел соединен с красным прямоугольником. Красный прямоугольник, правый узел соединен с зеленым квадратом.
Тогда наша модель мышления будет выглядеть так, как показано на следующей диаграмме.
Собственно, это все, что должна делать наша программа.
def Find(Tree):
match Tree:
case Square('green' ,
Circle('red') as left ,
_
) as root:
print(f'case1 \n {root = } \n {left = } \n ')
case Geometric( _ ,
Triangle('yellow') as left,
Rectangle('blue') as right,
):
print(f'case2 \n {left = } \n {right = } \n')
case Circle('yellow',
_ ,
Rectangle('blue',
Triangle('red'),
Rectangle('red',
_ ,
Square('green')
),
) as right
) as root:
print(f'case3 \n {root = } \n {right = } \n ')
case _:
print('Not Found \n')
Из этого примера мы можем увидеть силу сопоставления с образцом не только для сравнения узлов с явными условиями, но и для сравнения нечетких условий, как в случае 2. Не только легче писать код, но и легче отображать идеи, которые у нас есть в наша голова прямо к коду.
Фактически, оптимизаторы баз данных и механизмов больших данных активно используют сопоставление с образцом (не в Python), поскольку шаблоны, которым оптимизаторы должны сопоставлять, настолько сложны, что было бы очень сложно поддерживать код, который просто использует if-else
.
Заключение
Сопоставление с образцом — это мощный метод, это не просто if-else
или switch-case
, это pattern matching.
Однако в нашей повседневной жизни мы редко по-настоящему используем возможности сопоставления с образцом как потому, что мы редко имеем дело с большим количеством вложенных структур, так и потому, что мы неправильно понимаем сценарии, применяемые к ним.
Вот классический пример из Stack Overflow. Я запечатлел ответ, набравший наибольшее количество лайков:
match a:
case _ if a < 42:
print('Less')
case _ if a == 42:
print('The answer')
case _ if a > 42:
print('Greater')
Действительно ли это лучше, чем if-else
?
if a < 42:
print('Less')
elif a == 42:
print('The answer')
elif a > 42:
print('Greater')
Я считаю, что большинство людей так не думают, и это типичный сценарий неправильного применения.
Подводя итог, можно сказать, что сценарий, в котором используется сопоставление с образцом, — это не условное сравнение, а сравнение с образцом. Кроме того, реальная сила сопоставления с образцом заключается во вложенной структуре.