Python—删除所有非字母并用下划线替换它们的高效方法

Python—删除所有非字母并用下划线替换它们的高效方法,python,string,performance,Python,String,Performance,还有更快的吗 def format_title(title): ''.join(map(lambda x: x if (x.isupper() or x.islower()) else '_', title.strip())) 应该更快 如果要用一个下划线替换连续的相邻非字母,请使用 import re title = re.sub("[\W\d]", "_", title.strip()) 相反,它甚至更快 我刚刚进行了一次时间比较: title = re.sub("[\W\d]+

还有更快的吗

def format_title(title):  
  ''.join(map(lambda x: x if (x.isupper() or x.islower()) else '_', title.strip()))
应该更快

如果要用一个下划线替换连续的相邻非字母,请使用

import re
title = re.sub("[\W\d]", "_", title.strip())
相反,它甚至更快

我刚刚进行了一次时间比较:

title = re.sub("[\W\d]+", "_", title.strip())

这也适用于Unicode字符串(在Python3下,
\W
匹配任何非Unicode单词字符。在Python2下,您必须另外为此设置
Unicode
标志)。

更快的方法是使用
str.translate()
这比你的方式快约50倍

C:\>python -m timeit -n 100 -s "data=open('test.txt').read().strip()" "''.join(map(lambda x: x if (x.isupper() or x.islower()) else '_', data))"
100 loops, best of 3: 4.51 msec per loop

C:\>python -m timeit -n 100 -s "import re; regex=re.compile('[\W\d]+'); data=open('test.txt').read().strip()" "title=regex.sub('_',data)"
100 loops, best of 3: 2.35 msec per loop
这是unicode的一个版本

# You only need to do this once
>>> title_trans=''.join(chr(c) if chr(c).isupper() or chr(c).islower() else '_' for c in range(256))

>>> "abcde1234!@%^".translate(title_trans)
'abcde________'

# Using map+lambda
$ python -m timeit '"".join(map(lambda x: x if (x.isupper() or x.islower()) else "_", "abcd1234!@#$".strip()))'
10000 loops, best of 3: 21.9 usec per loop

# Using str.translate
$ python -m timeit -s 'titletrans="".join(chr(c) if chr(c).isupper() or chr(c).islower() else "_" for c in range(256))' '"abcd1234!@#$".translate(titletrans)'
1000000 loops, best of 3: 0.422 usec per loop

# Here is regex for a comparison
$ python -m timeit -s 'import re;transre=re.compile("[\W\d]+")' 'transre.sub("_","abcd1234!@#$")'
100000 loops, best of 3: 3.17 usec per loop
请注意,希腊字母计为大写和小写,因此它们不会被替换。 如果要替换它们,只需将条件更改为

# coding: UTF-8

def format_title_unicode_translate(title):
    return title.translate(title_unicode_trans)

class TitleUnicodeTranslate(dict):
    def __missing__(self,item):
        uni = unichr(item)
        res = u"_"
        if uni.isupper() or uni.islower():
            res = uni
        self[item] = res
        return res
title_unicode_trans=TitleUnicodeTranslate()

print format_title_unicode_translate(u"Metallica Μεταλλικα")

如果项目而不是
(x.isupper()或x.islower())
您应该能够使用。
isalpha()
方法可能会为
'
返回
True
(我不记得它是否返回了),但最后您只需将
'
替换为
'
,这样就不会造成任何伤害。(感谢您指出这一点,KennyTM。)

出于我自己的原因,我对这一点感到好奇,我写了一个快速脚本来测试这里列出的不同方法,同时删除lambda,我认为(错误地)它会加快原始解决方案的速度

简短的版本是str.translate方法会将其他方法吹走。另一方面,regex解决方案虽然是紧随其后的第二个解决方案,但正如上面所述是正确的

这是我的测试程序:

        if item<256 and (uni.isupper() or uni.islower()):
  • 编辑:我添加了一个变体,显示列表理解大大改进了原始实现,以及一个正确的正则表达式实现,它显示正确时仍然几乎一样快。当然,str.translate仍然轻而易举地获胜

实际上,它可能会将“u”本身算作字母字符,所以可能不会。试试看。将
\uu
替换为
\u
(或不替换)是无害的。您使用时间的方式是计算所有打开和读取文件的时间。您应该将这些内容移动到
-s
部分以获得有意义的结果谢谢,您当然是对的。因为这两个例子都是这样做的,所以这两个例子的错误应该是相同的。有趣的是,我发现预编译正则表达式并没有什么不同。无论如何,我都更新了计时示例;您必须使用re.compile()指定标志并对结果调用sub()。(这是一个奇怪的API省略。)如果删除“+”版本,您的答案会更好;这不是他想要的,所以这只是分散注意力。我的答案中有两个版本,所以他可以选择更适合自己需要的。有趣的是,我没有发现预编译正则表达式有很大的性能改进……问题告诉你他需要哪一个。正确答案至少应该先回答,然后再回答。+1,好主意。我能想到的唯一缺点是,如果必须考虑非ASCII字符,这将无法在Unicode字符串上正常工作。@Tim,Unicode也有一个翻译-尽管语义不同,让我看看是否能让它工作…@Tim,Unicode版本已启动。翻译映射是按需构建的,因此随着越来越多的字符串被翻译,错过的次数会越来越少。这很可爱,但不是一个非常好的主意。regex版本更清晰、更快。如果在任意输入上使用此dict,它也将不受限制地增长。(边界检查UTF-8值可以避免这成为一种潜在的拒绝服务攻击,但并非所有应用都希望这样做,通常也不需要这样做。)正则表达式解决方案是不正确的,因为它将几个相邻的非字母替换为一个下划线。将
+
放在字符类之后,它将是正确的,尽管速度较慢。我想问题是你是否真的想在替换字符串中有长串的下划线。。。
import re
from time import time


def format_title(title):
    return ''.join(map(lambda x: x if (x.isupper() or x.islower()) else "_",
                       title.strip()))


def format_title_list_comp(title):
    return ''.join([x if x.isupper() or x.islower() else "_" for x in
                    title.strip()])


def format_title_list_comp_is_alpha(title):
    return ''.join([x if x.isalpha() else "_" for x in title.strip()])


def format_title_is_alpha(title):
    return ''.join(map(lambda x: x if x.isalpha() else '_', title.strip()))


def format_title_no_lambda(title):

    def trans(c):
        if c.isupper() or c.islower():
            return c
        return "_"

    return ''.join(map(trans, title.strip()))


def format_title_no_lambda_is_alpha(title):

    def trans(c):
        if c.isalpha():
            return c
        return "_"

    return ''.join(map(trans, title.strip()))


def format_title_re(title):
    return re.sub("[\W\d]+", "_", title.strip())


def format_title_re_corrected(title):
    return re.sub("[\W\d]", "_", title.strip())


TITLE_TRANS = ''.join(chr(c) if chr(c).isalpha() else '_' for c in range(256))


def format_title_with_translate(title):
    return title.translate(TITLE_TRANS)


ITERATIONS = 200000
EXAMPLE_TITLE = "abc123def_$%^!FOO BAR*bazx-bif"


def timetest(f):
    start = time()
    for i in xrange(ITERATIONS):
        result = f(EXAMPLE_TITLE)
    diff = time() - start
    return result, diff


baseline_result, baseline_time = timetest(format_title)


def print_result(f, result, time):
    if result == baseline_result:
        msg = "CORRECT"
    else:
        msg = "INCORRECT"
    diff = time - baseline_time
    if diff < 0:
        indicator = ""
    else:
        indicator = "+"
    pct = (diff / baseline_time) * 100
    print "%s: %0.3fs %s%0.3fs [%s%0.4f%%] (%s - %s)" % (
        f.__name__, time, indicator, diff, indicator, pct, result, msg)


print_result(format_title, baseline_result, baseline_time)

print "----"

for f in [format_title_is_alpha,
          format_title_list_comp,
          format_title_list_comp_is_alpha,
          format_title_no_lambda,
          format_title_no_lambda_is_alpha,
          format_title_re,
          format_title_re_corrected,
          format_title_with_translate]:
    alt_result, alt_time = timetest(f)
    print_result(f, alt_result, alt_time)
format_title: 3.121s +0.000s [+0.0000%] (abc___def_____FOO_BAR_bazx_bif - CORRECT)
----
format_title_is_alpha: 2.336s -0.785s [-25.1470%] (abc___def_____FOO_BAR_bazx_bif - CORRECT)
format_title_list_comp: 2.369s -0.751s [-24.0773%] (abc___def_____FOO_BAR_bazx_bif - CORRECT)
format_title_list_comp_is_alpha: 1.735s -1.386s [-44.4021%] (abc___def_____FOO_BAR_bazx_bif - CORRECT)
format_title_no_lambda: 2.992s -0.129s [-4.1336%] (abc___def_____FOO_BAR_bazx_bif - CORRECT)
format_title_no_lambda_is_alpha: 2.377s -0.744s [-23.8314%] (abc___def_____FOO_BAR_bazx_bif - CORRECT)
format_title_re: 1.290s -1.831s [-58.6628%] (abc_def__FOO_BAR_bazx_bif - INCORRECT)
format_title_re_corrected: 1.338s -1.782s [-57.1165%] (abc___def_____FOO_BAR_bazx_bif - CORRECT)
format_title_with_translate: 0.098s -3.022s [-96.8447%] (abc___def_____FOO_BAR_bazx_bif - CORRECT)
import string,sys
letters=string.letters
mystring = list("abc134#$@e##$%%$*&(()#def")
for n,c in enumerate(mystring):
  if not c in letters:
    mystring[n]="_"
print ''.join(mystring)