22

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

假设我有以下条件:

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

根据情况,语句的数量or可以相当长。

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

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

非常感谢你。

4

4 回答 4

30

一种方法可能是

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
于 2012-06-25T12:58:16.077 回答
7

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

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)复杂的,但在这个简单的情况下它们是。

于 2012-06-25T13:00:22.690 回答
2

由于您正在逐字处理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)        
于 2012-06-25T13:49:43.093 回答
2

如果您有要检查的已知项目列表,您也可以将其写为

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

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

于 2012-06-25T15:37:34.280 回答