【问题标题】:What is the most efficient way in Python to convert a string to all lowercase stripping out all non-ascii alpha characters?Python中将字符串转换为全小写以去除所有非ASCII字母字符的最有效方法是什么?
【发布时间】:2009-03-12 14:35:42
【问题描述】:

我有一个需要在 Python 中执行的简单任务,即将字符串转换为全小写并去除所有非 ascii 非字母字符。

例如:

"This is a Test" -> "thisisatest"
"A235th@#$&( er Ra{}|?>ndom" -> "atherrandom"

我有一个简单的功能来做到这一点:

import string
import sys

def strip_string_to_lowercase(s):
    tmpStr = s.lower().strip()
    retStrList = []
    for x in tmpStr:
        if x in string.ascii_lowercase:
            retStrList.append(x)

    return ''.join(retStrList)

但我不禁想到有一种更有效或更优雅的方式。

谢谢!


编辑:

感谢所有回答的人。我学习了很多 Python,在某些情况下还重新学习了。

【问题讨论】:

  • 您的代码不会自动剥离
  • @SilentGhost 我认为
  • @SilentGhost:我有一个大写的“o”,看起来很像零。可能是一个不好的例子。我会编辑它。
  • @Dana:是的,

标签: python string


【解决方案1】:

另一个解决方案(不是pythonic,但非常快)是使用string.translate - 尽管请注意这不适用于unicode。还值得注意的是,您可以通过将字符移动到一个集合中来加速Dana's code(通过哈希查找,而不是每次都执行线性搜索)。以下是我得到的各种解决方案的时间安排:

import string, re, timeit

# Precomputed values (for str_join_set and translate)

letter_set = frozenset(string.ascii_lowercase + string.ascii_uppercase)
tab = string.maketrans(string.ascii_lowercase + string.ascii_uppercase,
                       string.ascii_lowercase * 2)
deletions = ''.join(ch for ch in map(chr,range(256)) if ch not in letter_set)

s="A235th@#$&( er Ra{}|?>ndom"

# From unwind's filter approach
def test_filter(s):
    return filter(lambda x: x in string.ascii_lowercase, s.lower())

# using set instead (and contains)
def test_filter_set(s):
    return filter(letter_set.__contains__, s).lower()

# Tomalak's solution
def test_regex(s):
    return re.sub('[^a-z]', '', s.lower())

# Dana's
def test_str_join(s):
    return ''.join(c for c in s.lower() if c in string.ascii_lowercase)

# Modified to use a set.
def test_str_join_set(s):
    return ''.join(c for c in s.lower() if c in letter_set)

# Translate approach.
def test_translate(s):
    return string.translate(s, tab, deletions)


for test in sorted(globals()):
    if test.startswith("test_"):
        assert globals()[test](s)=='atherrandom'
        print "%30s : %s" % (test, timeit.Timer("f(s)", 
              "from __main__ import %s as f, s" % test).timeit(200000))

这给了我:

               test_filter : 2.57138351271
           test_filter_set : 0.981806765698
                test_regex : 3.10069885233
             test_str_join : 2.87172979743
         test_str_join_set : 2.43197956381
            test_translate : 0.335367566218

[编辑] 也更新了过滤器解决方案。 (请注意,在这里使用 set.__contains__ 会有很大的不同,因为它避免了对 lambda 进行额外的函数调用。

【讨论】:

  • 计时码是一个很好的补充。请参阅下面我在过滤器解决方案中添加的答案。
  • 糟糕——错过了。我现在也添加了过滤器解决方案。
  • 接受这个,因为它很全面。它还包含带有一套解决方案的过滤器,这对我来说是速度和优雅的最佳组合。
  • 非常好,翻译表仍然是我的最爱。
  • test_filter_set = lambda s: filter(letter_set.__contains__, s).lower() 稍快
【解决方案2】:
>>> filter(str.isalpha, "This is a Test").lower()
'thisisatest'
>>> filter(str.isalpha, "A235th@#$&( er Ra{}|?>ndom").lower()
'atherrandom'

【讨论】:

  • str.isalpha 取决于区域设置。它可能会留下非 ASCII 字符。
【解决方案3】:

运行时效率不是特别高,但对于可怜、疲惫的编码员眼睛来说肯定更好:

def strip_string_and_lowercase(s):
    return ''.join(c for c in s.lower() if c in string.ascii_lowercase)

【讨论】:

  • 事实上它比我的运行时效率更高,更不用说 Tomalak 的了
  • @SilentGhost -- 哇!我是天才:P
  • 这是相当明显的解决方案:)
  • Join 在 python 中看起来非常高效。大多数涉及字符串连接的任务通过连接更快。
【解决方案4】:

我愿意:

  • 小写字符串
  • 将所有[^a-z] 替换为""

这样:

def strip_string_to_lowercase():
  nonascii = re.compile('[^a-z]')
  return lambda s: nonascii.sub('', s.lower().strip())

编辑:原来的版本(下)确实很慢,尽管通过将其转换为闭包(上)可以获得一些性能。

def strip_string_to_lowercase(s):
  return re.sub('[^a-z]', '', s.lower().strip())

我对字符串 100,000 次迭代的性能测量

"A235th@#$&( er Ra{}|?>ndom"

透露:

为了测试,我没有print结果。

【讨论】:

  • strip() 并不是特别需要,因为 strip() 将删除的任何内容都会被 '[^a-z]' 删除:o)
  • 是否有两个循环,它比我尝试过的任何其他方式都快,包括 string.translate。
  • @George Shore:你是对的。不过,我不希望它有很大的不同(性能方面)。我把它留在里面了,所以当你查看代码时,很明显结果会立即被剥离——否则这将是一个不那么明显的“副作用”。
  • @bobince:这是发布的最慢的解决方案
  • @Tomalok:你已经把 re.sub 的参数颠倒过来了——你正在对空字符串进行操作。
【解决方案5】:

Python 2.x translate 方法

转换为小写并过滤非ascii非字母字符:

from string import ascii_letters, ascii_lowercase, maketrans

table = maketrans(ascii_letters, ascii_lowercase*2)
deletechars = ''.join(set(maketrans('','')) - set(ascii_letters))

print "A235th@#$&( er Ra{}|?>ndom".translate(table, deletechars)
# -> 'atherrandom'

Python 3 translate 方法

过滤非ASCII:

ascii_bytes = "A235th@#$&(٠٫٢٥ er Ra{}|?>ndom".encode('ascii', 'ignore')

使用bytes.translate() 转换为小写并删除非字母字节:

from string import ascii_letters, ascii_lowercase

alpha, lower = [s.encode('ascii') for s in [ascii_letters, ascii_lowercase]]
table = bytes.maketrans(alpha, lower*2)           # convert to lowercase
deletebytes = bytes(set(range(256)) - set(alpha)) # delete nonalpha

print(ascii_bytes.translate(table, deletebytes))
# -> b'atherrandom'

【讨论】:

    【解决方案6】:

    类似于@Dana,但我认为这听起来像是一项过滤工作,并且应该在代码中可见。也无需显式调用join():

    def strip_string_to_lowercase(s):
      return filter(lambda x: x in string.ascii_lowercase, s.lower())
    

    【讨论】:

    • 这会错过大写的“A”和“R”,但是将最后一个 s 更改为 s.lower() 应该可以解决这个问题。感谢您的提示。
    • 糟糕,抱歉,现已修复。谢谢,很高兴你喜欢它,错误和所有。 :)
    • 这似乎是 OP 询问的“最有效的方式”。 +1
    • 似乎有一个优化潜伏在不使用 lambda 中(请参阅@Brian 的回答)。太好了!
    【解决方案7】:

    我在 Brian 的代码中添加了过滤器解决方案:

    import string, re, timeit
    
    # Precomputed values (for str_join_set and translate)
    
    letter_set = frozenset(string.ascii_lowercase + string.ascii_uppercase)
    tab = string.maketrans(string.ascii_lowercase + string.ascii_uppercase,
                           string.ascii_lowercase * 2)
    deletions = ''.join(ch for ch in map(chr,range(256)) if ch not in letter_set)
    
    s="A235th@#$&( er Ra{}|?>ndom"
    
    def test_original(s):
        tmpStr = s.lower().strip()
        retStrList = []
        for x in tmpStr:
            if x in string.ascii_lowercase:
                retStrList.append(x)
    
        return ''.join(retStrList)
    
    
    def test_regex(s):
        return re.sub('[^a-z]', '', s.lower())
    
    def test_regex_closure(s):
      nonascii = re.compile('[^a-z]')
      def replacer(s):
        return nonascii.sub('', s.lower().strip())
      return replacer(s)
    
    
    def test_str_join(s):
        return ''.join(c for c in s.lower() if c in string.ascii_lowercase)
    
    def test_str_join_set(s):
        return ''.join(c for c in s.lower() if c in letter_set)
    
    def test_filter_set(s):
        return filter(letter_set.__contains__, s.lower())
    
    def test_filter_isalpha(s):
        return filter(str.isalpha, s).lower()
    
    def test_filter_lambda(s):
        return filter(lambda x: x in string.ascii_lowercase, s.lower())
    
    def test_translate(s):
        return string.translate(s, tab, deletions)
    
    for test in sorted(globals()):
        if test.startswith("test_"):
            print "%30s : %s" % (test, timeit.Timer("f(s)", 
                  "from __main__ import %s as f, s" % test).timeit(200000))
    

    这给了我:

           test_filter_isalpha : 1.31981746283
            test_filter_lambda : 2.23935583992
               test_filter_set : 0.76511679557
                 test_original : 2.13079176264
                    test_regex : 2.44295629752
            test_regex_closure : 2.65205913042
                 test_str_join : 2.25571266739
             test_str_join_set : 1.75565888961
                test_translate : 0.269259640541
    

    似乎 isalpha 使用了与 set 算法类似的算法,至少在 O() 方面。


    编辑: 添加了过滤器集,将过滤器功能重命名为更清晰一点。

    【讨论】:

      【解决方案8】:

      这是列表理解的典型应用:

      import string
      s = "O235th@#$&( er Ra{}|?<ndom"
      print ''.join(c for c in s.lower() if c in string.ascii_lowercase)
      

      它不会像您的示例中那样过滤掉“

      【讨论】:

        【解决方案9】:
        >>> import string
        >>> a = "O235th@#$&( er Ra{}|?&lt;ndom"
        >>> ''.join(i for i in a.lower() if i in string.ascii_lowercase)
        'otheraltndom'
        

        基本上和你做的一样。

        【讨论】:

        • Yours 跳过大写的 O 和 R,sg,因为在调用 lower() 之前,您正在测试 ascii_lowercase 中的成员身份
        【解决方案10】:

        Python 2.x:

        import string
        valid_chars= string.ascii_lowercase + string.ascii_uppercase
        
        def only_lower_ascii_alpha(text):
            return filter(valid_chars.__contains__, text).lower()
        

        适用于 str 或 unicode 参数。

        >>> only_lower_ascii_alpha("Hello there 123456!")
        'hellothere'
        >>> only_lower_ascii_alpha(u"435 café")
        u'caf'
        

        【讨论】:

          【解决方案11】:

          我个人会使用正则表达式,然后将最终字符串转换为小写。我不知道如何用 Python 编写它,但基本思想是:

          1. 删除字符串中与不区分大小写的正则表达式“\w”不匹配的字符
          2. 将字符串转换为小写

          反之亦然。

          【讨论】:

            猜你喜欢
            • 2020-10-24
            • 1970-01-01
            • 2016-11-19
            • 2014-05-04
            • 2020-12-05
            • 1970-01-01
            • 2013-09-17
            • 1970-01-01
            相关资源
            最近更新 更多