Как парсить строку в python
Перейти к содержимому

Как парсить строку в python

  • автор:

Как парсить строки с помощью Python?

Как с помощью Python парсить строки? Например, приходит инф с адресом «Киевская обл. , г. Киев, ул. Крещатик, дом 50, кв. 8» по факту этот адрес должен быть связан с ФИО клиента в БД. Но так как в БД залили не такой формат, то по поиску в БД не ищет ибо адрес может быть в другом порядке записан или без указания букв «кв», «дом» и тд.

  • Вопрос задан более трёх лет назад
  • 1331 просмотр

2 комментария

Средний 2 комментария

Уровень изложения на уровне.

Нужен пример входной строки и то, что нужно получить на выходе

A1K0

A1K0 @A1K0 Автор вопроса

Входной строки с бд? сори я пока не очень понимаю.
За изложение тоже сори, по той дже причине)
Коротко изложить, то нам нужно просто найти совпадения в БД и распарсить на нужные строки типа «область, город, улица. Дом. кв»

Решения вопроса 2

ScriptKiddo

ScriptKiddo @ScriptKiddo

Для начала вам нужно нормализовать адреса в базе. Сделать это можно, например, с помощью геокодера от Яндекса
/geocoder

Например, ищем «Москва, Тверская 6»

 < "response": < "GeoObjectCollection": < "metaDataProperty": < "GeocoderResponseMetaData": < "request": "Москва,Тверская 6", "results": "10", "found": "1" >>, "featureMember": [ < "GeoObject": < "metaDataProperty": < "GeocoderMetaData": < "precision": "exact", "text": "Россия, Москва, Тверская улица, 6с1", "kind": "house", "Address": < "country_code": "RU", "formatted": "Россия, Москва, Тверская улица, 6с1", "postal_code": "125009", "Components": [ < "kind": "country", "name": "Россия" >, < "kind": "province", "name": "Центральный федеральный округ" >, < "kind": "province", "name": "Москва" >, < "kind": "locality", "name": "Москва" >, < "kind": "street", "name": "Тверская улица" >, < "kind": "house", "name": "6с1" >] >, "AddressDetails": < "Country": < "AddressLine": "Россия, Москва, Тверская улица, 6с1", "CountryNameCode": "RU", "CountryName": "Россия", "AdministrativeArea": < "AdministrativeAreaName": "Москва", "Locality": < "LocalityName": "Москва", "Thoroughfare": < "ThoroughfareName": "Тверская улица", "Premise": < "PremiseNumber": "6с1", "PostalCode": < "PostalCodeNumber": "125009" >> > > > > > > >, "name": "Тверская улица, 6с1", "description": "Москва, Россия", "boundedBy": < "Envelope": < "lowerCorner": "37.607242 55.757926", "upperCorner": "37.615452 55.762556" >>, "Point": < "pos": "37.611347 55.760241" >> > ] > > >

После чего загружаете в базу в нужной схеме и ищете по нормализованным данным

Ответ по адресу из вопроса

 < "response": < "GeoObjectCollection": < "metaDataProperty": < "GeocoderResponseMetaData": < "request": "Киевская обл. , г. Киев, ул. Крещатик, дом 50, кв. 8", "results": "10", "found": "2" >>, "featureMember": [ < "GeoObject": < "metaDataProperty": < "GeocoderMetaData": < "precision": "exact", "text": "Украина, Киев, улица Крещатик, 50", "kind": "house", "Address": < "country_code": "UA", "formatted": "Украина, Киев, улица Крещатик, 50", "Components": [ < "kind": "country", "name": "Украина" >, < "kind": "province", "name": "Киев" >, < "kind": "locality", "name": "Киев" >, < "kind": "street", "name": "улица Крещатик" >, < "kind": "house", "name": "50" >] >, "AddressDetails": < "Country": < "AddressLine": "Украина, Киев, улица Крещатик, 50", "CountryNameCode": "UA", "CountryName": "Украина", "AdministrativeArea": < "AdministrativeAreaName": "Киев", "Locality": < "LocalityName": "Киев", "Thoroughfare": < "ThoroughfareName": "улица Крещатик", "Premise": < "PremiseNumber": "50" >> > > > > > >, "name": "улица Крещатик, 50", "description": "Киев, Украина", "boundedBy": < "Envelope": < "lowerCorner": "30.516022 50.440632", "upperCorner": "30.524232 50.445875" >>, "Point": < "pos": "30.520127 50.443254" >> >, < "GeoObject": < "metaDataProperty": < "GeocoderMetaData": < "precision": "other", "text": "Украина, Киевская область", "kind": "province", "Address": < "country_code": "UA", "formatted": "Украина, Киевская область", "Components": [ < "kind": "country", "name": "Украина" >, < "kind": "province", "name": "Киевская область" >] >, "AddressDetails": < "Country": < "AddressLine": "Украина, Киевская область", "CountryNameCode": "UA", "CountryName": "Украина", "AdministrativeArea": < "AdministrativeAreaName": "Киевская область" >> > > >, "name": "Киевская область", "description": "Украина", "boundedBy": < "Envelope": < "lowerCorner": "29.266411 49.179114", "upperCorner": "32.161466 51.554013" >>, "Point": < "pos": "30.456149 50.29807" >> > ] > > >

UPD: добавил ответ по адресу из вопроса.

Ответ написан более трёх лет назад
Нравится 3 2 комментария

vmamontov

ScriptKiddo, отличная мысль!
Если не ошибаюсь, геокодер отдаёт список совпадений. Можно брать первый результат в ответе. Но будет ли он правильным? ScriptKiddo, интересует твой опыт)
И кроме того, у геокодера есть ограничения по числу запросов. Хотя думаю их хватит, просто придётся парсить на протяжении нескольких дней.
Мне тоже интересна тема с обработкой адресов. Буду рад любым советам)

ScriptKiddo

ScriptKiddo @ScriptKiddo

Ограничение — 25K запросов в сутки, но если результаты будете использовать в коммерческой деятельности, то все равно придется использовать платную версию API

vmamontov

A1K0, добро пожаловать в дивный мир распарсивания адресов)
Это «кроличья нора».
В идеале адреса в базу данных должны писаться через справочники (КЛАДР, ФИАС): выбираешь из словаря город, потом улицу и т.д.
Если же у тебя адрес зашит в одну строку, в одно текстовое поле в БД, то пользователи могут писать адрес всевозможными способами. Часто имеют место ошибки в написании улиц, использование черточек, дефисов, дробей, запятых, точек с запятой. Если база содержит сотни тысяч записей, то выверить все ошибки будет очень долго и муторно.
Если адрес строго формализован, то проблем почти нет. Парсишь через разделитель.
Но перед этим советую посчитать число этих разделителей в каждой строке. Может оказаться так, что у тебя в одну запись попали два адреса (регистрации и фактического проживания).

list_address = ["Киевская обл. , г. Киев, ул. Крещатик, дом 10, кв. 8", "Киевская обл. , г. Киев, ул. Крещатик, 2, 12", "Киевская обл. , г. Киев, ул. Крещатик, д.20, кв.118", "Киевская обл. ; Киев; Крещатик; 50-8", "Киевская обл. , г. Киев, пл. Незалежности, д.12-а, строение 3, помещение 8, офис. 33",] for adr in list_address: # допустим разделитель запятая # и корректный адрес должен состоять из 5 частей region, city, street, house, flat = "", "", "", "", "", if adr.count(",") == 4: adr = adr.split(",") region = adr[0] city = adr[1] street = adr[2] house = adr[3] flat = adr[4] print(region, city, street, house, flat) # распарсятся три адреса и пяти: # Киевская обл. г. Киев ул. Крещатик дом 10 кв. 8 # Киевская обл. г. Киев ул. Крещатик 2 12 # Киевская обл. г. Киев ул. Крещатик д.20 кв.118

Ответ написан более трёх лет назад
Комментировать
Нравится 1 Комментировать
Ответы на вопрос 1

dimonchik2013

Dimonchik @dimonchik2013
non progredi est regredi

но не копипастом функции, а пониманием исходных данных и методов преобразования

кому тамита парсер понадобится, а кому — простое векторное расстояние названия улицы от массива улиц в базе

Как разбить строки в Python 1 мин для чтения

PlayStation размахивает молотком запретов так, как мечтают игроки Xbox

Favorite

Добавить в избранное

Python 3 - Обзор

В этой статье объясняется, как разделить строки в Python с помощью методов «split()» и «partition()». Эти методы особенно полезны, если вы хотите преобразовать предложение или группу слов в анализируемые и повторяемые типы Python. Все примеры кода в этой статье протестированы на Python версии 3.8.6.

Метод разделения

Метод split() может использоваться для разделения слов с использованием разделителя, указанного пользователем. Он возвращает список разделенных слов без разделителя. Если разделитель не указан пользователем, пробел (один или несколько) используется в качестве единственного разделителя.

Например, приведенный ниже код вернет в качестве вывода «[‘Linux’, ‘Ex’]»:

text = "Linux Ex" text.split()

Приведенный ниже код вернет «[‘AndreyEx’, ‘ru’]» в качестве вывода, когда «.» используется как разделитель:

text = "AndreyEx.ru" text.split(“.”)

Разделитель не обязательно должен состоять из одного символа. Метод split принимает два аргумента:

  • sep: разделитель, который будет использоваться для разделения
  • maxsplit: количество делений, которое нужно сделать

Оба эти аргумента необязательны. Как упоминалось выше, если аргумент «sep» не указан, пробел используется в качестве разделителя для разделения. Аргумент «maxsplit» имеет значение по умолчанию «-1» и по умолчанию разбивает все вхождения. Рассмотрим код ниже:

text = "AndreyEx.co.us" text.split(“.”)

В качестве вывода он вернет «[‘AndreyEx’, ‘co’, ‘us’]». Если вы хотите остановить разделение при первом появлении разделителя, укажите «1» в качестве аргумента «maxsplit».

text = "AndreyEx.co.us" text.split(“.”, 1)

Приведенный выше код вернет в качестве вывода «[‘AndreyEx’, ‘co.us’]». Просто укажите количество вхождений, когда вы хотите, чтобы процесс разделения остановился, в качестве второго аргумента.

Обратите внимание, что если есть последовательные разделители, для остальных разделителей после первого разделения будет возвращена пустая строка (когда аргумент «maxsplit» не используется):

text = "AndreyEx.ru" text.split(".")

Приведенный выше код вернет «[‘AndreyEx’,», ‘ru’] »в качестве вывода. Если вы хотите удалить пустые строки из результирующего списка, вы можете использовать следующий оператор понимания списка:

text = "AndreyEx.ru" result = text.split(".") result = [item for item in result if item != ""] print (result)

Читать Лучший способ изучить Python (пошаговое руководство 2020 года). Часть 3

Вы получите «[‘AndreyEx’, ‘ru’]» в качестве вывода после выполнения приведенного выше примера кода.

Обратите внимание, что метод split() перемещается слева направо для разделения строк на слова. Если вы хотите разделить строку справа налево, используйте вместо этого «rsplit()». Его синтаксис, использование и аргументы точно такие же, как у метода split().

Если при использовании методов «split()» или «rsplit()» в строке не обнаружен разделитель, исходная строка возвращается как единственный элемент списка.

Метод разделения

Метод partition() может использоваться для разделения строк, и он работает идентично методу split() с некоторыми отличиями. Наиболее заметное отличие состоит в том, что он сохраняет разделитель и включает его как элемент в результирующий кортеж, содержащий разделенные слова. Это особенно полезно, если вы хотите разделить строку на повторяющийся объект (в данном случае кортеж), не удаляя исходные символы. Рассмотрим код ниже:

text = "AndreyEx.ru" result = text.partition(".") print (result)

Приведенный выше пример кода вернет «(‘AndreyEx’, ‘.’, ‘ru’)» в качестве вывода. Если вы хотите, чтобы результат был типа списка, используйте вместо этого следующий пример кода:

text = "AndreyEx.ru" result = list(text.partition(".")) print (result)

Вы должны получить «[‘AndreyEx’, ‘.’, ‘ru’]» в качестве вывода после выполнения приведенного выше примера кода.

Метод partition() принимает только один аргумент, называемый sep. Пользователи могут указать разделитель любой длины. В отличие от метода split(), этот аргумент является обязательным, поэтому вы не можете пропустить разделитель. Однако вы можете указать пробел в качестве разделителя.

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

text = "AndreyEx.co.us" result = list(text.partition(".")) print (result)

Пример кода выдаст в качестве вывода «[‘AndreyEx’, ‘.’, ‘Co.us’]». Если вы хотите разделить все вхождения разделителя и включить разделитель в окончательный список, возможно, вам придется использовать шаблон «Регулярное выражение» или «Регулярное выражение». В примере, упомянутом выше, вы можете использовать шаблон RegEx следующим образом:

import re text = "AndreyEx.co.us" result = re.split("(\.)", text) print (result)

Читать Пространства имен и область применения в Python

Вы получите «[‘AndreyEx’, ‘.’, ‘Co’, ‘.’, ‘Us’]» в качестве вывода после выполнения приведенного выше примера кода. Точка была экранирована в операторе RegEx, упомянутом выше. Обратите внимание, что хотя приведенный выше пример работает с одним символом точки, он может не работать со сложными разделителями и сложными строками. Возможно, вам придется определить свой собственный шаблон RegEx в зависимости от вашего варианта использования. Пример приведен здесь, чтобы дать вам некоторое представление о процессе сохранения разделителя в окончательном списке с помощью операторов RegEx.

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

text = "AndreyEx" result = list(text.partition(".")) result = [item for item in result if item != ""] print (result)

После выполнения приведенного выше кода вы должны получить в качестве вывода «[‘AndreyEx’]».

Вывод

Для простых и понятных разделений вы можете использовать методы «split()» и «partition()», чтобы получить повторяющиеся типы. Для сложных строк и разделителей вам нужно будет использовать операторы RegEx.

Если вы нашли ошибку, пожалуйста, выделите фрагмент текста и нажмите Ctrl+Enter.

Парсим на Python: Pyparsing для новичков

Парсинг (синтаксический анализ) представляет собой процесс сопоставления последовательности слов или символов — так называемой формальной грамматике. Например, для строчки кода:

import matplotlib.pyplot as plt 

имеет место следующая грамматика: сначала идёт ключевое слово import, потом название модуля или цепочка имён модулей, разделённых точкой, потом ключевое слово as, а за ним — наше название импортируемому модулю.

В результате парсинга, например, может быть необходимо прийти к следующему выражению:

Данное выражение представляет собой словарь Python, который имеет два ключа: ‘import’ и ‘as’. Значением для ключа ‘import’ является список, в котором по порядку перечислены названия импортируемых модулей.

Для парсинга как правило используют регулярные выражения. Для этого имеется модуль Python под названием re (regular expression — регулярное выражение). Если вам не доводилось работать с регулярными выражениями, их вид может вас испугать. Например, для строки кода ‘import matplotlib.pyplot as plt’ оно будет иметь вид:

r'^[ \t]*import +\D+\.\D+ +as \D+' 

К счастью, есть удобный и гибкий инструмент для парсинга, который называется Pyparsing. Главное его достоинство — он делает код более читаемым, а также позволяет проводить дополнительную обработку анализируемого текста.

В данной статье мы установим Pyparsing и создадим на нём наш первый парсер.

Вначале установим Pyparsing. Если Вы работаете в Linux, в командной строке наберите:

sudo pip install pyparsing 

В Windows Вам необходимо в командной строке, запущенной с правами администратора, предварительно зайти в каталог, где лежит файл pip.exe (например, C:\Python27\Scripts\), после чего выполнить:

pip install pyparsing 

Другой способ — это зайти на страницу проекта Pyparsing на SourceForge, скачать там инсталлятор для Windows и установить Pyparsing как обычную программу. Полную информацию о всевозможных способах установки Pyparsing можно получить на странице проекта.

Перейдём к парсингу. Пусть s — следующая строка:

s = 'import matplotlib.pyplot as plt' 

В результате парсинга мы хотим получить словарь:

Сначала необходимо импортировать Pyparsing. Запустите например Python IDLE и введите:

from pyparsing import * 

Звёздочка * выше означает импорт всех имён из pyparsing. В результате это может нарушить рабочее пространство имён, что приведёт к ошибкам в работе программы. В нашем случае * используется временно, потому что мы пока не знаем, какие классы из Pyparsing мы будем использовать. После того, как мы напишем парсер, мы заменим * на названия использованных нами классов.

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

Начнём с того, что у нас в строке есть название модуля. Формальная грамматика: в общем случае название модуля — это слово, состоящее из букв и символа нижнего подчёркивания. На pyparsing:

module_name = Word(alphas + '_') 

Word — это слово, alphas — буквы. Word(alphas + ‘_’) — слово, состоящее из букв и нижнего подчёркивания. module_name переводится как название модуля. Теперь читаем всё вместе: название модуля — это слово, состоящее из букв и символа нижнего подчёркивания. Таким образом, запись на Pyparsing очень близка к естественному языку.

Полное имя модуля — это название модуля, потом точка, потом название другого модуля, потом снова точка, потом название третьего модуля и так далее, пока по цепочке не дойдём до искомого модуля. Полное имя модуля может состоять из имени одного модуля и не иметь точек. На pyparsing:

full_module_name = module_name + ZeroOrMore('.' + module_name) 

ZeroOrMore дословно переводится как «ноль или более», а это означает, что содержимое в скобках может повторяться несколько раз или отсутствовать. В итоге читаем полностью вторую строчку парсера: полное имя модуля — это название модуля, после которого ноль и более раз идут точка и название модуля.

После полного названия модуля идёт необязательная часть ‘as plt’. Она представляет собой ключевое слово ‘as’, после которого идёт имя, которое мы сами дали импортируемому модулю. На pyparsing:

import_as = Optional('as' + module_name) 

Optional дословно переводится как «необязательный», а это означает, что содержимое в скобках может быть, а может отсутствовать. В сумме получаем: «необязательное выражение, состоящее из слова ‘as’ и названия модуля.

Полная инструкция импорта состоит из ключевого слова import, после которого идёт полное имя модуля, потом необязательная конструкция ‘as plt’. На pyparsing:

parse_module = 'import' + full_module_name + import_as 

В итоге имеем наш первый парсер:

module_name = Word(alphas + '_') full_module_name = module_name + ZeroOrMore('.' + module_name) import_as = Optional('as' + module_name) parse_module = 'import' + full_module_name + import_as 

Теперь надо распарсить строку s:

parse_module.parseString(s) 
(['import', 'matplotlib', '.', 'pyplot', 'as', 'plt'], <>) 

Вывод можно улучшить, преобразовав результат в список:

parse_module.parseString(s).asList() 
['import', 'matplotlib', '.', 'pyplot', 'as', 'plt'] 

Теперь будем совершенствовать парсер. Прежде всего, мы бы не хотели видеть в выводе парсера слово import и точку между названиями модулей. Для подавления вывода используется Suppress(). С учётом этого наш парсер выглядит так:

module_name = Word(alphas + '_') full_module_name = module_name + ZeroOrMore(Suppress('.') + module_name) import_as = Optional(Suppress('as') + module_name) parse_module = Suppress('import') + full_module_name + import_as 

Выполнив parse_module.parseString(s).asList() , получим:

['matplotlib', 'pyplot', 'plt'] 

Давайте теперь сделаем так, чтобы парсер сразу возвращал нам словарь вида <'import':[модуль1, модуль2, . ], 'as':модуль>. Прежде чем сделать это, вначале нужно отдельно получить доступ к списку импортируемых модулей (full_module_name) и к нашему собственному названию модуля (import_as). Для этого pyparsing позволяет назначать имена результатам парсинга. Давайте дадим списку импортируемых модулей имя ‘modules’, а тому, как мы сами назвали модуль — имя ‘import as’:

full_module_name = (module_name + ZeroOrMore(Suppress('.') + module_name))('modules') import_as = (Optional(Suppress('as') + module_name))('import_as') 

Как видно из двух строчек выше, чтобы дать результату парсинга имя, нужно выражение парсера поставить в скобки, и после этого выражения в скобках дать название результата. Давайте посмотрим, что изменилось. Для этого выполним код:

res = parse_module.parseString(s) print(res.modules.asList()) print(res.import_as.asList()) 
['matplotlib', 'pyplot'] ['plt'] 

Теперь мы можем отдельно извлекать цепочку модулей для импорта искомого и наше название для него. Осталось сделать так, чтобы парсер возвращал словарь. Для этого используется так называемое ParseAction — действие в процессе парсинга:

parse_module = (Suppress('import') + full_module_name).setParseAction(lambda t: ) 

lambda — это анонимная функция в Python, t — аргумент этой функции. Потом идёт двоеточие и выражение словаря Python, в который мы подставляем нужные нам данные. Когда мы вызываем asList(), мы получаем список. Имя модуля после as всегда одно, и список t.import_as.asList() всегда будет содержать только одно значение. Поэтому мы берём единственный элемент списка (он имеет индекс ноль) и пишем asList()[0].

Проверим парсер. Выполним parse_module.parseString(s).asList() и получим:

Мы почти достигли цели. Так как у полученного списка единственный аргумент, добавим [0] в конце строки для парсинга текста: parse_module.parseString(s).asList()[0]

Мы получили то, что хотели.

Достигнув цели, необходимо вернуться к ‘from pyparsing import *’ и поменять звёздочку на те классы, которые нам пригодились:

from pyparsing import Word, alphas, ZeroOrMore, Suppress, Optional 

В итоге наш код имеет следующий вид:

from pyparsing import Word, alphas, ZeroOrMore, Suppress, Optional module_name = Word(alphas + "_") full_module_name = (module_name + ZeroOrMore(Suppress('.') + module_name))('modules') import_as = (Optional(Suppress('as') + module_name))('import_as') parse_module = (Suppress('import') + full_module_name + import_as).setParseAction(lambda t: ) 

Мы рассмотрели совсем простой пример и лишь небольшую часть возможностей Pyparsing. За бортом — создание рекурсивных выражений, обработка таблиц, поиск по тексту с оптимизацией, резко ускоряющей сам поиск, и многое другое.

В заключение пару слов о себе. Я аспирант и ассистент МГТУ им. Баумана (кафедра МТ-1 „Металлорежущие станки“). Увлекаюсь Python, Linux, HTML, CSS и JS. Моё хобби — автоматизация инженерной деятельности и инженерных расчётов. Считаю, что могу быть полезным Хабру, делясь своими знаниями о работе в Pyparsing, Sage и некоторыми особенностями автоматизации инженерных расчётов. Также знаю среду SageMathCloud, которая является мощной альтернативой Wolfram Alpha. SageMathCloud заточена на проведение расчётов на Python в облаке. При этом Вам доступна консоль (Ubuntu под капотом), Sage, IPython и LaTeX. Есть возможность совместной работы. Помимо кода на Python SageMathCloud поддерживает html, css, js, coffescript, go, fortran, scilab и многое другое. В настоящее время среда бесплатна (достаточно стабильная бета-версия), потом будет будет работать по системе Freemium. На текущий момент времени эта среда не освещена на Хабре, и я хотел бы восполнить этот пробел.

Благодарю Дарью Фролову и Никиту Коновалова за помощь в редактировании статьи.

Парсинг строк на Python 3.x

Я хочу написать парсер строк, но я реально не знаю, какой модуль использовать и как это реализовать
То есть я хочу получить из, например, hello —world -N -w 5 —Num 5 3 2 и т. д. список [«hello», «big-flag:world», «flag:N», [«flag:w», «5»], [«big-flag:Num», «5»], «3», «2»]
Если ещё разделение по пробелам понятно, то как сделать флаги и другие элементы — не очень

Отслеживать
задан 6 мар 2021 в 10:39
Krutos VIP Krutos VIP
69 2 2 серебряных знака 9 9 бронзовых знаков
Посмотрите на стандартный модуль argparse .
6 мар 2021 в 10:43

Уже смотрел, он дает вроде только уже указанные флаги и параметры. Мне же надо отпарсить любые флаги и параметры, данные юзером. Правда, могу ошибаться

6 мар 2021 в 10:44

Можно получить строку с параметрами через sys.argv[1:] и потом парсить как обычный список (разделение по пробелу). А вообще зачем парсить любые флаги если каждый флаг = какому то действию, а если я передал флаг asd который Вы не запрограммировали что должно произойти?

6 мар 2021 в 11:31

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

6 мар 2021 в 12:01

1 ответ 1

Сортировка: Сброс на вариант по умолчанию

Сделал программу для разбора аргументов командной строки под ваши запросы. Получилась она не маленькой, но рабочей. По ссылке выше я разбил ее на несколько файлов и вообще поместил в отдельный модуль. Ниже приведен аналогичный код одним файлом.

class FToken(object): """Единица лексического разбора; абстрактный базовый класс. Токен имеет свой тип T, по которому его можно идентифицировать, и свое значение value. Для использования вам необходимо определить класс токена и как параметр передать его тип. class MyToken(FToken, type="MyType"): В этом классе необходимо переопределить статические методы can_parse и parse, принимающие один аргумент - экземпляр FParser. Метод can_parse всех дочерник классов будут вызываться в том порядке, в котором эти классы объявлены. Например, в следующем примере класс TEXT никогда не признается токеном. class MyToken(FToken, type="MyType"): @staticmethod def can_parse(parser): return True // . class TEXT(FToken, type="text"): // . """ T = "unknown" def __init__(self, value=None): """Конструктор. @param value Какое угодно значение токена. """ self.value = value def __str__(self): """Возвращает интерпретацию токена как строку.""" return self.T _childs = list() def __init_subclass__(cls, type: str="unknown"): """Инициализирует и регистрирует наследников.""" super().__init_subclass__() cls.T = type cls._childs.append(cls) @staticmethod def can_parse(parser): """Проверяет, возможно ли пролексировать этот токен. @param parser Экземпляр FParser. @return Возвращает True, если возможно; иначе, False. """ return False @classmethod def parse(cls, parser): """Лексирует этот токен. @param parser Экземпляр FParser. @return Возвращает полученный токен или None. """ raise NotImplementedError() #''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''# class FTWhitespaces(FToken, type="whitespaces"): @staticmethod def can_parse(parser): return (parser.char.isspace()) @classmethod def parse(cls, parser): def _(): return parser.char.isspace() parser.parse_by_condition(_) return None class FTParam(FToken, type="param"): def __str__(self): return self.T + ":" + self.value @staticmethod def can_parse(parser): return (parser.char in "'\"" or parser.char.isalnum()) @classmethod def parse(cls, parser): value = str() if parser.char in "'\"": pair = parser.char parser.advance() def _(): return (parser.char != pair) value = parser.parse_by_condition(_) parser.eat(pair) else: value = parser.parse_keyword() return cls(value) class FTBigFlag(FToken, type="big-flag"): def __str__(self): return self.T + ":" + self.value @staticmethod def can_parse(parser): return (parser.char == '-' and parser.peek() == '-') @classmethod def parse(cls, parser): parser.eat('--') key = parser.parse_keyword() return cls(key) class FTFlag(FToken, type="flag"): def __str__(self): return self.T + ":" + self.value @staticmethod def can_parse(parser): return (parser.char == '-') @classmethod def parse(cls, parser): parser.eat('-') key = parser.parse_keyword() return cls(key) #''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''# class FParser(object): """Парсер строк аргументов. """ def __init__(self, arguments: str): self._tokens = list() self.data = arguments self.index = -1 self.char = None if len(arguments) != 0: self.advance() self._parse() self._dispose() def advance(self, n: int=1): """Продвижение вперед. Продвигает переменные вперед; переход к следующему символу. @param n На сколько символов продвинуться. """ self.index += n if self.index >= 0 and self.index < len(self.data): self.char = self.data[self.index] else: self.char = None def eat(self, victim: str): """Поедает ключевую фразу. Если ожидаемая и фактическая фраза расходятся, то вызывается синтаксическое исключение. """ for char in victim: if self.char != char: raise SyntaxError("Unexcepted syntax error.") self.advance() def peek(self): """Предпросмотр следующего символа. @return Возвращает следующий символ или None. """ self.advance() ch = self.char self.advance(-1) return ch def parse_by_condition(self, condition): """Получает ключевую фразу. Ключевая фраза состоит из любых символов, добавляемых, если функция condition возвращает истину (True). Пример использования смотрите в коде FParser.parse_keyword. @param condition Функция-условие, отвечающая за остановку ключевой фразы. Для этого должна вернуть False. @return Возвращает ключевую фразу. """ keyword = str() while self.char != None and condition() == True: keyword += self.char self.advance() return keyword def parse_keyword(self): """Получает ключевое слово. @return Возвращает ключевое слово. """ def _(): return not self.char.isspace() return self.parse_by_condition(_) def _parse(self): while self.char != None: index = self.index for token_cls in FToken._childs: if token_cls.can_parse(self): self._tokens.append(token_cls.parse(self)) break; if index == self.index: ''' Что произошло? - был вызван token_cls.parse, но он ни шиша не сделал. - ни один token_cls.can_parse не вернул True. Что делать? - Вызовем абстрактное исключение лексирования. ''' print(self._tokens) raise SyntaxError(f"Неверный синтаксис на позиции ") def _dispose(self): self.index = None self.char = None self.data = None # Примечание: self._tokens удаляется при getter'е свойства result. @property def result(self): """Выдает результат лексирования. Анализирует полученные токены и превращает их в конечный результат. """ if '_result' in dir(self): return self._result result = list() require_param = False for token in self._tokens: if token == None: continue # whitespaces и другой мусор. if require_param == True: if not isinstance(token, FTParam): break # Go to raise result[len(result) - 1].append(token.value) require_param = False continue if isinstance(token, FTParam): result.append(token.value) elif isinstance(token, (FTBigFlag, FTFlag)): if token.value in ("w", "Num"): result.append([str(token), ]) require_param = True else: result.append(str(token)) else: ''' Программист добавил токены, но не добавил способ их обработки в конечный результат. ''' raise NotImplementedError() if require_param == True: raise Exception("Флаг с параметром не имеет параметра.") _tokens = None self._result = result return result #''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''# import os def GetArgsStr(): result = str() for arg in os.sys.argv: if ' ' in arg: result += "'" + arg + "'" else: result += arg result += ' ' return result.rstrip(' ') def main(): arguments = GetArgsStr() #print("Command line: \"" + arguments + "\"") parser = FParser(arguments) print(parser.result) if __name__ == "__main__": main() 

Пример командной строки запуска скрипта:

C:\Users\Shamus_Rezol\Desktop\fargparse>python example.py --world -N -w 5 --Num 5 3 2 
Command line: "example.py --world -N -w 5 --Num 5 3 2" ['example.py', 'big-flag:world', 'flag:N', ['flag:w', '5'], ['big-flag:Num', '5'], '3', '2'] 

Примечание

Создание экземпляра FParser и первое получение результатов - места возможного возникновения исключений.

obj = FParser("example.py --world -N -w 5 --Num 5 3 2") obj.result 

Я не учел, что параметр может начинаться со специального символа '/'. Исправить это возможно в tokens.py , FTParam , can_parse . Однако с параметрами вида 'C:/Users' проблем не будет.

Возможно, не учел я и много других мелочей.

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *