Pyparsing предлагает подкласс ParseElementEnhance
DelimitedList
для анализа списков (обычно разделенных запятыми):
>>> kv_element = pp.Word(pp.alphanums)
>>> kv_list = pp.DelimitedList(kv_element)
>>> kv_list.parse_string('red, green, blue')
ParseResults(['red', 'green', 'blue'], {})
И он предоставляет подкласс TokenConverter
Dict
для преобразования повторяющегося выражения в словарь:
>>> 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 из списка с разделителями, но по сравнению с приведенным выше для этого требуется:
DelimitedList
для вывода Group()
sDelimitedList
при построении 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, так что, возможно, я просто смотрю на него неправильно.)
Ни один вариант перегрузки «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)
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 первым!
Хм. Возможно, стоит предложить pyparsing переименовать их класс Dict
в Dictionary
канонически, чтобы избежать неправильной интерпретации проверяющими типы и тому подобными. (Они всегда могут оставить Dict
в качестве псевдонима для Dictionary
, для совместимости.)
О, хе. Поскольку вы фактически занимаетесь анализом pyparsing, я думаю, я только что предложил это! 😉
«Я сильно подозреваю, что проблема в проверке типов». Я думаю, что ты, возможно, прав. Очень странно, что он неправильно интерпретирует
pp.Dict
даже с префиксом пакета, но, опять же, даже подсветка синтаксиса stackoverflow выделяетDict
вpp.Dict
, как будто это встроенная функция (и отличается от, скажем,pp.Word
илиpp.Suppress
), заставляя меня думать, что он неправильно интерпретируетDict
в так же.