Почему `DelimitedList` и `Dict` pyparsing так неудобно использовать вместе?

Pyparsing предлагает подкласс ParseElementEnhanceDelimitedList для анализа списков (обычно разделенных запятыми):

>>> kv_element = pp.Word(pp.alphanums)
>>> kv_list = pp.DelimitedList(kv_element)
>>> kv_list.parse_string('red, green, blue')
ParseResults(['red', 'green', 'blue'], {})

И он предоставляет подкласс TokenConverterDict для преобразования повторяющегося выражения в словарь:

>>> key = value = pp.Word(pp.alphanums)
>>> kv_pair = key + pp.Suppress(" = ") + value
>>> kv_dict = pp.Dict(pp.Group(kv_pair)[...])
>>> kv_dict.parse_string('R=red G=green B=blue')
ParseResults([
  ParseResults(['R', 'red'], {}),
  ParseResults(['G', 'green'], {}),
  ParseResults(['B', 'blue'], {})
], {'R': 'red', 'G': 'green', 'B': 'blue'})

Но объединять их кажется неудобным. Можно построить успешную комбинацию ParserElement для анализа dict из списка с разделителями, но по сравнению с приведенным выше для этого требуется:

  1. Переопределение DelimitedList для вывода Group()s
  2. Повторение DelimitedList при построении Dict() вокруг него, чтобы успокоить проверку типов.1
>>> kv_pair = key + pp.Suppress(" = ") + value
>>> kv_pairlist = pp.DelimitedList(pp.Group(kv_pair))
>>> kv_pairdict = pp.Dict(kv_pairlist[...])
>>> kv_pairdict.parse_string('R=red, G=green, B=blue')
ParseResults([
  ParseResults(['R', 'red'], {}), 
  ParseResults(['G', 'green'], {}), 
  ParseResults(['B', 'blue'], {})
], {'R': 'red', 'G': 'green', 'B': 'blue'})

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

Написание кода для выражения намерения — определения синтаксического анализатора, соответствующего одному ограниченному списку, содержащему серию совпадений пар «ключ-значение», — похоже на борьбу с API. (Особенно раздражает тот факт, что использование kv_pairdict = pp.Dict(kv_pairlist) будет работать так же, как указано выше, но противоречит проверке типов.)

Есть ли более чистый способ выразить предполагаемое определение синтаксического анализатора в API Pyparsing? Если нет, то является ли это недостатком моего дизайна, API Pyparsing или чего-то еще?

(У меня есть определение наизнанку? DelimitedList(Dict(Group(kv_pair)[1, ...])) тоже работает, но кажется мне еще более концептуально обратным. Но оно не требует почти такой же борьбы с API, так что, возможно, я просто смотрю на него неправильно.)

Примечания

  1. (В противном случае, по крайней мере в VSCode, он получит эту смутно сумасшедшую аннотацию:)

    Ни один вариант перегрузки «dict» не соответствует типу аргумента «DelimitedList» (перегрузка mypycall)

    Возможные варианты перегрузки:

        def [_KT, _VT] __init__(self) -> dict[_KT, _VT]
        def [_KT, _VT] __init__(self, **kwargs: _VT) -> dict[str, _VT]
        def [_KT, _VT] __init__(self, SupportsKeysAndGetItem[_KT, _VT], /) -> dict[_KT, _VT]
        def [_KT, _VT] __init__(self, SupportsKeysAndGetItem[str, _VT], /, **kwargs: _VT) -> dict[str, _VT]
        def [_KT, _VT] __init__(self, Iterable[tuple[_KT, _VT]], /) -> dict[_KT, _VT]
        def [_KT, _VT] __init__(self, Iterable[tuple[str, _VT]], /, **kwargs: _VT) -> dict[str, _VT]
        def [_KT, _VT] __init__(self, Iterable[list[str]], /) -> dict[str, str]
        def [_KT, _VT] __init__(self, Iterable[list[bytes]], /) -> dict[bytes, bytes]mypy(note)
    
Почему в Python есть оператор "pass"?
Почему в Python есть оператор "pass"?
Оператор pass в Python - это простая концепция, которую могут быстро освоить даже новички без опыта программирования.
Некоторые методы, о которых вы не знали, что они существуют в Python
Некоторые методы, о которых вы не знали, что они существуют в Python
Python - самый известный и самый простой в изучении язык в наши дни. Имея широкий спектр применения в области машинного обучения, Data Science,...
Основы Python Часть I
Основы Python Часть I
Вы когда-нибудь задумывались, почему в программах на Python вы видите приведенный ниже код?
LeetCode - 1579. Удаление максимального числа ребер для сохранения полной проходимости графа
LeetCode - 1579. Удаление максимального числа ребер для сохранения полной проходимости графа
Алиса и Боб имеют неориентированный граф из n узлов и трех типов ребер:
Оптимизация кода с помощью тернарного оператора Python
Оптимизация кода с помощью тернарного оператора Python
И последнее, что мы хотели бы показать вам, прежде чем двигаться дальше, это
Советы по эффективной веб-разработке с помощью Python
Советы по эффективной веб-разработке с помощью Python
Как веб-разработчик, Python может стать мощным инструментом для создания эффективных и масштабируемых веб-приложений.
1
0
56
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Dict должен быть создан с использованием одного ParserElement, который представляет повторение сгруппированных элементов ParserElements, принимая текст, соответствующий 0-му элементу каждой группы, в качестве ключа этой группы, а оставшуюся часть группы в качестве соответствующего значения. Обычно повторение выполняется с использованием OneOrMore или ZeroOrMore (или их новых обозначений, похожих на срезы [1, ...] или [...]). Но для этого повторения вполне подходит использование DelimitedList, если выражение, используемое для повторяющихся пар ключ-значение, является группой. Посмотрите, поможет ли эта небольшая доработка вашего кода (на самом деле я просто переместил группу вверх до определения kv_pair):

key = pp.common.identifier  # keep the keys usable for use as attribute names
value = pp.Word(pp.alphanums)
kv_pair = pp.Group(key + pp.Suppress(" = ") + value)
kv_pairlist = pp.DelimitedList(kv_pair)
kv_pairdict = pp.Dict(kv_pairlist)


kv_pairdict.run_tests("""\
    R=red, G=green, B=blue
"""
)

Я сохранил это как dict_of_delimited_list.py, и добавив эти строки, я получил dict_of_delimited_list_diagram.html, содержащий схему вашего парсера.

pp.autoname_elements()
kv_pairdict.create_diagram(f"{__file__.removesuffix('.py')}_diagram.html")

Что касается вашего примечания, я сильно подозреваю, что проблема в проверке типов. Подпись __init__ для Dict явно принимает один ParserElement, а не тип ключа и тип значения, поэтому я подозреваю, что программа проверки типов видит «pp.Dict» и думает «typing.Dict». Я подтвердил это, используя модифицированную версию pyparsing, которая переименовывает Dict в DictOf, и не вносил никаких других изменений, и ваше безумно звучащее предложение типа было решено. (К сожалению, простого выполнения from pyparsing import Dict as DictOf было недостаточно.) В качестве примечания я хотел бы упомянуть, что класс Dict в pyparsing появился раньше, чем типизация. Dict примерно на 15 лет — в pyparsing появился Dict первым!

«Я сильно подозреваю, что проблема в проверке типов». Я думаю, что ты, возможно, прав. Очень странно, что он неправильно интерпретирует pp.Dict даже с префиксом пакета, но, опять же, даже подсветка синтаксиса stackoverflow выделяет Dict в pp.Dict, как будто это встроенная функция (и отличается от, скажем, pp.Word или pp.Suppress), заставляя меня думать, что он неправильно интерпретирует Dict в так же.

FeRD 02.07.2024 16:53

Хм. Возможно, стоит предложить pyparsing переименовать их класс Dict в Dictionary канонически, чтобы избежать неправильной интерпретации проверяющими типы и тому подобными. (Они всегда могут оставить Dict в качестве псевдонима для Dictionary, для совместимости.)

FeRD 02.07.2024 17:04

О, хе. Поскольку вы фактически занимаетесь анализом pyparsing, я думаю, я только что предложил это! 😉

FeRD 02.07.2024 17:07

Другие вопросы по теме