Есть ли лучший способ писать последовательные "или" утверждения в Python?
Простой вопрос, на который я не могу найти ни одного "приятного" ответа:
Скажем, у меня есть следующее условие:
if 'foo' in mystring or 'bar' in mystring or 'hello' in mystring:
# Do something
pass
Где число операторов or
может быть довольно большим в зависимости от ситуации.
Есть ли более приятный (более питонический) способ написания этого, не жертвуя производительностью?
Если вы думаете об использовании any()
, но он берет список логических элементов, поэтому мне придется сначала собрать этот список (отказ от короткого замыкания в процессе), поэтому я думаю, что он менее эффективен.
Большое спасибо.
Ответы
Ответ 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" является более вероятным чем "бар".