После публикации этого сообщения, пока прочитав ответы (всем спасибо!) И немного подумав о проблеме, вот лучший подход, который мне удалось найти:
- Найти первое
${
.
- Найти следующий
}
после этого.
- Кормите все, что находится между
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.
'''