【问题标题】:Is there any nicer way to write successive "or" statements in Python?有没有更好的方法在 Python 中编写连续的“或”语句?
【发布时间】:2012-06-26 17:39:12
【问题描述】:

我自己找不到任何“好”答案的简单问题:

假设我有以下条件:

if 'foo' in mystring or 'bar' in mystring or 'hello' in mystring:
    # Do something
    pass

or 语句的数量视情况而定。

在不牺牲性能的情况下,有没有一种“更好”(更 Pythonic)的写法?

如果考虑使用any(),但它需要一个类似布尔元素的列表,所以我必须先构建该列表(在此过程中放弃短路评估),所以我想它的效率较低。

非常感谢。

【问题讨论】:

    标签: python conditional


    【解决方案1】:

    一种方法

    if any(s in mystring for s in ('foo', 'bar', 'hello')):
        pass
    

    你迭代的东西是一个元组,它是建立在函数的编译之上的,所以它不应该逊于你的原始版本。

    如果你担心元组会变得太长,你可以这样做

    def mystringlist():
        yield 'foo'
        yield 'bar'
        yield 'hello'
    if any(s in mystring for s in mystringlist()):
        pass
    

    【讨论】:

    • 谢谢。但是这种技术不是可以防止短路优化吗?
    • 这是一个生成器,而不是一个列表。
    • 不。 (s in mystring for s in 'foo', 'bar', 'hello') 是一个生成器表达式,这意味着它不会作为一个整体立即计算,只是按需计算。 any() 在看到第一个真值时停止迭代,因此永远不会检查其余部分。阅读生成器表达式。
    • @gnibbler:我没说any 不会短路。我担心 list 构造 不会。但由于它实际上是一个生成器,这会改变一切。
    • OTOH,如果你做了any([s in mystring for s in ... ]),你就不会短路。 [] 列表推导总是创建一个完整的列表; any() 不会遍历所有内容,但仍会在 any 调用之前对其进行评估。
    【解决方案2】:

    这听起来像是一个正则表达式的工作。

    import re
    
    if re.search("(foo|bar|hello)", mystring):
        # Do something
        pass
    

    它也应该更快。特别是如果您提前编译正则表达式。

    如果您要自动生成正则表达式,您可以使用re.escape() 来确保没有特殊字符破坏您的正则表达式。例如,如果words 是您希望搜索的字符串列表,您可以像这样生成您的模式:

    pattern = "(%s)" % ("|".join(re.escape(word) for word in words), )
    

    您还应该注意,如果您有m 字并且您的字符串有n 字符,那么您的原始代码具有O(n*m) 复杂度,而正则表达式具有O(n) 复杂度。尽管 Python 正则表达式并不是真正理论上的 comp-sci 正则表达式,并且并不总是 O(n) 复杂性,但在这个简单的情况下它们是。

    【讨论】:

    • 但是如果您要查找的任何“单词”包含特殊的正则表达式字符,您必须小心
    • @gnibbler:是的。相反,您可以通过使用模式匹配来编写更少的代码。如果您正在执行诸如自动生成正则表达式之类的操作,则可以使用 re.escape()
    • 确实可以,您应该将其添加到您的答案中
    • 感谢您的建议。我的真实案例场景与这种技术不太兼容(但您显然无法猜测)。为公平而投票,因为答案是有效的并且可以帮助某人。
    • 确实更快! ideone.com/MLgCU 可能是因为正则表达式只需要遍历字符串一次。
    【解决方案3】:

    由于您正在逐字处理mystring,因此 mystring 肯定可以用作一个集合。然后只取包含mystring 中单词的集合与目标单词组之间的交集:

    In [370]: mystring=set(['foobar','barfoo','foo'])
    
    In [371]: mystring.intersection(set(['foo', 'bar', 'hello']))
    Out[371]: set(['foo'])
    

    您的逻辑“或”是两个集合的交集的成员。

    使用集合也更快。以下是相对时间与生成器和正则表达式:

    f1:  generator to test against large string 
    f2:  re to test against large string 
    f3:  set intersection of two sets of words 
    
        rate/sec      f2     f1     f3
    f2   101,333      -- -95.0% -95.5%
    f1 2,026,329 1899.7%     -- -10.1%
    f3 2,253,539 2123.9%  11.2%     --
    

    所以生成器和in 操作比正则表达式快 19 倍,集合交集比正则表达式快 21 倍,比生成器快 11%。

    这是生成计时的代码:

    import re
    
    with open('/usr/share/dict/words','r') as fin:
         set_words={word.strip() for word in fin}
    
    s_words=' '.join(set_words)
    target=set(['bar','foo','hello'])
    target_re = re.compile("(%s)" % ("|".join(re.escape(word) for word in target), ))
    
    gen_target=(word for word in ('bar','foo','hello'))
    
    def f1():
        """ generator to test against large string """        
        if any(s in s_words for s in gen_target):
            return True
    
    def f2():
        """ re to test against large string """
        if re.search(target_re, s_words):
            return True
    
    def f3():
        """ set intersection of two sets of words """
        if target.intersection(set_words):
            return True
    
    funcs=[f1,f2,f3]
    legend(funcs)
    cmpthese(funcs)        
    

    【讨论】:

    • 这与接受的答案有何不同?唯一的区别是你使用set而不是tuple,否则完全一样。
    • @cha0site:接受的答案还提出了一个大型列表的功能。我认为一组是更好的方法。这也提出了两组——不使用any
    【解决方案4】:

    如果你有一个已知的要检查的项目列表,你也可以把它写成

    if mystring in ['foo', 'bar', 'hello']:
    

    您可能无法获得确保比较顺序的好处(我认为 Python 不需要从左到右检查列表元素),但如果您知道 'foo' 的可能性更大,这只是一个问题比“酒吧”。

    【讨论】:

    • 这不是一回事。当mystring = 'hello world' 时,问题中的条件也为真。这个不会。
    • 好点,谢谢 - 正如你所说,不完全一样,也许不适合具体问题。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2022-11-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多