我的编程几乎都是自学成才,所以如果我的一些术语在这个问题上没有,我会提前道歉。另外,我将使用一个简单的例子来帮助说明我的问题,但请注意,示例本身并不重要,它只是一种希望让我的问题更清晰的方法。
想象一下,我有一些格式不正确的文本,有很多额外的空白区域,我想要清理。因此,我创建了一个函数,它将替换任何具有新行字符的空白字符组,其中包含一个新行字符和任何其他具有单个空格的空白字符组。该函数可能如下所示
def white_space_cleaner(text):
new_line_finder = re.compile(r"\s*\n\s*")
white_space_finder = re.compile(r"\s\s+")
text = new_line_finder.sub("\n", text)
text = white_space_finder.sub(" ", text)
return text
这很好用,问题是现在每次调用函数时都要编译正则表达式。为了让它运行得更快我可以像这样重写它
new_line_finder = re.compile(r"\s*\n\s*")
white_space_finder = re.compile(r"\s\s+")
def white_space_cleaner(text):
text = new_line_finder.sub("\n", text)
text = white_space_finder.sub(" ", text)
return text
现在正则表达式只编译一次,函数运行得更快。在两个函数上使用timeit
我发现第一个函数每个循环需要27.3μs,第二个函数每个循环需要25.5μs。一个小的加速,但如果函数被称为数百万的时间或有数百个模式而不是2,这可能是重要的。当然,第二个函数的缺点是它污染全局命名空间并使代码可读性降低。是否有一些“Pythonic”方法将一个对象(如编译的正则表达式)包含在函数中,而不是每次调用函数时都重新编译它?
答案 0 :(得分:5)
保留要应用的元组(正则表达式和替换文本)列表;似乎并没有迫切需要单独命名每一个。
finders = [
(re.compile(r"\s*\n\s*"), "\n"),
(re.compile(r"\s\s+"), " ")
]
def white_space_cleaner(text):
for finder, repl in finders:
text = finder.sub(repl, text)
return text
您还可以合并functools.partial
:
from functools import partial
replacers = {
r"\s*\n\s*": "\n",
r"\s\s+": " "
}
# Ugly boiler-plate, but the only thing you might need to modify
# is the dict above as your needs change.
replacers = [partial(re.compile(regex).sub, repl) for regex, repl in replacers.iteritems()]
def white_space_cleaner(text):
for replacer in replacers:
text = replacer(text)
return text
答案 1 :(得分:3)
另一种方法是在一个类中对通用功能进行分组:
class ReUtils(object):
new_line_finder = re.compile(r"\s*\n\s*")
white_space_finder = re.compile(r"\s\s+")
@classmethod
def white_space_cleaner(cls, text):
text = cls.new_line_finder.sub("\n", text)
text = cls.white_space_finder.sub(" ", text)
return text
if __name__ == '__main__':
print ReUtils.white_space_cleaner("the text")
它已经分组在一个模块中,但根据代码的其余部分,一个类也适用。
答案 2 :(得分:2)
您可以将正则表达式编译放入函数参数中,如下所示:
def white_space_finder(text, new_line_finder=re.compile(r"\s*\n\s*"),
white_space_finder=re.compile(r"\s\s+")):
text = new_line_finder.sub("\n", text)
text = white_space_finder.sub(" ", text)
return text
由于默认函数参数被评估when the function is parsed,它们只会被加载一次而它们不会在模块命名空间中。如果你真的需要,它们还可以灵活地替换调用代码。缺点是有些人可能会认为它污染了功能签名。
我想尝试计时,但我无法弄清楚如何正确使用timeit
。您应该看到与全局版本类似的结果。
马库斯对你的帖子的评论是正确的;有时将变量放在模块级别是很好的。但是,如果您不希望其他模块容易看到它们,请考虑使用下划线添加名称;这个marks them as module-private,如果你from module import *
,它将不会导入以下划线开头的名称(如果你通过名字询问它们,你仍然可以得到它们)。
永远记住;最终所有“在Python中执行此操作的最佳方式”几乎总是“使代码最具可读性的原因是什么?”首先创建Python是为了易于阅读,所以做你认为最具可读性的东西。
答案 3 :(得分:1)
在这种特殊情况下,我认为无关紧要。检查:
Is it worth using Python's re.compile?
正如您在答案和源代码中所看到的那样:
https://github.com/python/cpython/blob/master/Lib/re.py#L281
re
模块的实现具有正则表达式本身的缓存。因此,您看到的小速度可能是因为您避免查找缓存。
现在,正如问题一样,有时候做这样的事情是非常相关的,就像建立一个内部缓存一样,它仍然是函数的命名空间。
def heavy_processing(arg):
return arg + 2
def myfunc(arg1):
# Assign attribute to function if first call
if not hasattr(myfunc, 'cache'):
myfunc.cache = {}
# Perform lookup in internal cache
if arg1 in myfunc.cache:
return myfunc.cache[arg1]
# Very heavy and expensive processing with arg1
result = heavy_processing(arg1)
myfunc.cache[arg1] = result
return result
这样执行:
>>> myfunc.cache
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: 'function' object has no attribute 'cache'
>>> myfunc(10)
12
>>> myfunc.cache
{10: 12}
答案 4 :(得分:-1)
您可以使用静态函数属性来保存已编译的re。此示例执行类似操作,将转换表保留在一个函数属性中。
def static_var(varname, value):
def decorate(func):
setattr(func, varname, value)
return func
return decorate
@static_var("complements", str.maketrans('acgtACGT', 'tgcaTGCA'))
def rc(seq):
return seq.translate(rc.complements)[::-1]