Подтвердить что ты не робот

Есть ли лучший способ писать последовательные "или" утверждения в Python?

Простой вопрос, на который я не могу найти ни одного "приятного" ответа:

Скажем, у меня есть следующее условие:

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

Где число операторов or может быть довольно большим в зависимости от ситуации.

Есть ли более приятный (более питонический) способ написания этого, не жертвуя производительностью?

Если вы думаете об использовании any(), но он берет список логических элементов, поэтому мне придется сначала собрать этот список (отказ от короткого замыкания в процессе), поэтому я думаю, что он менее эффективен.

Большое спасибо.

4b9b3361

Ответ 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

Ответ 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), в этом простом случае они есть.

Ответ 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)        

Ответ 4

Если у вас есть известный список элементов для проверки, вы также можете записать его как

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

Вы не можете воспользоваться преимуществами обеспечения порядка сравнения (я не думаю, что Python должен проверять элементы списка слева направо), но это только проблема, если вы знаете, что "foo" является более вероятным чем "бар".