Извлечение заключенного в скобки выражения Python из строки

1

Мне было интересно, как сложно написать код Python для поиска строки для индекса подстроки формы ${ expr }, например, где expr означает Python или что-то похожее на него. Учитывая такую ​​вещь, можно было легко представить, как проверить синтаксис выражения с помощью compile(), оценить его против определенной области с помощью eval() и, возможно, даже подставить результат в исходную строку. Люди должны делать очень похожие вещи все время.

Я мог бы представить себе решение такой проблемы с использованием стороннего генератора парсеров [oof], или с помощью ручного кодирования какого-то государственного устройства [eek], или, возможно, путем убеждения собственного парсера Python как-то сделать тяжелый подъем [hmm ]. Возможно, там есть сторонняя библиотека шаблонов, которая может быть сделана именно так. Возможно, ограничение синтаксиса выражения expr скорее всего будет достойным компромиссом с точки зрения простоты или времени выполнения или сокращения внешних зависимостей - например, возможно, мне действительно нужно что-то, что соответствует любому выражению, которое имеет сбалансированные фигурные скобки.

Каков ваш смысл?

Обновление:

Большое спасибо за ваши ответы! Оглядываясь назад на то, что я написал вчера, я не уверен, что я достаточно ясно понимаю, о чем я прошу. Замена шаблона действительно интересная проблема и, вероятно, гораздо более полезная для многих других людей, чем подзадача экспрессии, о которой мне интересно, но я поднял ее только как простой пример того, как ответ на мой вопрос может быть полезен в реальном жизнь. Некоторые другие потенциальные приложения могут включать передачу выделенных выражений в ярлык синтаксиса; передача результата в реальный синтаксический анализатор Python и просмотр или обезьяна с деревом разбора; или используя последовательность извлеченных выражений для создания более крупной программы Python, возможно, в сочетании с некоторой информацией, взятой из окружающего текста.

Синтаксис ${ expr }, о котором я упоминал, также предназначен в качестве примера, и на самом деле я задаюсь вопросом, не следует ли вместо этого использовать $( expr ) в качестве моего примера, потому что он создает потенциал недостатки очевидного подхода, по линии re.finditer(r'$\{([^}]+)\}', s), немного легче увидеть. Выражения Python могут (и часто) содержать символ ) (или }). Кажется возможным, что обработка любого из этих случаев может быть намного сложнее, чем того стоит, но я пока не уверен в этом. Пожалуйста, не стесняйтесь попробовать этот случай!

Прежде чем опубликовать этот вопрос, я потратил довольно много времени на моделирование шаблонов Python, надеясь, что можно будет разоблачить такую ​​низкоуровневую функциональность, о которой я спрашиваю, а именно, что-то, что может найти выражения в разнообразии контекстов и расскажите мне, где они, а не ограничиваются поиском выражений, встроенных с использованием одного жестко закодированного синтаксиса, всегда оценивая их и всегда подставляя результаты обратно в исходную строку. Я еще не понял, как использовать любой из них для решения моей проблемы, но я очень ценю предложения, касающиеся большего, чтобы посмотреть (не могу поверить, что я пропустил этот замечательный список на вики!). Документация API для этих вещей имеет тенденцию быть довольно высокоуровневой, и я не слишком хорошо знаком с внутренними компонентами любого из них, поэтому я уверен, что смогу использовать помощь, глядя на нее, и выяснить, как заставить их делать такого рода вещи.

Спасибо за ваше терпение!

Теги:
parsing

4 ответа

1
Лучший ответ

Я думаю, что ваш лучший выбор - это сопоставление для всех фигурных скопированных записей, а затем проверка на сам Python независимо от того, действительно ли он Python, для которого compiler было бы полезно.

2

Я думаю, что вы спрашиваете о возможности вставить код Python в текстовые файлы для оценки. Существует несколько модулей, которые уже существуют для обеспечения такого рода функциональности. Вы можете проверить Python.org страницу вики-страницы для полного списка.

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

Если вы действительно хотите просто написать это самостоятельно по какой-либо причине, вы также можете вникать в это решение для кулинарной книги Python Еще одна утилита для папирования Python (YAPTU ):

"Templating" (копирование входного файла для вывода, на лету, вставка Python выражения и утверждения) является частым потреблением, а YAPTU является небольшим, но полный модуль Python для этого; выражений и утверждений произвольными пользовательскими регулярными выражениями.

РЕДАКТИРОВАТЬ: просто для этого я взломал для этого упрощенный пример кода. Я уверен, что у него есть ошибки, но он, по крайней мере, иллюстрирует упрощенную версию концепции:

#!/usr/bin/env python

import sys
import re

FILE = sys.argv[1]

handle = open(FILE)
fcontent = handle.read()
handle.close()

for myexpr in re.finditer(r'\${([^}]+)}', fcontent, re.M|re.S):
    text = myexpr.group(1)
    try:
        exec text
    except SyntaxError:
        print "ERROR: unable to compile expression '%s'" % (text)

Протестировано по следующему тексту:

This is some random text, with embedded python like 
${print "foo"} and some bogus python like

${any:thing}.

And a multiline statement, just for kicks: 

${
def multiline_stmt(foo):
  print foo

multiline_stmt("ahem")
}

More text here.

Выход:

[user@host]$ ./exec_embedded_python.py test.txt
foo
ERROR: unable to compile expression 'any:thing'
ahem
1

Если вы хотите обрабатывать произвольные выражения, такие как {'{spam': 42}["spam}"], вы не можете обойтись без полноразмерного парсера.

0

После публикации этого сообщения, прочитав ответы до сих пор (спасибо всем!), и немного подумав о проблеме, вот лучший подход, который я смог придумать:

  • Найдите первый ${.
  • После этого найдите следующий }.
  • Загружайте все, что находится между ними, на compile(). Если он работает, вставьте в него вилку, и мы закончили.
  • В противном случае продолжайте расширять строку, ища последующие вхождения }. Как только что-то скомпилируется, верните его.
  • Если мы закончим }, не имея возможности компилировать что-либо, используйте результаты последней попытки компиляции, чтобы предоставить информацию о том, где проблема.

Преимущества такого подхода:

  • Код довольно короткий и понятный.
  • Это довольно эффективно - оптимально даже в том случае, если выражение не содержит }. В худшем случае кажется, что это тоже не так уж плохо.
  • Он работает во многих выражениях, содержащих ${ и/или }.
  • Нет внешних зависимостей. На самом деле ничего не нужно импортировать. (Это меня удивило.)

Недостатки:

  • Иногда он хватает слишком много или слишком мало. Ниже приведен пример последнего. Я мог бы представить себе страшный пример, в котором у вас есть два выражения, а первый - неправильно, и алгоритм по ошибке ошибочно захватывает все это и все между ними и возвращает его как действительное, хотя я не смог это продемонстрировать. Возможно, все не так плохо, как я боюсь. Я не думаю, что в целом можно избежать недоразумений - определение проблемы является скользким - но похоже, что следовало бы сделать это лучше, особенно если бы вы хотели торговать простотой или временем выполнения.
  • Я не делал никаких тестов, но я мог предположить, что существуют более быстрые альтернативы, особенно в случаях, когда в выражении участвует много }. Это может быть большой проблемой, если вы захотите применить этот метод к значительным блокам кода Python, а не только к очень коротким выражениям.

Вот моя реализация.

def findExpr(s, i0=0, begin='${', end='}', compArgs=('<string>', 'eval')):
  assert '\n' not in s, 'line numbers not implemented'
  i0 = s.index(begin, i0) + len(begin)
  i1 = s.index(end, i0)
  code = errMsg = None
  while code is None and errMsg is None:
    expr = s[i0:i1]
    try: code = compile(expr, *compArgs)
    except SyntaxError, e:
      i1 = s.find(end, i1 + 1)
      if i1 < 0: errMsg, i1 = e.msg, i0 + e.offset
  return i0, i1, code, errMsg

И вот докштрина с некоторыми иллюстрациями в формате doctest, которую я не вставлял в середину вышеприведенной функции только потому, что она длинная, и мне кажется, что код легче читать без нее.

'''
Search s for a (possibly invalid) Python expression bracketed by begin
and end, which default to '${' and '}'.  Return a 4-tuple.

>>> s = 'foo ${a*b + c*d} bar'
>>> i0, i1, code, errMsg = findExpr(s)
>>> i0, i1, s[i0:i1], errMsg
(6, 15, 'a*b + c*d', None)
>>> ' '.join('%02x' % ord(byte) for byte in code.co_code)
'65 00 00 65 01 00 14 65 02 00 65 03 00 14 17 53'
>>> code.co_names
('a', 'b', 'c', 'd')
>>> eval(code, {'a': 1, 'b': 2, 'c': 3, 'd': 4})
14
>>> eval(code, {'a': 'a', 'b': 2, 'c': 'c', 'd': 4})
'aacccc'
>>> eval(code, {'a': None})
Traceback (most recent call last):
  ...
NameError: name 'b' is not defined

Expressions containing start and/or end are allowed.

>>> s = '{foo ${{"}": "${"}["}"]} bar}'
>>> i0, i1, code, errMsg = findExpr(s)
>>> i0, i1, s[i0:i1], errMsg
(7, 23, '{"}": "${"}["}"]', None)

If the first match is syntactically invalid Python, i0 points to the
start of the match, i1 points to the parse error, code is None and
errMsg contains a message from the compiler.

>>> s = '{foo ${qwerty asdf zxcvbnm!!!} ${7} bar}'
>>> i0, i1, code, errMsg = findExpr(s)
>>> i0, i1, s[i0:i1], errMsg
(7, 18, 'qwerty asdf', 'invalid syntax')
>>> print code
None

If a second argument is given, start searching there.

>>> i0, i1, code, errMsg = findExpr(s, i1)
>>> i0, i1, s[i0:i1], errMsg
(33, 34, '7', None)

Raise ValueError if there are no further matches.

>>> i0, i1, code, errMsg = findExpr(s, i1)
Traceback (most recent call last):
  ...
ValueError: substring not found

In ambiguous cases, match the shortest valid expression.  This is not
always ideal behavior.

>>> s = '{foo ${x or {} # return {} instead of None} bar}'
>>> i0, i1, code, errMsg = findExpr(s)
>>> i0, i1, s[i0:i1], errMsg
(7, 25, 'x or {} # return {', None)

This implementation must not be used with multi-line strings.  It does
not adjust line number information in the returned code object, and it
does not take the line number into account when computing the offset
of a parse error.

'''

Ещё вопросы

Сообщество Overcoder
Наверх
Меню