Главный секрет операторов match/case в пайтоне +26




Не так давно увидела свет версия языка пайтон 3.10. В ней был добавлен pattern matching statement (оператор сопоставления с шаблонами). Как гласит официальное описание этого оператора в PEP622, разработчики в большей мере вдохновлялись наработками таких языков как: Scala, Erlang, Rust.

Многие, в том числе и я, встретили оператор с критикой. Можно для примера почитать комментарии к недавнему посту. В основном люди жалуются на синтаксис, который похож на синтаксис пайтона, однако означает совершенно другое. Вот несколько примеров:

match values:
  case name, "1"|"2" as access:
    print(f"Access for {name} granted with {access}")
  case _:
    print("Deny")

Здесь выражение "1"|"2" as access очень похоже на то, что мы уже много раз видели в пайтоне, например в with или except. Слева — обычное выражение на пайтоне, справа название переменной, которой присвоится это выражение. Но присмотритесь внимательнее, "1"|"2" это бессмыслица, так как | — это оператор бинарного ИЛИ, которое очевидно не может существовать для строк. Здесь оператор | — часть механизма pattern matching, а не языка пайтон.

Ещё хуже придется, если флаги, который вы хотите проверить, как раз будут битовыми:

match values:
  case name, 0b001 | 0b010 as access:
    print(f"Access for {name} granted with {access}")
  case _:
    print("Deny")

0b001 | 0b010 должно означать «права на то и на другое» (например, на чтение и на запись), однако в pattern matching это не является выражением пайтона и пользователь получает доступ имея права только на что-то одно.

Другое ограничение match/case — вы не можете использовать никакие внешние переменные для сопоставления. Попробуйте вспомнить другое место в пайтоне, где вы можете написать "Vadim", но не можете get_username() или использовать локальную переменную. Я не вспомнил.

class User:
  __match_args__ = ('name', 'access')
  def __init__(self, name, access):
    self.name = name
    self.access = access

def match_name(data_class, username):
  match data_class:
    case User(username) as req:
      print(f"Granted to {req.name}")
    case _:
      print("deny")

match_name(User("Anna", 1), "Vadim")
Granted to Anna

Кстати, заметили выражение UserRequest(username)? Выглядит как создание экземпляра класса, однако очевидно им не является, хотя бы потому, что у класса два обязательных аргумента, а тут передано только одно значение.

Из всего изложенного можно сделать вывод:

Операторы match/case не являются частью языка пайтон, это встроенный язык со своим уникальным синтаксисом!

Можно представить, что создатели решили бы встроить не pattern matching, а например SQL.

users = [
  User("Anna", 0x011),
  User("Vadim", 0x010)
]

granted = SELECT users.name FROM users WHERE users.access IN (1, 2);

Вряд ли в этом случае можно было бы ожидать, что внутри такого выражения стало бы работать всё богатство синтаксиса пайтона.

Таким образом, никакой синтаксис и выражения из пайтона и не должны работать внутри match/case по умолчанию. Другой вопрос, насколько оправданно было встраивать в пайтон другой язык, с таким похожим синтаксисом и совершенно другой семантикой.

PS. Более основательная критика pattern matching в том числе от контрибьютеров CPython: https://github.com/markshannon/pep622-critique (используется чуть более старый синтаксис, но отличия не принципиальны).




Комментарии (23):