2012-06-25 8 views
22

cuestión muy sencilla que no puedo encontrar ninguna "buena" respuesta por mí mismo:¿Hay alguna forma más agradable de escribir sucesivas declaraciones "o" en Python?

Digamos que tengo la siguiente condición:

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

Cuando el número de or declaración puede ser bastante más tiempo dependiendo de la situación.

¿Existe alguna manera "más agradable" (más Pythonic) de escribir esto, sin sacrificar el rendimiento?

Si se piensa en usar any() pero lleva una lista de elementos booleanos, entonces tendría que crear esa lista primero (dejando de lado la evaluación de cortocircuitos en el proceso), así que supongo que es menos eficiente.

Muchas gracias.

Respuesta

30

Una forma podría ser

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

Lo que es iterar sobre una tupla, que se basa en la compilación de la función, por lo que no debe ser inferior a su versión original.

Si tiene miedo de que la tupla llegará a ser demasiado larga, se puede hacer

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

Gracias. Pero, ¿esa técnica no previene la optimización de cortocircuitos? – ereOn

+2

Es un generador, no una lista. – johv

+9

No. '(s en mystring para s en 'foo', 'bar', 'hola')' es una expresión de generador, lo que significa que no se calcula instantáneamente como un todo, solo a pedido. 'any()' detiene la iteración al ver el primer valor verdadero, por lo que el resto nunca se comprobará. Lea sobre las expresiones del generador. – Kos

7

Esto suena como un trabajo para una expresión regular.

import re 

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

Debe ser más rápido, también. Especialmente si compila la expresión regular antes de tiempo.

Si está generando la expresión regular automáticamente, puede usar re.escape() para asegurarse de que ningún carácter especial rompa su expresión regular. Por ejemplo, si words es una lista de cadenas que desea buscar, puede generar su patrón de esta manera:

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

También debe tener en cuenta que si usted tiene m palabras y la cadena tiene n caracteres, el original el código tiene O(n*m) complejidad, mientras que la expresión regular tiene O(n) complejidad. Aunque las expresiones regulares de Python no son realmente expresiones teóricas comp-sci regulares, y no son siempreO(n) complejidad, en este caso simple sí lo son.

+4

Pero debe tener cuidado si alguna de las "palabras" que está buscando contiene caracteres especiales de expresiones regulares –

+0

@gnibbler: verdadero. Por el contrario, es posible que pueda escribir menos código usando la coincidencia de patrones. Si está haciendo algo así como generar automáticamente la expresión regular, podría usar 're.escape()'. – cha0site

+0

De hecho, puede, debe agregar eso a su respuesta –

2

Dado que procesa palabra por palabra contra mystring, seguramente se puede usar mystring como un conjunto. Luego tomar la intersección entre el conjunto que contiene las palabras mystring y los grupos destinatarios de las palabras:

In [370]: mystring=set(['foobar','barfoo','foo']) 

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

Su lógica 'o' son los miembros de la intersección de los dos conjuntos.

El uso de un conjunto también es más rápido. Éstos son sincronización relativa frente a un generador y expresión regular:

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%  -- 

lo tanto, un generador y la operación in es 19x más rápido que una expresión regular y una intersección de conjuntos es 21x más rápido que una expresión regular y un 11% más rápido que un generador.

Aquí está el código que genera el tiempo:

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)   
+0

¿Cómo es diferente de la respuesta aceptada? La única diferencia es que usas un 'conjunto 'en su lugar de una 'tupla', de lo contrario es exactamente lo mismo. – cha0site

+0

@ cha0site: La respuesta aceptada también propone una función para una lista grande. Creo que un conjunto es la mejor manera de hacerlo.Esto también propone dos conjuntos, sin el uso de 'any' –

2

Si usted tiene una lista conocida de artículos para comprobar en contra, también se puede escribir como

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

El usuario no puede obtener la beneficios de garantizar el orden de comparación (no creo que Python deba verificar los elementos de la lista de izquierda a derecha), pero eso es solo un problema si sabes que 'foo' es mucho más probable que 'bar'.

+0

. No es exactamente lo mismo. La condición en la pregunta también sería cierta cuando 'mystring = 'hello world''. Este no lo haría – Izkata

+0

Buen punto, gracias, como dices, no es exactamente lo mismo, y tal vez no se ajuste bien al problema específico. – kimvanwyk

Cuestiones relacionadas