在列表推导式中如何使用多个条件语句?

5

我正在做一些字符串解析工作,如果字符是字母,则返回1,如果字符是数字,则返回2,如果字符是其他内容,则跳过。

通常我会使用for循环并像这样添加:


```python result = [] for char in string: if char.isalpha(): result.append(1) elif char.isdigit(): result.append(2) ```
string = 'hello123...'
values = []
for char in string:
    if char.isalpha():
        values.append(1)
    elif char.isdigit():
        values.append(2)

该函数返回

[1, 1, 1, 1, 1, 2, 2, 2]

正如预期的那样。根据https://dev59.com/cF0a5IYBdhLWcg3whI4b#30245465,使用列表推导式可以更快地执行。因此,我尝试了以下代码:

values = [1 if char.isalpha else 2 if char.isdigit for char in string]

然而,这给我带来了语法错误,因为需要一个 'else'。
File "C:/Users/test3.py", line 12
values = [1 if char.isalpha else 2 if char.isdigit for char in string]
                                                     ^
SyntaxError: invalid syntax

如果字符不是字母数字,我希望它不添加任何内容。我的代码有什么问题?

我可能需要执行此函数数十亿次,因此如果有更好的方法来完成此操作,任何提高效率的方法都受欢迎。


仅代表个人意见,我认为这会影响你程序的可读性。列表推导式非常紧凑和强大,但如果滥用可能会变得有点晦涩。另外,alpha是一个方法,但第二个语句是一个属性。你能提供一个可重现的例子吗? - JAponte
1
试图使用列表推导式,因为它“更快”可能是一个坏主意。在一个什么也不做,只是将一个列表复制到另一个列表的空循环中,列表推导式大约快20%(但只是调用list更快)。要做的实际工作越多,差异就越小。而且在Python中很少有需要这样做的代码——通常已经足够快了,当它不够快时,通常需要考虑更大的变化,比如在PyPy中运行或编写Cython扩展或使用NumPy之类的东西,然后再担心10%的问题。 - abarnert
@NidhinSajeev 这个问题的关键在于 OP 没有最终的 else - AChampion
2
相反,许多时候,如果保持在合理的复杂性范围内,理解起来更易读。只要那个代码行是经过深思熟虑的,很多人不喜欢阅读五行代码,而只需要一行就足够了。 - Amadan
@smci 任何单独的 list.append 的最坏情况是 O(N),但是重复调用它是平摊的 O(1)。这是列表呈几何增长的微不足道的结果,就像几乎每种语言/库中的类似动态数组一样。 - abarnert
显示剩余10条评论
3个回答

6

如果您不想考虑特定的元素,您应该在推导式的末尾包含一个条件作为保护

[1 if char.isalpha() else 2 for char in string if char.isdigit() or char.isalpha()]

在末尾使用if char.isdigit() or char.isalpha()可以消除不符合这些条件的元素。尽管这个一行代码看起来很巧妙,但为了可读性,建议将至少翻译部分(可能还有条件语句)分解为单独的函数。

太棒了,谢谢!我忽略了另一种处理这个问题的方法。 - Cyon
3
如果你真的想将这个作为优化方式使用,那么你就需要用 timeit 对比一下你实际的代码。因为额外一个函数调用和一个条件判断可能会比内置于列表推导式中的追加优化带来更大的开销。 - abarnert

3
其他答案已经很好地解释了如何更改列表推导式以正确处理非字母数字情况。我想解决的是一个假设:列表推导式总是比传统循环快得多。
这通常是真的,但很多时候你可以修改循环来弥补大部分或全部失去的优势。特别是,在列表上查找“append”方法相对较慢,因为它涉及在字典中查找某个东西并创建绑定方法对象。您可以在循环之前一次进行查找,这样您的代码可能比其他任何版本都更快:
values = []
values_append = values.append   # cache this method lookup
for char in string:
    if char.isalpha():
        values_append(1)        # used cached method here
    elif char.isdigit():
        values_append(2)        # and here

这里是一些测试时间,使用一个一百万字符的字符串:
import random, timeit

big_str = "".join(random.choice(['a', '1', '~']) for _ in range(1000000))

def loop_cyon(string):
    values = []
    for char in string:
        if char.isalpha():
            values.append(1)
        elif char.isdigit():
            values.append(2)
    return values

def comp_silvio_mayolo(string):
    return [1 if char.isalpha() else 2 for char in string if char.isdigit() or char.isalpha()]

def comp_amadan1(string):
    return [1 if char.isalpha() else 2 for char in string if char.isalnum()]

def comp_amadan2(string):
    return list(filter(None, (1 if char.isalpha() else 2 if char.isalnum() else None for char in string)))

def loop_blckknght(string):
    values = []
    values_append = values.append
    for char in string:
        if char.isalpha():
            values_append(1)
        elif char.isdigit():
            values_append(2)
    return values

for func in [loop_cyon, comp_silvio_mayolo, comp_amadan1, comp_amadan2, loop_blckknght]:
    print(func.__name__)
    timeit.timeit(lambda: func(big_str), number=10)

我的系统输出(Windows 10 64位,Python 3.6):

loop_cyon 2.5896435911574827
comp_silvio_mayolo 2.6970998627145946
comp_amadan1 2.177768147485949
comp_amadan2 2.676028711925028
loop_blckknght 2.244682003625485

看起来最好的列表推导式仍然比我的循环代码稍微快一点,但差别不大。我肯定会说在这种情况下显式循环更清晰,而这种清晰度可能比性能差异更重要。


我不知道你可以这样使用append。谢谢! - Cyon
没错,绑定方法和函数一样,在Python中是一等对象。你可以将它们分配给变量以便稍后调用,或者对它们进行任何你想要的操作。有时将一个绑定方法传递给另一个函数是很有用的(例如在list.sort中使用一个作为“key”函数)。 - Blckknght

2

不幸的是,Python不允许你在推导式中这样做。但是,你可以这样做:

values = [1 if char.isalpha() else 2 for char in string if char.isalnum()]

(其中char.isalnum()当且仅当char.isalpha()或char.isdigit()。)
您还可以执行
values = [value for value in (1 if char.isalpha() else 2 if char.isalnum() else None for char in string) if value]

或其等效物(感谢AChampion的提醒)
values = list(filter(None, (1 if char.isalpha() else 2 if char.isalnum() else None for char in string)))

为了摆脱 None,虽然我不确定速度快慢如何(它并不建立新列表,只是使用生成器,因此减速可能会非常微不足道)。


有没有任何理由不使用 filter(None, ...) 来删除 None - AChampion
@AChampion: “没有。”我的意思是,什么都没有。 - Amadan
谢谢!知道Python确实做不到这一点,而不是我忽略了一些愚蠢的打字错误,感觉很好。 - Cyon
1
不仅仅是Python。问题在于x if z else y,就像其他语言中的z ? x : y一样,它是一个表达式,并且表达式需要返回一个值;再加上[e for x in y if z]中的e必须是一个表达式,以及推导式中的if z在语法上完全与e分开。 - Amadan

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