¿Python tiene un método de subcadena 'contiene' de cadena?
Estoy buscando un método string.contains
o string.indexof
en Python.
Quiero hacer:
if not somestring.contains("blah"):
continue
Utilice el in
operador :
if "blah" not in somestring:
continue
Nota: Esto distingue entre mayúsculas y minúsculas.
Si es solo una búsqueda de subcadenas, puedes usar string.find("substring")
.
Tienes que tener un poco de cuidado con find
, index
y in
, ya que son búsquedas de subcadenas. En otras palabras, esto:
s = "This be a string"
if s.find("is") == -1:
print("No 'is' here!")
else:
print("Found 'is' in the string.")
Se imprimiría Found 'is' in the string.
de manera similar, if "is" in s:
se evaluaría como True
. Esto puede ser o no lo que quieres.
¿Python tiene una cadena que contiene un método de subcadena?
El 99% de los casos de uso se cubrirán utilizando la palabra clave, in
que devuelve True
o False
:
'substring' in any_string
Para el caso de uso de obtener el índice, utilice str.find
(que devuelve -1 en caso de error y tiene argumentos posicionales opcionales):
start = 0
stop = len(any_string)
any_string.find('substring', start, stop)
o str.index
(me gusta find
pero genera ValueError en caso de falla):
start = 100
end = 1000
any_string.index('substring', start, end)
Explicación
Utilice el in
operador de comparación porque
- el idioma pretende su uso, y
- otros programadores de Python esperarán que lo uses.
>>> 'foo' in '**foo**'
True
Lo contrario (complemento), que pedía la pregunta original, es not in
:
>>> 'foo' not in '**foo**' # returns False
False
Esto es semánticamente igual not 'foo' in '**foo**'
, pero es mucho más legible y está previsto explícitamente en el lenguaje como una mejora de la legibilidad.
Evitar el uso de__contains__
El método "contiene" implementa el comportamiento de in
. Este ejemplo,
str.__contains__('**foo**', 'foo')
devoluciones True
. También puedes llamar a esta función desde la instancia de la supercadena:
'**foo**'.__contains__('foo')
Pero no lo hagas. Los métodos que comienzan con guiones bajos se consideran semánticamente no públicos. La única razón para usar esto es al implementar o ampliar la funcionalidad in
y not in
(por ejemplo, si se crea una subclase str
):
class NoisyString(str):
def __contains__(self, other):
print(f'testing if "{other}" in "{self}"')
return super(NoisyString, self).__contains__(other)
ns = NoisyString('a string with a substring inside')
y ahora:
>>> 'substring' in ns
testing if "substring" in "a string with a substring inside"
True
No lo use find
y index
pruebe si "contiene"
No utilice los siguientes métodos de cadena para probar "contiene":
>>> '**foo**'.index('foo')
2
>>> '**foo**'.find('foo')
2
>>> '**oo**'.find('foo')
-1
>>> '**oo**'.index('foo')
Traceback (most recent call last):
File "<pyshell#40>", line 1, in <module>
'**oo**'.index('foo')
ValueError: substring not found
Es posible que otros lenguajes no tengan métodos para probar directamente las subcadenas, por lo que tendría que usar este tipo de métodos, pero con Python, es mucho más eficiente usar el in
operador de comparación.
Además, estos no son reemplazos directos de in
. Es posible que tengas que manejar la excepción o -1
los casos, y si regresan 0
(porque encontraron la subcadena al principio), la interpretación booleana es False
en lugar de True
.
Si realmente not any_string.startswith(substring)
lo dices en serio, dilo.
Comparaciones de rendimiento
Podemos comparar varias formas de lograr el mismo objetivo.
import timeit
def in_(s, other):
return other in s
def contains(s, other):
return s.__contains__(other)
def find(s, other):
return s.find(other) != -1
def index(s, other):
try:
s.index(other)
except ValueError:
return False
else:
return True
perf_dict = {
'in:True': min(timeit.repeat(lambda: in_('superstring', 'str'))),
'in:False': min(timeit.repeat(lambda: in_('superstring', 'not'))),
'__contains__:True': min(timeit.repeat(lambda: contains('superstring', 'str'))),
'__contains__:False': min(timeit.repeat(lambda: contains('superstring', 'not'))),
'find:True': min(timeit.repeat(lambda: find('superstring', 'str'))),
'find:False': min(timeit.repeat(lambda: find('superstring', 'not'))),
'index:True': min(timeit.repeat(lambda: index('superstring', 'str'))),
'index:False': min(timeit.repeat(lambda: index('superstring', 'not'))),
}
Y ahora vemos que usarlo in
es mucho más rápido que los demás. Menos tiempo para hacer una operación equivalente es mejor:
>>> perf_dict
{'in:True': 0.16450627865128808,
'in:False': 0.1609668098178645,
'__contains__:True': 0.24355481654697542,
'__contains__:False': 0.24382793854783813,
'find:True': 0.3067379407923454,
'find:False': 0.29860888058124146,
'index:True': 0.29647137792585454,
'index:False': 0.5502287584545229}
¿Cómo puede in
ser más rápido que __contains__
si se in
usa __contains__
?
Esta es una buena pregunta de seguimiento.
Desmontemos funciones con los métodos de interés:
>>> from dis import dis
>>> dis(lambda: 'a' in 'b')
1 0 LOAD_CONST 1 ('a')
2 LOAD_CONST 2 ('b')
4 COMPARE_OP 6 (in)
6 RETURN_VALUE
>>> dis(lambda: 'b'.__contains__('a'))
1 0 LOAD_CONST 1 ('b')
2 LOAD_METHOD 0 (__contains__)
4 LOAD_CONST 2 ('a')
6 CALL_METHOD 1
8 RETURN_VALUE
entonces vemos que el .__contains__
método debe buscarse por separado y luego llamarse desde la máquina virtual Python; esto debería explicar adecuadamente la diferencia.
if needle in haystack:
es el uso normal, como dice @Michael: depende del in
operador, es más legible y más rápido que una llamada a un método.
Si realmente necesita un método en lugar de un operador (por ejemplo, ¿hacer algo extraño key=
para un tipo muy peculiar...?), ese sería 'haystack'.__contains__
. Pero dado que su ejemplo es para usar en if
, supongo que realmente no quiere decir lo que dice ;-). No es una buena forma (ni legible ni eficiente) usar métodos especiales directamente; en cambio, están destinados a usarse a través de los operadores y las funciones integradas que les delega.