我一直想知道编写一些Python代码来搜索字符串以查找${
expr }
形式的子字符串索引的难度。例如, expr 意味着是Python表达式或类似的表达式。鉴于这样的事情,可以很容易地想象继续使用compile()
检查表达式的语法,使用eval()
针对特定范围进行评估,甚至可能将结果替换为原始字符串。人们必须一直做着非常相似的事情。
我可以想象使用第三方解析器生成器[oof]解决这样的问题,或者通过手工编写某种状态机[eek],或者通过说服Python自己的解析器以某种方式完成繁重的工作[嗯]。也许在某个地方有一个第三方模板库可以做到这一点。也许限制 expr 的语法在简单性或执行时间或减少外部依赖性方面可能是一个值得妥协的方法 - 例如,我真正需要的只是与任何 expr 具有平衡的花括号。
你有什么感觉?
非常感谢您迄今为止的回复!回顾昨天我写的内容,我不确定我是否已经清楚地知道我在问什么。模板替换确实是一个有趣的问题,对于更多人来说可能比我想知道的表达式提取子问题更有用,但是我提出它只是作为一个简单的例子来说明我的问题的答案在实际中是如何有用的生活。其他一些潜在的应用程序可能包括将提取的表达式传递给语法高亮显示器;将结果传递给真正的Python解析器并查看或使用解析树进行查找;或者使用提取的表达式序列来构建一个更大的Python程序,可能与从周围文本中获取的一些信息一起使用。
我提到的${
expr }
语法也是一个例子,事实上我想知道我是否应该使用$(
< em> expr )
作为我的例子,因为它使re.finditer(r'$\{([^}]+)\}', s)
的明显方法的潜在缺点更容易看到。 Python表达式可以(通常也包含))
(或}
)字符。处理任何这些案件似乎可能比它的价值更麻烦,但我还不相信。请随意尝试这个案例!
在发布这个问题之前,我花了很多时间研究Python模板引擎,希望有人可能会暴露我正在询问的那种低级功能 - 即可以找到多种表达式的东西上下文并告诉我它们在哪里而不是仅限于使用单个硬编码语法查找嵌入的表达式,始终对它们进行评估,并始终将结果替换回原始字符串。我还没弄清楚如何使用它们中的任何一个来解决我的问题,但我非常感谢有关更多内容的建议(不能相信我错过了维基上那个精彩的列表!)。这些东西的API文档往往是相当高级的,我对它们中的任何内部都不太熟悉,所以我确信我可以使用帮助来查看这些并找出如何让它们去做这种事情。
感谢您的耐心等待!
答案 0 :(得分:2)
我认为您所要求的是能够将Python代码插入要评估的文本文件中。有几个模块已经存在以提供这种功能。您可以查看Python.org Templating wiki page以获取完整列表。
一些谷歌搜索也发现了一些您可能感兴趣的其他模块:
如果你真的只是为了自己编写这个问题,你也可以深入研究这个Python食谱解决方案Yet Another Python Templating Utility (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 :(得分:1)
我认为你最好的选择是匹配所有花括号的条目,然后检查Python本身是否是有效的Python,compiler会有用。
答案 2 :(得分:1)
如果你想处理像{'{spam': 42}["spam}"]
这样的任意表达式,如果没有完整的解析器,你就无法逃脱。
答案 3 :(得分: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.
'''