2

我有一个函数,它遍历文本文件以通过使用正则表达式来查找信息来提取某些信息。但是,程序会遍历多个文件,因此会多次调用此函数。

目前,我将正则表达式编译为函数的第一步。但是我开始怀疑从编程的角度来看这是否是一个好的设计,因为该函数被多次调用。

解释器是否足够聪明,可以看到这些不会改变并在运行之间缓存它们?或者,我考虑将它们编译为全局变量,以便它始终只能编译一次,但这会将正则表达式与将要使用的位置分开,这使得它更难阅读。我看到的另一个选项是将函数创建为闭包,并在创建时传入正则表达式值,但这似乎不必要地复杂。

简而言之,编译仍然可读且 Pythonic 的 RE(或任何其他计算一次的值)的最有效方法是什么?

谢谢你。

4

4 回答 4

5

Python 的正则表达式模块确实缓存了最近使用的正则表达式的编译版本,因此您可能可以删除显式编译并且没有问题。

于 2013-10-18T17:39:45.203 回答
1

必须注意:在进入这个兔子洞之前,请务必确保这是您的瓶颈。如果您只运行几次正则表达式,并且正则表达式不是特别复杂,那么节省的时间可能很少。

re.compile如果您计划多次运行正则表达式操作,最好的选择是使用。但是,如果没有更多细节,最多只能说:你应该明确地测试。使用 timeit 或其他模块对过程进行计时。

至于缓存编译好的正则表达式,不管是否在后台缓存,直接使用re函数而不是先编译都会付出代价。要看到这一点,您应该使用dis它来查看它的作用:

>>> def f():
...     x="foo bar baz"
...     return re.match("foo", x)
...
>>> dis.dis(f)
  2           0 LOAD_CONST               1 ('foo bar baz')
              3 STORE_FAST               0 (x)

  3           6 LOAD_GLOBAL              0 (re)
              9 LOAD_ATTR                1 (match)
             12 LOAD_CONST               2 ('foo') ** always have to pass the regex
             15 LOAD_FAST                0 (x)
             18 CALL_FUNCTION            2
             21 RETURN_VALUE
>>> n=re.compile("foo")
>>> def g():
...     x="foo bar baz"
...     return n.match("foo")
...
>>> dis.dis(g)
  2           0 LOAD_CONST               1 ('foo bar baz')
              3 STORE_FAST               0 (x)

  3           6 LOAD_GLOBAL              0 (n)
              9 LOAD_ATTR                1 (match)
             12 LOAD_FAST                0 (x)
             15 CALL_FUNCTION            1
             18 RETURN_VALUE

因此,即使它确实在后台缓存了正则表达式,该计划也必须涉及将正则表达式传递给re.match(这是不可避免的)。编译后的版本避免了这一步。

于 2013-10-18T17:47:54.600 回答
0

你可以写一个简单的RegexCache类。类似(未经测试):

class RegexCache(object):
    def __init__(self):
        self.cache = {}

    def get(self, pattern):
        if pattern not in self.cache:
            self.cache[pattern] = re.compile(pattern)

        return self.cache[pattern]
recache = RegexCache()

然后,而不是:

re.search(complex_pattern, whatver)

你做:

re.search(recache.get(complex_pattern), whatver)

这将模式与其使用位置保持在一起,同时也确保您多次使用的模式将被编译。您还必须确保缓存不会变得太大。

于 2013-10-18T17:47:02.987 回答
0

将 re 编译并作为模块级对象使用并没有错。并不是说它始终是当前的最佳实践,而是它在 stdlib 中的各种模块中使用。

对我来说,使用模块级编译的 re 对象比 3.3 的缓存更可取,因为缓存依赖于您可能没有太多控制权(或可能在未来版本中更改)的实现细节。在模块范围内定义它们会使代码的读者清楚地知道它们只编译一次并使用了 N 次。

于 2013-10-18T17:52:23.223 回答