[Python, Программирование] Pattern matching. Теперь и в Python
Автор
Сообщение
news_bot ®
Стаж: 6 лет 9 месяцев
Сообщений: 27286
Привет!
В юбилейный минор третьего питона наконец-то завезли pattern matching. Саму концепцию сложно назвать новой, она уже реализована во многих языках, причём как нового поколения (Rust, Golang), так и у тех, кому уже за 0x18 (Java).
Извините, данный ресурс не поддреживается. :(
Анонсировал pattern matching Гвидо ван Россум, автор языка программирования Python и «великодушный пожизненный диктатор»
Меня зовут Денис Кайшев, я код-ревьюер на курсе «Мидл Python-разработчик». В этом посте хочу рассказать, зачем в Python pattern matching и как с ним работать.
Синтаксически конструкция pattern matching по сути аналогична тому, как это представлено в ряде других языков:
match_expr:
| star_named_expression ',' star_named_expressions?
| named_expression
match_stmt: "match" match_expr ':' NEWLINE INDENT case_block+ DEDENT
case_block: "case" patterns [guard] ':' block
guard: 'if' named_expression
patterns: value_pattern ',' [values_pattern] | pattern
pattern: walrus_pattern | or_pattern
walrus_pattern: NAME ':=' or_pattern
or_pattern: '|'.closed_pattern+
closed_pattern:
| capture_pattern
| literal_pattern
| constant_pattern
| group_pattern
| sequence_pattern
| mapping_pattern
| class_pattern
capture_pattern: NAME !('.' | '(' | '=')
literal_pattern:
| signed_number !('+' | '-')
| signed_number '+' NUMBER
| signed_number '-' NUMBER
| strings
| 'None'
| 'True'
| 'False'
constant_pattern: attr !('.' | '(' | '=')
group_pattern: '(' patterns ')'
sequence_pattern: '[' [values_pattern] ']' | '(' ')'
mapping_pattern: '{' items_pattern? '}'
class_pattern:
| name_or_attr '(' ')'
| name_or_attr '(' ','.pattern+ ','? ')'
| name_or_attr '(' ','.keyword_pattern+ ','? ')'
| name_or_attr '(' ','.pattern+ ',' ','.keyword_pattern+ ','? ')'
signed_number: NUMBER | '-' NUMBER
attr: name_or_attr '.' NAME
name_or_attr: attr | NAME
values_pattern: ','.value_pattern+ ','?
items_pattern: ','.key_value_pattern+ ','?
keyword_pattern: NAME '=' or_pattern
value_pattern: '*' capture_pattern | pattern
key_value_pattern:
| (literal_pattern | constant_pattern) ':' or_pattern
| '**' capture_pattern
Может показаться сложным и запутанным, но на самом деле всё сводится примерно к такому виду:
match some_expression:
case pattern_1:
...
case pattern_2:
...
Это выглядит куда понятнее и приятнее глазу.
Сами шаблоны разбиты на несколько групп:
- Literal Patterns;
- Capture Patterns;
- Wildcard Pattern;
- Constant Value Patterns;
- Sequence Patterns;
- Mapping Patterns;
- Class Patterns.
Расскажу немного о каждой из них.
Literal Patterns
Паттерн Literal, как намекает нам название, предполагает сопоставление между собой ряда значений, а именно строк, чисел, булевых значений и NULLNone.
Это выглядит как string == 'string', используется метод __eq__.
match number:
case 42:
print('answer')
case 43:
print('not answer')
Capture Patterns
Шаблон захвата позволяет связать переменную с заданным в шаблоне именем и использовать это имя внутри локальной области видимости.
match greeting:
case "":
print('Hello my friend')
case name:
print(f'Hello {name}')
Wildcard Pattern
Если вариантов сопоставления слишком много, то можно использовать _ , что является неким значением по умолчанию и будет совпадать со всеми элементами в конструкции match
match number:
case 42:
print("Its’s forty two")
case _:
print("I don’t know, what it is")
Constant Value Patterns
При использовании констант нужно использовать dotted names, к примеру перечисления, иначе сработает паттерн захвата.
OK = 200
CONFLICT = 409
response = {'status': 409, 'msg': 'database error'}
match response['status'], response['msg']:
case OK, ok_msg:
print('handler 200')
case CONFLICT, err_msg:
print('handler 409')
case _:
print('idk this status')
И ожидаемый результат будет не самым очевидным.
Sequence Patterns
Позволяет сопоставлять списки, кортежи и любые другие объекты от collections.abc.Sequence, кроме str, bytes, bytearray.
answer = [42]
match answer:
case []:
print('i do not find answer')
case [x]:
print('asnwer is 42')
case [x, *_]:
print('i find more than one answers')
Теперь нет необходимости каждый раз вызывать len() для проверки количества элементов в списке, так как будет вызван метод __len__.
Mapping Patterns
Эта группа немного похожа на предыдущую, только здесь мы сопоставляем словари, или, если быть точным, объекты типа collections.abc.Mapping. Их можно достаточно неплохо сочетать друг с другом.
args = (1, 2)
kwargs = {'kwarg': 'kwarg', 'one_more_kwarg': 'one_more_kwarg'}
def match_something(*args, **kwargs):
match (args, kwargs):
case (arg1, arg2), {'kwarg': kwarg}:
print('i find positional args and one keyword args')
case (arg1, arg2), {'kwarg': kwarg, 'one_more_kwarg': one_more_kwarg}:
print('i find a few keyword args')
case _:
print('i cannot match anything')
match_something(*args, **kwargs)
И всё бы ничего, но есть особенность. Этот паттерн гарантирует вхождение этого ключа (ключей) в словарь, но длина словаря не имеет значения. Поэтому на экране появится i find positional args and one keyword args.
Class patterns
Что касается пользовательских типов данных, то здесь используется синтаксис, схожий с инициализацией объекта.
Вот как это будет выглядеть на примере дата-классов:
from dataclasses import dataclass
@dataclass
class Coordinate:
x: int
y: int
z: int
coordinate = Coordinate(1, 2, 3)
match coordinate:
case Coordinate(0, 0, 0):
print('Zero point')
case _:
print('Another point')
Также можно использовать if, или так называемый guard. Если условие ложно, то сопоставление с шаблоном продолжится. Стоит отметить, что сначала происходит сопоставление с шаблоном, и только после этого проверяется условие:
case Coordinate(x, y, z) if z == 0:
print('Point in the plane XY')
Если использовать непосредственно классы, то нужен атрибут __match_args__, в котором необходимы позиционные аргументы (для namedtuple и dataclasses __match_args__ генерируется автоматически).
class Coordinate:
__match_args__ = ['x', 'y', 'z']
def __init__(self, x, y, z):
self.x = x
self.y = y
self.z = z
сoordinate = Сoordinate(1, 2, 3)
match Сoordinate:
case Сoordinate(0, 0, 0):
print('Zero Сoordinate')
case Сoordinate(x, y, z) if z == 0:
print('Сoordinate in the plane Z')
case _:
print('Another Сoordinate')
Иначе вызовется исключение TypeError: Coordinate() accepts 0 positional sub-patterns (3 given)
Что в итоге?
Фактически это выглядит как очередной синтаксический сахар наряду с недавним walrus operator. Реализация в текущем её виде преобразует блоки оператора match в эквивалентные конструкции if/else, а именно в байт-код, который имеет такой же эффект.
Извините, данный ресурс не поддреживается. :(
Армин Ронахер, создатель веб-фреймворка Flask для Python, очень ёмко описалтекущее состояние Pattern matching
Да, сложно спорить: код станет несколько чище, нежели это была бы башня из if/else на треть экрана. Но и назвать это тем, что производит вау-эффект, тоже нельзя. Неплохо, что оно вводится: местами это будет удобно использовать, но не везде. Так или иначе, главное с этой новизной не переусердствовать, не бежать быстрее обновлять все проекты на 3.10 и всё переписывать, ведь:
Now is better than never. Although never is often better than right now.
Будете использовать? Если да, то где?
===========
Источник:
habr.com
===========
Похожие новости:
- [Python, Программирование, Машинное обучение] Поиск нарушений на видео с помощью компьютерного зрения
- [Ненормальное программирование, Поисковые технологии, Python, Игры и игровые приставки] Однажды Microsoft забанила всю мою страну за читерство (перевод)
- [Программирование, *nix, Учебный процесс в IT, Карьера в IT-индустрии] Полезные материалы для разработчика
- [JavaScript, Совершенный код, Интерфейсы, Функциональное программирование] Шпаргалка по функциональному программированию
- [Программирование, ERP-системы, Управление персоналом, Читальный зал, 1С] Помолчи-ка, программист
- [JavaScript, Программирование, ReactJS] React-компоненты шаблонов проектирования (перевод)
- [Программирование, Системное программирование, Промышленное программирование, Программирование микроконтроллеров] Добавляем modbus в Embox RTOS и используем на STM32 и не только
- [Open source, JavaScript, Программирование, TypeScript] 5 фактов о том, как Microsoft приватизировала открытый исходный код, убивая JavaScript в процессе (перевод)
- [Разработка веб-сайтов, .NET, C#, Функциональное программирование] От внедрения зависимостей к отказу от зависимостей
- [Программирование, GTK+, Разработка под Linux] Создатель динамических обоев на языке Vala
Теги для поиска: #_python, #_programmirovanie (Программирование), #_jandeks.praktikum (Яндекс.Практикум), #_python, #_if/else, #_pattern_matching, #_programmirovanie (программирование), #_blog_kompanii_jandeks.praktikum (
Блог компании Яндекс.Практикум
), #_python, #_programmirovanie (
Программирование
)
Вы не можете начинать темы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы
Текущее время: 22-Ноя 19:11
Часовой пояс: UTC + 5
Автор | Сообщение |
---|---|
news_bot ®
Стаж: 6 лет 9 месяцев |
|
Привет! В юбилейный минор третьего питона наконец-то завезли pattern matching. Саму концепцию сложно назвать новой, она уже реализована во многих языках, причём как нового поколения (Rust, Golang), так и у тех, кому уже за 0x18 (Java). Извините, данный ресурс не поддреживается. :( Анонсировал pattern matching Гвидо ван Россум, автор языка программирования Python и «великодушный пожизненный диктатор» Меня зовут Денис Кайшев, я код-ревьюер на курсе «Мидл Python-разработчик». В этом посте хочу рассказать, зачем в Python pattern matching и как с ним работать. Синтаксически конструкция pattern matching по сути аналогична тому, как это представлено в ряде других языков: match_expr:
| star_named_expression ',' star_named_expressions? | named_expression match_stmt: "match" match_expr ':' NEWLINE INDENT case_block+ DEDENT case_block: "case" patterns [guard] ':' block guard: 'if' named_expression patterns: value_pattern ',' [values_pattern] | pattern pattern: walrus_pattern | or_pattern walrus_pattern: NAME ':=' or_pattern or_pattern: '|'.closed_pattern+ closed_pattern: | capture_pattern | literal_pattern | constant_pattern | group_pattern | sequence_pattern | mapping_pattern | class_pattern capture_pattern: NAME !('.' | '(' | '=') literal_pattern: | signed_number !('+' | '-') | signed_number '+' NUMBER | signed_number '-' NUMBER | strings | 'None' | 'True' | 'False' constant_pattern: attr !('.' | '(' | '=') group_pattern: '(' patterns ')' sequence_pattern: '[' [values_pattern] ']' | '(' ')' mapping_pattern: '{' items_pattern? '}' class_pattern: | name_or_attr '(' ')' | name_or_attr '(' ','.pattern+ ','? ')' | name_or_attr '(' ','.keyword_pattern+ ','? ')' | name_or_attr '(' ','.pattern+ ',' ','.keyword_pattern+ ','? ')' signed_number: NUMBER | '-' NUMBER attr: name_or_attr '.' NAME name_or_attr: attr | NAME values_pattern: ','.value_pattern+ ','? items_pattern: ','.key_value_pattern+ ','? keyword_pattern: NAME '=' or_pattern value_pattern: '*' capture_pattern | pattern key_value_pattern: | (literal_pattern | constant_pattern) ':' or_pattern | '**' capture_pattern Может показаться сложным и запутанным, но на самом деле всё сводится примерно к такому виду: match some_expression:
case pattern_1: ... case pattern_2: ... Это выглядит куда понятнее и приятнее глазу. Сами шаблоны разбиты на несколько групп:
Расскажу немного о каждой из них. Literal Patterns Паттерн Literal, как намекает нам название, предполагает сопоставление между собой ряда значений, а именно строк, чисел, булевых значений и NULLNone. Это выглядит как string == 'string', используется метод __eq__. match number:
case 42: print('answer') case 43: print('not answer') Capture Patterns Шаблон захвата позволяет связать переменную с заданным в шаблоне именем и использовать это имя внутри локальной области видимости. match greeting:
case "": print('Hello my friend') case name: print(f'Hello {name}') Wildcard Pattern Если вариантов сопоставления слишком много, то можно использовать _ , что является неким значением по умолчанию и будет совпадать со всеми элементами в конструкции match match number:
case 42: print("Its’s forty two") case _: print("I don’t know, what it is") Constant Value Patterns При использовании констант нужно использовать dotted names, к примеру перечисления, иначе сработает паттерн захвата. OK = 200
CONFLICT = 409 response = {'status': 409, 'msg': 'database error'} match response['status'], response['msg']: case OK, ok_msg: print('handler 200') case CONFLICT, err_msg: print('handler 409') case _: print('idk this status') И ожидаемый результат будет не самым очевидным. Sequence Patterns Позволяет сопоставлять списки, кортежи и любые другие объекты от collections.abc.Sequence, кроме str, bytes, bytearray. answer = [42]
match answer: case []: print('i do not find answer') case [x]: print('asnwer is 42') case [x, *_]: print('i find more than one answers') Теперь нет необходимости каждый раз вызывать len() для проверки количества элементов в списке, так как будет вызван метод __len__. Mapping Patterns Эта группа немного похожа на предыдущую, только здесь мы сопоставляем словари, или, если быть точным, объекты типа collections.abc.Mapping. Их можно достаточно неплохо сочетать друг с другом. args = (1, 2)
kwargs = {'kwarg': 'kwarg', 'one_more_kwarg': 'one_more_kwarg'} def match_something(*args, **kwargs): match (args, kwargs): case (arg1, arg2), {'kwarg': kwarg}: print('i find positional args and one keyword args') case (arg1, arg2), {'kwarg': kwarg, 'one_more_kwarg': one_more_kwarg}: print('i find a few keyword args') case _: print('i cannot match anything') match_something(*args, **kwargs) И всё бы ничего, но есть особенность. Этот паттерн гарантирует вхождение этого ключа (ключей) в словарь, но длина словаря не имеет значения. Поэтому на экране появится i find positional args and one keyword args. Class patterns Что касается пользовательских типов данных, то здесь используется синтаксис, схожий с инициализацией объекта. Вот как это будет выглядеть на примере дата-классов: from dataclasses import dataclass
@dataclass class Coordinate: x: int y: int z: int coordinate = Coordinate(1, 2, 3) match coordinate: case Coordinate(0, 0, 0): print('Zero point') case _: print('Another point') Также можно использовать if, или так называемый guard. Если условие ложно, то сопоставление с шаблоном продолжится. Стоит отметить, что сначала происходит сопоставление с шаблоном, и только после этого проверяется условие: case Coordinate(x, y, z) if z == 0:
print('Point in the plane XY') Если использовать непосредственно классы, то нужен атрибут __match_args__, в котором необходимы позиционные аргументы (для namedtuple и dataclasses __match_args__ генерируется автоматически). class Coordinate:
__match_args__ = ['x', 'y', 'z'] def __init__(self, x, y, z): self.x = x self.y = y self.z = z сoordinate = Сoordinate(1, 2, 3) match Сoordinate: case Сoordinate(0, 0, 0): print('Zero Сoordinate') case Сoordinate(x, y, z) if z == 0: print('Сoordinate in the plane Z') case _: print('Another Сoordinate') Иначе вызовется исключение TypeError: Coordinate() accepts 0 positional sub-patterns (3 given) Что в итоге? Фактически это выглядит как очередной синтаксический сахар наряду с недавним walrus operator. Реализация в текущем её виде преобразует блоки оператора match в эквивалентные конструкции if/else, а именно в байт-код, который имеет такой же эффект. Извините, данный ресурс не поддреживается. :( Армин Ронахер, создатель веб-фреймворка Flask для Python, очень ёмко описалтекущее состояние Pattern matching Да, сложно спорить: код станет несколько чище, нежели это была бы башня из if/else на треть экрана. Но и назвать это тем, что производит вау-эффект, тоже нельзя. Неплохо, что оно вводится: местами это будет удобно использовать, но не везде. Так или иначе, главное с этой новизной не переусердствовать, не бежать быстрее обновлять все проекты на 3.10 и всё переписывать, ведь: Now is better than never. Although never is often better than right now.
Будете использовать? Если да, то где? =========== Источник: habr.com =========== Похожие новости:
Блог компании Яндекс.Практикум ), #_python, #_programmirovanie ( Программирование ) |
|
Вы не можете начинать темы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы
Текущее время: 22-Ноя 19:11
Часовой пояс: UTC + 5