Извлечение заключенного в скобки выражения Python из строки - PullRequest
1 голос
/ 16 октября 2008

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

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

Какой у тебя смысл?

Обновление:

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

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

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

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

Ответы [ 4 ]

2 голосов
/ 16 октября 2008

Я думаю, что вы спрашиваете о возможности вставить код 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 голос
/ 16 октября 2008

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

1 голос
/ 16 октября 2008

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

0 голосов
/ 17 октября 2008

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

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

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

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

Недостатки:

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

'''
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...