使用Python从字符串中去除除字母数字字符以外的所有内容。

456

使用Python,从字符串中剥离所有非字母数字字符的最佳方法是什么?

此问题的PHP变体中提供的解决方案可能需要进行一些微调,但对我来说似乎不太“pythonic”。

记录一下,我不仅想去掉句号、逗号(和其他标点符号),还想去掉引号、括号等。


9
你关心国际字母数字字符吗,比如'æøå','مرحبا','สวัสดี','こんにちは'? - Pimin Konstantin Kefaloukos
5
@PiminKonstantinKefaloukos,我确实关心国际字符,因此在接受的答案中评论使用re.UNICODE。 - Mark van Lent
16个回答

448

我只是出于好奇计时了一些函数。在这些测试中,我从内置的string模块的string.printable字符串中删除非字母数字字符。使用编译后的'[\W_]+'pattern.sub('', str)方法被发现是最快的。

$ python -m timeit -s \
     "import string" \
     "''.join(ch for ch in string.printable if ch.isalnum())" 
10000 loops, best of 3: 57.6 usec per loop

$ python -m timeit -s \
    "import string" \
    "filter(str.isalnum, string.printable)"                 
10000 loops, best of 3: 37.9 usec per loop

$ python -m timeit -s \
    "import re, string" \
    "re.sub('[\W_]', '', string.printable)"
10000 loops, best of 3: 27.5 usec per loop

$ python -m timeit -s \
    "import re, string" \
    "re.sub('[\W_]+', '', string.printable)"                
100000 loops, best of 3: 15 usec per loop

$ python -m timeit -s \
    "import re, string; pattern = re.compile('[\W_]+')" \
    "pattern.sub('', string.printable)" 
100000 loops, best of 3: 11.2 usec per loop

4
非常有趣的结果:我本以为正则表达式会更慢。有趣的是,我尝试了另外一个选项(使用valid_characters = string.ascii_letters + string.digits并跟随join(ch for ch in string.printable if ch in valid_characters)),它比使用isalnum()选项快6微秒。但仍然比正则表达式慢得多。 - DrAl
58
记录一下:使用re.compile('[\W_]+', re.UNICODE)来保证Unicode安全。 - Mark van Lent
6
你如何在不移除空格的情况下完成它? - maudulus
8
不移除空格的情况下执行:re.sub('[\W_]+', ' ', sentence, flags=re.UNICODE) - PALEN
使用 filter 的第二种解决方案返回的是一个过滤器对象,而不是一个字符串。还需要将其连接起来。 - physicalattraction
显示剩余6条评论

373

利用正则表达式解决问题:

import re
re.sub(r'\W+', '', your_string)
根据Python的定义,'\W == [^a-zA-Z0-9_],即排除所有数字字母_

2
正则表达式中的加号是什么作用?(我知道它的意思,只是好奇为什么在re.sub中需要它。) - Mark van Lent
7
@Mark:我认为这可以加快替换速度,因为替换将一次性去除块内所有非单词字符,而不是逐个删除它们。 - DrAl
2
是的,我之前在调整一些性能关键代码时进行了基准测试。如果有大量字符需要替换,那么加速效果非常明显。 - Ants Aasma
28
在这种情况下可能并不相关,但\W会同时保留下划线。 - Blixt
16
按照@Blixt的建议,如果你只想保留字母和数字,可以使用re.sub(r'[^a-zA-Z0-9]','', your_string)。 - Nigini
显示剩余5条评论

81
使用 str.translate() 方法。
假设您将经常这样做:
  1. Once, create a string containing all the characters you wish to delete:

    delchars = ''.join(c for c in map(chr, range(256)) if not c.isalnum())
    
  2. Whenever you want to scrunch a string:

    scrunched = s.translate(None, delchars)
    
设置成本可能比re.compile更有优势;边际成本要低得多:
C:\junk>\python26\python -mtimeit -s"import string;d=''.join(c for c in map(chr,range(256)) if not c.isalnum());s=string.printable" "s.translate(None,d)"
100000 loops, best of 3: 2.04 usec per loop

C:\junk>\python26\python -mtimeit -s"import re,string;s=string.printable;r=re.compile(r'[\W_]+')" "r.sub('',s)"
100000 loops, best of 3: 7.34 usec per loop

注意:使用string.printable作为基准数据会使模式'[\W_]+'具有不公平的优势;所有非字母数字字符都在一起...在典型数据中,可能需要进行多次替换:
C:\junk>\python26\python -c "import string; s = string.printable; print len(s),repr(s)"
100 '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ!"#$%&\'()*+,-./:;=>?@[\\]^_`{|}~ \t\n\r\x0b\x0c'

如果您让re.sub做更多的工作,会发生以下情况:

C:\junk>\python26\python -mtimeit -s"d=''.join(c for c in map(chr,range(256)) if not c.isalnum());s='foo-'*25" "s.translate(None,d)"
1000000 loops, best of 3: 1.97 usec per loop

C:\junk>\python26\python -mtimeit -s"import re;s='foo-'*25;r=re.compile(r'[\W_]+')" "r.sub('',s)"
10000 loops, best of 3: 26.4 usec per loop

3
这绝对是最具Python特色的解决方案。 - codygman
2
这几乎让我信服了,但我建议使用 string.punctuation 而不是 ''.join(c for c in map(chr, range(256)) if not c.isalnum()) - ArnauOrriols
3
请注意,这适用于 str 对象而不是 unicode 对象。 - Yavar
1
需要更新为Python 3! - jtlz2
1
@jtlz2 我将这个答案改编为 Python 3,并在此处发布了一篇新的答案:https://dev59.com/jnM_5IYBdhLWcg3wrFIt#70310018 - jslatane
显示剩余5条评论

66
你可以尝试这样做:
print ''.join(ch for ch in some_string if ch.isalnum())

Python的简洁之美,真是可爱! - sandeepsign

17
>>> import re
>>> string = "Kl13@£$%[};'\""
>>> pattern = re.compile('\W')
>>> string = re.sub(pattern, '', string)
>>> print string
Kl13

1
我很喜欢你的回答,但它也删除了阿拉伯字符,请问你能告诉我如何保留它们吗? - Charif DZ

16

你觉得这个怎么样:

def ExtractAlphanumeric(InputString):
    from string import ascii_letters, digits
    return "".join([ch for ch in InputString if ch in (ascii_letters + digits)])

使用列表推导式生成一个包含 InputString 中存在于组合的ascii_lettersdigits字符串中的字符的列表,然后将列表合并为一个字符串。


似乎 string.ascii_letters 只包含字母(当然),而不包括数字。我还需要数字... - Mark van Lent
添加 string.digits 确实可以解决我刚才提到的问题。 :) - Mark van Lent
是的,当我回过头来阅读你的问题时,我意识到了这一点。自己给自己留个便条:学会阅读! - DrAl

10

我用perfplot(我的一个项目)检查了结果,并发现对于短字符串,

"".join(filter(str.isalnum, s))

对于长字符串(200+字符),它是最快的。

re.sub("[\W_]", "", s)

速度最快。

输入图像描述

复制代码以重现绘图:

import perfplot
import random
import re
import string

pattern = re.compile("[\W_]+")


def setup(n):
    return "".join(random.choices(string.ascii_letters + string.digits, k=n))


def string_alphanum(s):
    return "".join(ch for ch in s if ch.isalnum())


def filter_str(s):
    return "".join(filter(str.isalnum, s))


def re_sub1(s):
    return re.sub("[\W_]", "", s)


def re_sub2(s):
    return re.sub("[\W_]+", "", s)


def re_sub3(s):
    return pattern.sub("", s)


b = perfplot.bench(
    setup=setup,
    kernels=[string_alphanum, filter_str, re_sub1, re_sub2, re_sub3],
    n_range=[2**k for k in range(10)],
)
b.save("out.png")
b.show()

使用这段代码,将允许通过非字母数字字符,比如俄文文本。 - Ezequiel Adrian

8
sent = "".join(e for e in sent if e.isalpha())

我来试着解释一下:它通过 e for e in sent 遍历所有字符串字符,并通过 if e.isalpha() 语句检查当前字符是否为字母符号,如果是,则通过 sent = "".join() 将其连接到 sent 变量上,而所有非字母符号将因为 join 函数而被替换为 ""(空字符串)。 - Sysanin
由于这是对每个字符执行循环而不是依赖于C正则表达式,所以速度非常慢,对吗? - dcsan
我更喜欢使用 e.alnum() - Vishal Kumar Sahu

7

使用ASCII可打印字符的随机字符串进行计时:

from inspect import getsource
from random import sample
import re
from string import printable
from timeit import timeit

pattern_single = re.compile(r'[\W]')
pattern_repeat = re.compile(r'[\W]+')
translation_tb = str.maketrans('', '', ''.join(c for c in map(chr, range(256)) if not c.isalnum()))


def generate_test_string(length):
    return ''.join(sample(printable, length))


def main():
    for i in range(0, 60, 10):
        for test in [
            lambda: ''.join(c for c in generate_test_string(i) if c.isalnum()),
            lambda: ''.join(filter(str.isalnum, generate_test_string(i))),
            lambda: re.sub(r'[\W]', '', generate_test_string(i)),
            lambda: re.sub(r'[\W]+', '', generate_test_string(i)),
            lambda: pattern_single.sub('', generate_test_string(i)),
            lambda: pattern_repeat.sub('', generate_test_string(i)),
            lambda: generate_test_string(i).translate(translation_tb),

        ]:
            print(timeit(test), i, getsource(test).lstrip('            lambda: ').rstrip(',\n'), sep='\t')


if __name__ == '__main__':
    main()

结果(Python 3.7):

       Time       Length                           Code                           
6.3716264850008880  00  ''.join(c for c in generate_test_string(i) if c.isalnum())
5.7285426190064750  00  ''.join(filter(str.isalnum, generate_test_string(i)))
8.1875841680011940  00  re.sub(r'[\W]', '', generate_test_string(i))
8.0002205439959650  00  re.sub(r'[\W]+', '', generate_test_string(i))
5.5290945199958510  00  pattern_single.sub('', generate_test_string(i))
5.4417179649972240  00  pattern_repeat.sub('', generate_test_string(i))
4.6772285089973590  00  generate_test_string(i).translate(translation_tb)
23.574712151996210  10  ''.join(c for c in generate_test_string(i) if c.isalnum())
22.829975890002970  10  ''.join(filter(str.isalnum, generate_test_string(i)))
27.210196289997840  10  re.sub(r'[\W]', '', generate_test_string(i))
27.203713296003116  10  re.sub(r'[\W]+', '', generate_test_string(i))
24.008979928999906  10  pattern_single.sub('', generate_test_string(i))
23.945240008994006  10  pattern_repeat.sub('', generate_test_string(i))
21.830899796994345  10  generate_test_string(i).translate(translation_tb)
38.731336012999236  20  ''.join(c for c in generate_test_string(i) if c.isalnum())
37.942474347000825  20  ''.join(filter(str.isalnum, generate_test_string(i)))
42.169366310001350  20  re.sub(r'[\W]', '', generate_test_string(i))
41.933375883003464  20  re.sub(r'[\W]+', '', generate_test_string(i))
38.899814646996674  20  pattern_single.sub('', generate_test_string(i))
38.636144253003295  20  pattern_repeat.sub('', generate_test_string(i))
36.201238164998360  20  generate_test_string(i).translate(translation_tb)
49.377356811004574  30  ''.join(c for c in generate_test_string(i) if c.isalnum())
48.408927293996385  30  ''.join(filter(str.isalnum, generate_test_string(i)))
53.901889764994850  30  re.sub(r'[\W]', '', generate_test_string(i))
52.130339455994545  30  re.sub(r'[\W]+', '', generate_test_string(i))
50.061149017004940  30  pattern_single.sub('', generate_test_string(i))
49.366573111998150  30  pattern_repeat.sub('', generate_test_string(i))
46.649754120997386  30  generate_test_string(i).translate(translation_tb)
63.107938601999194  40  ''.join(c for c in generate_test_string(i) if c.isalnum())
65.116287978999030  40  ''.join(filter(str.isalnum, generate_test_string(i)))
71.477421126997800  40  re.sub(r'[\W]', '', generate_test_string(i))
66.027950693998720  40  re.sub(r'[\W]+', '', generate_test_string(i))
63.315361931003280  40  pattern_single.sub('', generate_test_string(i))
62.342320287003530  40  pattern_repeat.sub('', generate_test_string(i))
58.249303059004890  40  generate_test_string(i).translate(translation_tb)
73.810345625002810  50  ''.join(c for c in generate_test_string(i) if c.isalnum())
72.593953348005020  50  ''.join(filter(str.isalnum, generate_test_string(i)))
76.048324580995540  50  re.sub(r'[\W]', '', generate_test_string(i))
75.106637657001560  50  re.sub(r'[\W]+', '', generate_test_string(i))
74.681338128997600  50  pattern_single.sub('', generate_test_string(i))
72.430461594005460  50  pattern_repeat.sub('', generate_test_string(i))
69.394243567003290  50  generate_test_string(i).translate(translation_tb)

str.maketransstr.translate是最快的,但包含所有非ASCII字符。re.compilepattern.sub比较慢,但比''.joinfilter要快。


7
作为其他答案的补充,我提供了一种非常简单而灵活的方法来定义一组字符,限制字符串的内容。在这种情况下,我允许字母数字字符加上破折号和下划线。根据你的用例需要,只需添加或删除我的PERMITTED_CHARS中的字符即可。
PERMITTED_CHARS = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ_-" 
someString = "".join(c for c in someString if c in PERMITTED_CHARS)

3
不要硬编码允许输入的字符,因为这容易出现细微错误。使用string.digits + string.ascii_letters + '_-'来代替。 - Reti43
你的建议并没有错,但如果你的目标是节省“打字”的字符数量,它也没有节省多少。如果你复制我的帖子,你也不会有拼写错误!然而,我回答的真正重点是允许一种明确、开放和简单的方式来定义你想要允许的确切字符。 - BuvinJ
作为一个折中方案,您可以将这些建议合并为 SPECIAL_CHARS = '_-',然后使用 string.digits + string.ascii_letters + SPECIAL_CHARS - BuvinJ
这只是一个合理的建议,除非我们在进行代码高尔夫比赛。在键盘上“走动”,以按顺序输入52个字母,比导入一个包来使用一个或两个对象要花费更长的时间。而且这还不包括双重检查你是否已经正确地输入了所有内容的时间。这只是关于良好实践的问题。 - Reti43
我明白你的意思!我的真正观点是极度灵活,以防您想要更具体地设置字符集。 - BuvinJ

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接