Оператор case, соответствующий многим регулярным выражениям - PullRequest
0 голосов
/ 11 июля 2020

Python наконец получил выражения присваивания в версии 3.8. Но обсуждение включало:

Во время разработки этого PEP многие люди (как сторонники, так и критики) имели тенденцию сосредотачиваться на игрушечных примерах, с одной стороны, и на слишком сложных примерах, с другой. .

Игрушечные примеры опасны двояко: они часто слишком абстрактны, чтобы заставить кого-нибудь go «ох, это убедительно», и их легко опровергнуть, сказав: «Я бы никогда не стал так писать».

Итак, вот реальный пример: я пишу парсер DSL, я хочу, чтобы он работал на Python версиях, отличных от последней, и я не могу понять Pythoni c способ сделать это.

Итак, если вы «никогда не будете писать это (нижний образец кода) таким образом», как do вы напишите оператор case в Python 3,7 без использования бесконечных уровней вложенности?

import re

doc = """
 123 45 6789
   red sky at night
     abc42
       [foo, bar+, $)&%(@]
 ContentType: image/jpeg
"""

pat1 = r'^\s*(\w+)\s*$'
pat2 = r'^\s*(\d+(?:\s+\d+)*)\s*$'
pat3 = r'^\s*(\w+):\s*(\w+\/\w+)\s*$'
pat4 = r'^\s*\[([^]]+)\]\s*$'

# Python 3.8+
for line in doc.splitlines():
    if m := re.match(pat1, line):
        print(f'Type 1: |{m.group(1)}|')
    elif m := re.match(pat2, line):
        print(f'Type 2: |{m.group(1)}|')
    elif m := re.match(pat3, line):
        print(f'Type 3: |{m.group(1)}|{m.group(2)}|')
    elif m := re.match(pat4, line):
        print(f'Type 4: |{m.group(1)}|')
    elif line:
        print(f'Unknown Format: |{line}|')

print('=============')

# Python 3.x <3.8
for line in doc.splitlines():
    m = re.match(pat1, line)
    if m:
        print(f'Type 1: |{m.group(1)}|')
    else:
        m = re.match(pat2, line)
        if m:
            print(f'Type 2: |{m.group(1)}|')
        else:
            m = re.match(pat3, line)
            if m:
                print(f'Type 3: |{m.group(1)}|{m.group(2)}|')
            else:
                m = re.match(pat4, line)
                if m:
                    print(f'Type 4: |{m.group(1)}|')
                elif line:
                    print(f'Unknown Format: |{line}|')

Вывод не имеет значения, это игрушечный пример для иллюстрации реальной проблемы. Но для записи, работающей под Python 3.7, генерируется синтаксическая ошибка.

Запуск под Python 3.8 дает:

Type 2: |123 45 6789|
Unknown Format: |   red sky at night|
Type 1: |abc42|
Type 4: |foo, bar+, $)&%(@|
Type 3: |ContentType|image/jpeg|
=============
Type 2: |123 45 6789|
Unknown Format: |   red sky at night|
Type 1: |abc42|
Type 4: |foo, bar+, $)&%(@|
Type 3: |ContentType|image/jpeg|

EDIT:

Подход Хелвуда самый простой. Легко понять с первого взгляда, больше, чем перебирать шаблоны или отправлять.

Это все еще намного уродливее, чем версия Python 3.8. Я понятия не имею, почему кто-то будет против выражений присваивания или почему Python потребовалось так много времени, чтобы их получить.

# Python 3.7
def process_line(ln):
    m = re.match(pat1, ln)
    if m:
        print(f'Type 1: |{m.group(1)}|')
        return
    m = re.match(pat2, ln)
    if m:
        print(f'Type 2: |{m.group(1)}|')
        return
    m = re.match(pat3, ln)
    if m:
        print(f'Type 3: |{m.group(1)}|{m.group(2)}|')
        return
    m = re.match(pat4, ln)
    if m:
        print(f'Type 4: |{m.group(1)}|')
        return
    print(f'Unknown Format: |{ln}|')

for line in doc.splitlines():
    if line:
        process_line(line)

EDIT (orial) # 2:

Теперь я знаю, почему Python потребовалось так много времени, чтобы реализовать такую ​​простую и полезную идею: PEP 572 Controversy . Отвращение к всему фиаско привело к тому, что создатель Python навсегда ушел в отставку, и это предостерегающий рассказ об опасностях дизайна комитета. Позор тем, кто несет ответственность за эту потерю.

1 Ответ

1 голос
/ 11 июля 2020

Вот один из способов сделать это, используя словарь шаблонов (таблицу диспетчеризации). Я ищу соответствующий шаблон, а затем вызываю связанную с ним функцию (в данном случае лямбда).

import re
doc = """123 45 6789
   red sky at night
     abc42
       [foo, bar+, $)&%(@]
 ContentType: image/jpeg
"""

pat0 = r'^\s*(\w+)\s*$'
pat1 = r'^\s*(\d+(?:\s+\d+)*)\s*$'
pat2 = r'^\s*(\w+):\s*(\w+\/\w+)\s*$'
pat3 = r'^\s*\[([^]]+)\]\s*$'

d = OrderedDict([
    (pat0, lambda m: print(f'Type 1: |{m.group(1)}|')), 
    (pat1, lambda m: print(f'Type 2: |{m.group(1)}|')), 
    (pat2, lambda m: print(f'Type 3: |{m.group(1)}|{m.group(2)}|')), 
    (pat3, lambda m: print(f'Type 4: |{m.group(1)}|'))    
])

for line in doc.splitlines():
    # look for the first pattern that matches the line 
    pattern = next((pat for pat in d.keys() if re.match(pat, line)), None)
    if pattern:
        m = re.match(pattern, line)
        d[pattern](m)
    else: 
        print(f'Unknown Format: |{line}|')
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...