在 Python 中缓存已编译的正则表达式对象?
题
每次导入包含大量静态正则表达式的 python 文件时,都会花费 CPU 周期将字符串编译到内存中的代表性状态机中。
a = re.compile("a.*b")
b = re.compile("c.*d")
...
问题:是否可以以预编译的方式将这些正则表达式存储在磁盘上的缓存中,以避免在每次导入时执行正则表达式编译?
Pickle 对象只是执行以下操作,无论如何都会导致编译发生:
>>> import pickle
>>> import re
>>> x = re.compile(".*")
>>> pickle.dumps(x)
"cre\n_compile\np0\n(S'.*'\np1\nI0\ntp2\nRp3\n."
和 re
对象是不可编组的:
>>> import marshal
>>> import re
>>> x = re.compile(".*")
>>> marshal.dumps(x)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
ValueError: unmarshallable object
解决方案
是否可以以预编译的方式将这些正则表达式存储在磁盘上的缓存中,以避免在每次导入时执行正则表达式编译?
不容易。您必须编写一个挂钩到 C 的自定义序列化程序 sre
Python 正则表达式引擎的实现。所需的时间和精力将远远超过任何性能优势。
首先,您是否真正分析过代码?我怀疑编译正则表达式是应用程序运行时的重要组成部分。请记住,它们仅在当前执行中第一次导入模块时进行编译 - 此后,模块及其属性将缓存在内存中。
如果您有一个程序基本上生成一次,编译一堆正则表达式,然后退出,您可以尝试重新设计它以在一次调用中执行多个测试。然后您可以重复使用正则表达式,如上所述。
最后,您可以将正则表达式编译成基于 C 的状态机,然后将它们与扩展模块链接。虽然这可能更难以维护,但它将完全从您的应用程序中消除正则表达式编译。
其他提示
请注意,每个模块在应用程序的生命周期中仅初始化一次,无论您导入多少次。因此,如果您在模块的全局范围内编译表达式(即不在函数中)你应该没问题。
首先,这是 python re 模块中的一个明显的限制。它限制了正则表达式的合理程度和大小。对于长时间运行的进程,该限制较大;对于命令行应用程序等短期进程,该限制较小。
几年前我确实看过它,可以挖掘出编译结果,pickle它,然后unpickle它并重用它。问题是它需要使用 sre.py 内部结构,因此可能无法在不同的 python 版本中工作。
我希望我的工具箱中有这样的功能。我还想知道是否有任何单独的模块可以替代。
这 搁置 模块似乎工作得很好:
import re
import shelve
a_pattern = "a.*b"
b_pattern = "c.*d"
a = re.compile(a_pattern)
b = re.compile(b_pattern)
x = shelve.open('re_cache')
x[a_pattern] = a
x[b_pattern] = b
x.close()
# ...
x = shelve.open('re_cache')
a = x[a_pattern]
b = x[b_pattern]
x.close()
然后,您可以创建一个漂亮的包装类,它自动为您处理缓存,以便它对用户变得透明......留给读者的练习。
打开 /usr/lib/python2.5/re.py 并查找“def _compile”。您会发现 re.py 的内部缓存机制。
可以将每个正则表达式(或正则表达式组)放入单独的文件中,然后使用 imp 模块动态导入所需的文件。我怀疑它的扩展性是否很好,但它可能正是您所需要的。
哼,
架子上不用pickle吗?
不管怎样,我同意前面的答案。由于模块仅处理一次,我怀疑编译正则表达式将成为您的应用程序瓶颈。Python re 模块速度非常快,因为它是用 C 编写的:-)
但好消息是 Python 拥有一个很好的社区,所以我相信您可以找到目前正在开发您所需要的东西的人。
我用谷歌搜索了 5 秒,发现: http://home.gna.org/oomadness/en/cerealizer/index.html.
不知道它是否会做到这一点,但如果没有,祝你研究好运:-)