【问题标题】:When to use re.compile何时使用 re.compile
【发布时间】:2018-04-26 09:17:22
【问题描述】:

请耐心等待,我无法包含我的 1,000+ 行程序,并且说明中有几个问题。

所以我正在寻找几种类型的模式:

#literally just a regular word
re.search("Word", arg)

#Varying complex pattern
re.search("[0-9]{2,6}-[0-9]{2}-[0-9]{1}", arg)

#Words with varying cases and the possibility of ending special characters 
re.search("Supplier [Aa]ddress:?|Supplier [Ii]dentification:?|Supplier [Nn]ame:?", arg)

#I also use re.findall for the above patterns as well
re.findall("uses patterns above", arg

我总共有大约 75 个,其中一些需要移动到深度嵌套的函数中

我应该何时何地编译这些模式?

现在我正在尝试通过编译 main 中的所有内容来改进我的程序,然后将已编译 RegexObjects 的正确列表传递给使用它的函数。 这会提高我的表现吗?

执行以下操作会提高我的程序速度吗?

re.compile("pattern").search(arg)

编译后的模式是否保留在内存中,所以如果一个函数被多次调用,它会跳过编译部分吗?所以我不必在函数之间移动数据。

如果我移动数据这么多,是否值得编译所有模式?

有没有更好的方法来匹配没有正则表达式的常规单词?

我的代码的简短示例:

import re

def foo(arg, allWords):
   #Does some things with arg, then puts the result into a variable, 
   # this function does not use allWords

   data = arg #This is the manipulated version of arg

   return(bar(data, allWords))


def bar(data, allWords):
   if allWords[0].search(data) != None:
      temp = data.split("word1", 1)[1]
      return(temp)

   elif allWords[1].search(data) != None:
      temp = data.split("word2", 1)[1]
      return(temp)


def main():

   allWords = [re.compile(m) for m in ["word1", "word2", "word3"]]

   arg = "This is a very long string from a text document input, the provided patterns might not be word1 in this string but I need to check for them, and if they are there do some cool things word3"

   #This loop runs a couple million times 
   # because it loops through a couple million text documents
   while True:
      data = foo(arg, allWords)

【问题讨论】:

  • 如果每个模式都使用一次,编译它们是没有意义的。当您预编译并在循环中数千次使用这些模式时,您会看到速度提升,因为您每次都节省了预编译。
  • @cᴏʟᴅsᴘᴇᴇᴅ 每个模式我使用了几百万次
  • 好,你的决定变得容易了!
  • re.compile(m) for m in ["word1", "word2", "word3"] 为什么不 re.compile("|".join(["word1", "word2", "word3"]) 所以你让“oring”到re(它更快)
  • @Jean-FrançoisFabre 现在我知道我可以做到,我一定会使用它

标签: python regex python-2.7 performance


【解决方案1】:

假设word1word2 ... 是正则表达式:

让我们重写这些部分:

allWords = [re.compile(m) for m in ["word1", "word2", "word3"]]

我将为所有模式创建一个单一的正则表达式:

allWords = re.compile("|".join(["word1", "word2", "word3"])

要支持带有| 的正则表达式,您必须将表达式括起来:

allWords = re.compile("|".join("({})".format(x) for x in ["word1", "word2", "word3"])

(当然也适用于标准词,由于| 部分,它仍然值得使用正则表达式)

现在这是一个变相的循环,每个术语都是硬编码的:

def bar(data, allWords):
   if allWords[0].search(data):
      temp = data.split("word1", 1)[1]  # that works only on non-regexes BTW
      return(temp)

   elif allWords[1].search(data):
      temp = data.split("word2", 1)[1]
      return(temp)

可以简单地改写为

def bar(data, allWords):
   return allWords.split(data,maxsplit=1)[1]

在性能方面:

  • 正则表达式是在启动时编译的,所以它尽可能快
  • 没有循环或粘贴表达式,“或”部分由正则表达式引擎完成,这在大多数情况下是一些编译代码:在纯 python 中无法击败。
  • 匹配和拆分在一个操作中完成

最后一个问题是正则表达式引擎在内部循环搜索所有表达式,这使得它成为O(n) 算法。为了让它更快,你必须预测哪个模式是最频繁的,然后把它放在第一位(我的假设是正则表达式是“不相交的”,这意味着一个文本不能被几个匹配,否则最长的将不得不在较短的之前出现)

【讨论】:

  • 我明白你的建议,但我不确定用许多项目来搜索长字符串是个好主意。 1)项目必须首先按长度以相反的顺序排序。 2)re无法在“引擎行走”之前优化这种模式设计。 3) 在“engine walk”中,所有备选方案都在字符串中不匹配的每个位置进行测试。
  • 我明白你的意思:搜索仍然是线性的,所以如果有很多正则表达式可能会很慢。对此的替代方案是泰勒制作的解析,这意味着提前了解模式并在没有正则表达式的情况下优化解析(至少在开始时)。如果存在匹配 2 种表达式的风险,我会说需要反向长度,而 OP 似乎不是这种情况(检查问题中的正则表达式)。我将编辑以添加您提出的问题的综合。
  • 在不知道模式(或它们应该找到什么)和字符串(大小和格式,如果有的话)的情况下,确实很难提出一种方法。当我看到这种问题(带有模式列表)时,我开始怀疑上游的方法,或者我认为人们遗漏了字符串格式的一些细节。但在最坏的情况下,当脚本语言开始达到极限时,也许 DBMS 和最终的工具(如 elasticsearch)是可行的方法。
【解决方案2】:

这是一个棘手的问题:许多答案,甚至一些合法来源,例如 David Beazley 的Python Cookbook,都会告诉您以下信息:

[使用compile()] 当您要使用相同的模式执行大量匹配时。这使您可以只编译一次正则表达式,而不是每次匹配。 [见第那本书的45篇]

然而,自从 Python 2.5 左右的某个时候,这确实不是真的。这是直接来自 re 文档的注释:

注意传递给re.compile()的最新模式的编译版本和模块级匹配函数被缓存,因此一次只使用几个正则表达式的程序不必担心关于编译正则表达式。

有两个小论据反对这一点,但(从传闻中说)这些在大多数情况下不会导致明显的时间差异:

  • 缓存的大小是有限的。
  • 直接使用编译表达式可以避免缓存查找开销。

这是使用20 newsgroups text dataset 对上述内容进行的初步测试。相对而言,编译后的速度提高了大约 1.6%,可能主要是由于缓存查找。

import re
from sklearn.datasets import fetch_20newsgroups

# A list of length ~20,000, paragraphs of text
news = fetch_20newsgroups(subset='all', random_state=444).data

# The tokenizer used by most text-processing vectorizers such as TF-IDF
regex = r'(?u)\b\w\w+\b'
regex_comp = re.compile(regex)


def no_compile():
    for text in news:
        re.findall(regex, text)


def with_compile():
    for text in news:
        regex_comp.findall(text)

%timeit -r 3 -n 5 no_compile()
1.78 s ± 16.2 ms per loop (mean ± std. dev. of 3 runs, 5 loops each)

%timeit -r 3 -n 5 with_compile()
1.75 s ± 12.2 ms per loop (mean ± std. dev. of 3 runs, 5 loops each)

这真的只留下一个非常有理由使用re.compile()的理由:

通过在加载模块时预编译所有表达式,编译工作转移到应用程序启动时间,而不是转移到程序可能响应用户操作的时间点。 [source; p. 15]。在模块顶部用compile 声明的常量并不罕见。例如,在 smtplib 中,您会找到 OLDSTYLE_AUTH = re.compile(r"auth=(.*)", re.I)

请注意,无论您是否使用re.compile(),编译都会(最终)发生。当您使用compile() 时,您正在编译传递的正则表达式。如果您使用re.search() 之类的模块级函数,您将在这一调用中进行编译和搜索。下面的两个过程在这方面是等价的:

# with re.compile - gets you a regular expression object (class)
#     and then call its method, `.search()`.
a = re.compile('regex[es|p]')  # compiling happens now
a.search('regexp')             # searching happens now

# with module-level function
re.search('regex[es|p]', 'regexp')  # compiling and searching both happen here

最后你问了,

有没有更好的方法来匹配没有正则表达式的常规单词?

是的;这在 HOWTO 中被称为"common problem"

有时使用 re 模块是一个错误。如果你匹配一个固定的 字符串或单个字符类,并且您没有使用任何 re IGNORECASE 标志等功能,然后是常规的全部功能 可能不需要表达式。 字符串有几种方法 使用固定字符串执行操作,它们通常很多 更快,因为实现是一个小的 C 循环 为此目的进行了优化,而不是大型的、更通用的 正则表达式引擎。 [强调]

...

简而言之,在转向 re 模块之前,请考虑您的 问题可以用更快更简单的字符串方法来解决。

【讨论】:

    猜你喜欢
    • 2010-10-01
    • 2023-04-01
    • 2011-10-08
    • 1970-01-01
    • 1970-01-01
    • 2019-10-29
    • 2019-10-05
    • 2013-12-21
    相关资源
    最近更新 更多