Не так давно увидела свет версия языка пайтон 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 (используется чуть более старый синтаксис, но отличия не принципиальны).
Комментарии (24)
iroln
26.10.2021 16:17+5match data_class: case User(username) as req: print(f"Granted to {req.name}") case _: print("deny")
Кстати, заметили выражение User(username)? Выглядит как создание экземпляра класса, однако очевидно им не является, хотя бы потому, что у класса два обязательных аргумента, а тут передано только одно значение.
Это конечно вынос мозга. Где там что-то было про "явно лучше неявного"? Если запись
User(username)
это явное для сопоставления значения поля экземпляра, то я уже ни в чём не уверен.
boojum
26.10.2021 18:10+1так как
|
— это оператор бинарного ИЛИНе всегда.
Например
dict1 | dict2
объединит два словаря.
event1
26.10.2021 18:48+3Использование вертикальной черты для разделения вариантов вполне логично, ведь она означает "или".
По поводу скобок, вы, в принципе правы, но не вполне понятно, какой мог бы быть альтернативный вариант для биндинга? Угловые скобки?
DrMefistO
26.10.2021 19:52+1Ну, а в случае бинарной операции это совсем другое. Куда логичнее было бы использовать ранее используемое
or
, которое точно ИЛИ.
iroln
26.10.2021 19:14+3Практически о том же говорит Larry Hastings:
I dislike the syntax and semantics expressed in PEP 634. I see the match statement as a DSL contrived to look like Python, and to be used inside of Python, but with very different semantics. When you enter a PEP 634 match statement, the rules of the language change completely, and code that looks like existing Python code does something surprisingly very different. It also adds unprecedented new rules to Python, e.g. you can replace one expression with another in the exact same spot in your code, and if one has dots and the other doesn’t, the semantics of what the statement expresses changes completely. And it changes to yet a third set of semantics if you replace the expression with a single _.
https://discuss.python.org/t/gauging-sentiment-on-pattern-matching/5770/21
Я эту штуку в своём коде точно использовать не буду.
funca
27.10.2021 08:35+2В том же JavaScript принято добавлять в стандарт то, что на практике уже давно широко используется. Поэтому новые фичи проходят долгий путь от нестандартных расширений, по пути избавляясь от ошибок дизайна.
В Python все фантазии влиятельных персонажей сразу вливаются в стандарт, а потом ошибки уже не исправить, т.к. нужно поддерживать обратную совместимость. До второго пришествия Гвидо эта беда касалась, в основном, лишь непомерно разросшейся стандартной библиотеки. Но теперь эта же участь постигла и сам язык.
MarkTanashchuk
28.10.2021 10:46по пути избавляясь от ошибок дизайна.
JS - один из последних из списка современных языков в котором это заметно
piratarusso
27.10.2021 09:42Совершенно справедливое замечание. На мой взгляд введение таких конструкций просто портит язык.
Питоновский вариант без case выглядит более предсказуемым и естественным
if name in {0b001, 0b010}:print(f"Access for {name} granted with access")
elif <Что там синтаксически питоновскoе возвращающее True>:
.....else: print("Deny")
В принципе вместо if-elif можно было бы использовать case . Но это по сути ничего не меняет в смысле написания кода.
case name in {0b001, 0b010}: print(f"Access for {name} granted with access")
case <Что там синтаксически питоновскoе возвращающее True>: .....
else: print("Deny")
Мне кажется, что python вступил в эпоху архитектурных излишеств. Началось всё с расширения функционала для более строгой поддержки типов.
NeoCode
Странная тенденция - затаскивать синтаксис из функциональных языков. То что берут семантику - замечательно, полноценный паттерн матчинг давно пора ввести, и очень хорошо что он постепенно проникает во все языки. Но синтаксис... с этой дурацкой вертикальной чертой, в Rust ее затащили, теперь и в Python (хотя мне Python никогда не нравился за его форматно-несвбодный синтаксис).
Вертикальная черта это битовое ИЛИ ! В языках, сколько-то претендующих на мейнстримовый синтаксис, вместо нее в case-паттернах вполне можно было бы использовать обычную запятую.
Кажется, из всех современных языков лучше всех сделано в Swift.
bbc_69
Идею с запятой поддерживаю.
Вообще, есть ощущение, что после моржового оператора Питон идёт куда-то не туда.
NeoCode
«Моржовый оператор» лучше всего использован в Go.
Вообще проблема Python (а также Ruby, Perl, PHP, JavaScript и т.д.) в том, что можно создавать переменные без явного объявления. Вот просто написать «x = 100», и у вас новая переменная. Или не новая, а ранее объявленная… Если имя переменной длинное, то одна случайная опечатка — и программа не работает. Или работает, но не совсем так как задумывалось… В Go придумали идеальное решение: отдельный оператор для объявления переменных.
csshacker
В js подобное прокатит разве что поиграться в нестрогом режиме. В реальной разработке всё будет объявляться const или в крайнем случае let.
bbc_69
Этому оператору присвоения сто лет в обед. :)
Что касается динамической природы языка, так это достоинство и недостаток одновременно. Зато не надо придумывать название для той же сущности в другом формате или экономить на переменных. А для глупых ошибок есть линтеры.
AnthonyMikh
Если в джаве так делать нельзя, то это не значит, что так делать в статически типизированных языках нельзя в принципе:
tsukanov-as
Ну не совсем идеальное.
Можно взорваться случайно перекрыв переменную:
Это надуманный пример только для иллюстрации сути, но я, например, наступал на подобные грабли.
Идеально было бы, имхо, запретить перекрывать переменные.
Еще, кстати, раздражает объявлять переменные через var отдельно в подобном коде:
Ко всему этому конечно привыкаешь, но идеальным это сложно назвать :)
charypopper
1 пример - затенение во многих яп есть, и умение кодить на go включает знание этой особенности. Тут кстати можно линтер наверно сделать на одинаковые имена в скоупе (не точно)
2 пример - все от того что мы вводим короткие имена (читай ленимся), и вместо errFoo используем err - и удивляемся, что неудобно в некоторых местах
freecoder_xx
Например в Rust сложно представить, как затенить переменную случайно. Для затенения нужно новый байндинг делать с инструкцией
let
:А модифицируется имеющееся значение так:
NeoCode
В C#10 сделали совмещенную деконструкцию в новые и существующие переменные. Может и в Go подтянутся
и это кстати нужная фича для паттерн-матчинга частично определенными паттернами. Типа вот такого (на некотором гипотетическом C-like языке)
middle
Кому он нужен, ваш мейнстримовый синтаксис.
NeoCode
Мне нужен. Это как математическая нотация: в разных разделах математики изучаются совершенно разные вопросы, но нотация на 90% общая. Если бы в каждом случае были бы свои обозначения для арифметических операций, функций и т.д., это был бы полный мрак...