用Python构造高效函数
我的编程几乎都是自学的,所以如果我在这个问题上的一些术语不正确,我会提前道歉。另外,我将用一个简单的例子来帮助说明我的问题,但请注意,这个例子本身并不重要,它只是希望让我的问题更清楚的一种方式 想象一下,我有一些格式不好的文本,有很多额外的空白,我想清理。因此,我创建了一个函数,该函数将用一个新行字符替换任何包含新行字符的空白字符组,并用一个空格替换任何其他空白字符组。函数可能如下所示用Python构造高效函数,python,Python,我的编程几乎都是自学的,所以如果我在这个问题上的一些术语不正确,我会提前道歉。另外,我将用一个简单的例子来帮助说明我的问题,但请注意,这个例子本身并不重要,它只是希望让我的问题更清楚的一种方式 想象一下,我有一些格式不好的文本,有很多额外的空白,我想清理。因此,我创建了一个函数,该函数将用一个新行字符替换任何包含新行字符的空白字符组,并用一个空格替换任何其他空白字符组。函数可能如下所示 def白色空间清洁剂(文本): new\u line\u finder=re.compile(r“\s*\n\
def白色空间清洁剂(文本):
new\u line\u finder=re.compile(r“\s*\n\s*”)
白空间查找器=重新编译(r“\s\s+”)
text=new\u line\u finder.sub(“\n”,text)
text=white\u space\u finder.sub(“,text)
返回文本
这很好,问题是现在每次我调用函数时,它都必须编译正则表达式。为了让它运行得更快,我可以这样重写它
new\u line\u finder=re.compile(r“\s*\n\s*”)
白空间查找器=重新编译(r“\s\s+”)
def白色空间清洁剂(文本):
text=new\u line\u finder.sub(“\n”,text)
text=white\u space\u finder.sub(“,text)
返回文本
现在正则表达式只编译一次,函数运行速度更快。在两个函数上使用
timeit
,我发现第一个函数每个循环需要27.3µs,第二个函数每个循环需要25.5µs。这是一个小的速度提升,但是如果函数被调用数百万次,或者有数百个模式而不是2个模式,这可能会非常重要。当然,第二个函数的缺点是它污染了全局名称空间,使代码可读性降低。是否有某种“Pythonic”方法可以在函数中包含对象(如编译后的正则表达式),而不必每次调用函数时都重新编译它?您可以使用静态函数属性保存编译后的正则表达式。这个例子做了类似的事情,在一个函数属性中保留一个转换表
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]
保留要应用的元组列表(正则表达式和替换文本);似乎没有迫切的必要单独命名每一个
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
您可以将正则表达式编译放入函数参数中,如下所示:
>>> 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}
def white\u space\u finder(text,new\u line\u finder=re.compile(r“\s*\n\s*”),
白空间查找器=重新编译(r“\s\s+”):
text=new\u line\u finder.sub(“\n”,text)
text=white\u space\u finder.sub(“,text)
返回文本
由于默认函数参数是经过计算的,因此它们只会被加载一次,并且不会出现在模块名称空间中。如果您真的需要,它们还可以让您灵活地替换调用代码时使用的代码。缺点是有些人可能认为它污染了函数签名。
我想尝试计时,但我不知道如何正确使用timeit
。您应该会看到与全局版本类似的结果
不过,马库斯对你文章的评论是正确的;有时将变量放在模块级是很好的。如果你不想让他们很容易看到其他模块,那么,考虑用下划线准备名字;如果您从模块导入中执行*
,它将不会导入以下划线开头的名称(但是,如果您按名称向他们询问,您仍然可以获取这些名称)
永远记住;“在Python中实现这一点的最佳方法是什么”的结尾几乎总是“什么使代码最具可读性?”Python的创建首先也是最重要的,是为了易于阅读,所以做您认为最具可读性的事情。另一种方法是将通用功能分组到一个类中:
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")
它已经分组在一个模块中,但根据代码的其余部分,一个类也可能是合适的。在这种特殊情况下,我认为这无关紧要。检查: 正如您在答案和源代码中所看到的:
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}
>>myfunc.cache
回溯(最近一次呼叫最后一次):
文件“”,第1行,在
AttributeError:“函数”对象没有属性“缓存”
>>>myfunc(10)
12
>>>myfunc.cache
{10: 12}
您所做的一切都很好。有时,全局变量是最佳解决方案,尤其是当它是一个无法编辑的静态值时。当变量的值从其他地方更改而您不希望它更改时,问题通常会出现。对于像编译的正则表达式值这样的只读变量,这不应该是个问题。这在本例中不太起作用,因为根据是否找到换行符,您正在子绑定不同的值。根据你的想法,我想你可能会使用字典,但那肯定没有你给出的例子那么简洁易读。哦,对了,我错过了。我可以解决这个问题,并提供一个更具可读性的替代方案(尽管这个解决方案并不太糟糕)。我检查了这个答案,因为它实现了加速,并且可以轻松地扩展到更复杂的示例,但是chepner的答案也达到了速度,在你对几种不同的模式做完全相同的事情的情况下,答案可能更清晰,AlexVan Liew的答案也有效,在模式数量较少或需要灵活性的情况下,答案可能更清晰