Python a-t-il une méthode de sous-chaîne «contient»?


3599

Je recherche une méthode string.containsou string.indexofen Python.

Je veux faire:

if not somestring.contains("blah"):
   continue

Réponses:


6266

Vous pouvez utiliser l' inopérateur :

if "blah" not in somestring: 
    continue

233
Sous le capot, Python utilisera __contains__(self, item), __iter__(self)et __getitem__(self, key)dans cet ordre pour déterminer si un élément se trouve dans un contenu donné. Implémentez au moins une de ces méthodes pour rendre indisponible votre type personnalisé.
BallpointBen

28
Assurez-vous simplement que quelque chose ne sera pas None. Sinon, vous obtenez unTypeError: argument of type 'NoneType' is not iterable
Big Pumpkin

6
FWIW, c'est la manière idiomatique d'atteindre cet objectif.
Trenton

7
Pour les chaînes, l' inopérateur Python utilise-t-il l'algorithme Rabin-Carp?
Sam Chats

4
@SamChats voir stackoverflow.com/questions/18139660/… pour les détails d'implémentation (en CPython; afaik la spécification du langage ne requiert aucun algorithme particulier ici).
Christoph Burschka

667

Si c'est juste une recherche de sous-chaîne que vous pouvez utiliser string.find("substring").

Vous ne devez être un peu prudent avec find, indexet insi, comme ils sont sousChaîne recherches. En d'autres termes, ceci:

s = "This be a string"
if s.find("is") == -1:
    print("No 'is' here!")
else:
    print("Found 'is' in the string.")

Il imprimerait de la Found 'is' in the string.même manière, if "is" in s:évaluerait True. Cela peut ou non être ce que vous voulez.


78
+1 pour mettre en évidence les pièges impliqués dans les recherches de sous-chaîne. la solution évidente est celle if ' is ' in s:qui reviendra Falsecomme prévu (probablement).
aaronasterling

95
@aaronasterling C'est évident, mais pas tout à fait correct. Et si vous avez une ponctuation ou si c'est au début ou à la fin? Et la capitalisation? Mieux serait une recherche de regex insensible à la casse \bis\b(limites de mots).
Bob

2
@JamieBull Encore une fois, vous devez considérer si vous souhaitez inclure la ponctuation comme délimiteur d'un mot. Le fractionnement aurait en grande partie le même effet que la solution naïve de vérification ' is ', notamment, il n'attrapera pas This is, a comma'ou 'It is.'.
Bob

7
@JamieBull: Je doute fortement que toute séparation d'entrée réelle s.split(string.punctuation + string.whitespace)se divise même une fois; splitn'est pas comme la famille de fonctions strip/ rstrip/ lstrip, elle ne se divise que lorsqu'elle voit tous les caractères délimiteurs, contigus, dans cet ordre exact. Si vous souhaitez diviser les classes de caractères, vous revenez aux expressions régulières (à quel moment, rechercher r'\bis\b'sans fractionner est le moyen le plus simple et le plus rapide).
ShadowRanger

8
'is' not in (w.lower() for w in s.translate(string.maketrans(' ' * len(string.punctuation + string.whitespace), string.punctuation + string.whitespace)).split()- ok, remarque prise. C'est maintenant ridicule ...
Jamie Bull

190

Python a-t-il une chaîne contenant une méthode de sous-chaîne?

Oui, mais Python a un opérateur de comparaison que vous devez utiliser à la place, car le langage a l'intention de l'utiliser, et d'autres programmeurs s'attendront à ce que vous l'utilisiez. Ce mot-clé est inutilisé comme opérateur de comparaison:

>>> 'foo' in '**foo**'
True

Le contraire (complément), que la question initiale demande, est not in:

>>> 'foo' not in '**foo**' # returns False
False

C'est sémantiquement le même que not 'foo' in '**foo**'mais c'est beaucoup plus lisible et explicitement prévu dans le langage comme une amélioration de la lisibilité.

Évitez d' utiliser __contains__, findetindex

Comme promis, voici la containsméthode:

str.__contains__('**foo**', 'foo')

retourne True. Vous pouvez également appeler cette fonction à partir de l'instance de la super chaîne:

'**foo**'.__contains__('foo')

Mais non. Les méthodes commençant par des traits de soulignement sont considérées comme sémantiquement privées. La seule raison d'utiliser cela est lors de l'extension des fonctionnalités inet not in(par exemple, si le sous-classement str):

class NoisyString(str):
    def __contains__(self, other):
        print('testing if "{0}" in "{1}"'.format(other, self))
        return super(NoisyString, self).__contains__(other)

ns = NoisyString('a string with a substring inside')

et maintenant:

>>> 'substring' in ns
testing if "substring" in "a string with a substring inside"
True

Évitez également les méthodes de chaîne suivantes:

>>> '**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

D'autres langages peuvent ne pas avoir de méthodes pour tester directement les sous-chaînes, et vous devrez donc utiliser ces types de méthodes, mais avec Python, il est beaucoup plus efficace d'utiliser l' inopérateur de comparaison.

Comparaisons de performances

Nous pouvons comparer différentes façons d'atteindre le même objectif.

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'))),
}

Et maintenant, nous voyons que l'utilisation inest beaucoup plus rapide que les autres. Moins de temps pour faire une opération équivalente, c'est mieux:

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

6
Pourquoi faut-il éviter str.indexet str.find? Sinon, comment suggéreriez-vous que quelqu'un trouve l'index d'une sous-chaîne au lieu de simplement savoir s'il existe ou non? (ou avez - vous évité moyen de les utiliser à la place de contient - alors ne pas utiliser au s.find(ss) != -1lieu de ss in s?)
coderforlife

3
Précisément, bien que l'intention derrière l'utilisation de ces méthodes puisse être mieux abordée par une utilisation élégante du remodule. Je n'ai pas encore trouvé d'utilisation pour str.index ou str.find moi-même dans aucun code que j'ai écrit pour le moment.
Aaron Hall

Veuillez également étendre votre réponse aux conseils contre l'utilisation str.count( string.count(something) != 0). frisson
CS95

Comment fonctionne la operatorversion du module ?
jpmc26 du

@ jpmc26 c'est la même chose que in_ci-dessus - mais avec un stackframe autour, donc c'est plus lent que ça: github.com/python/cpython/blob/3.7/Lib/operator.py#L153
Aaron Hall

175

if needle in haystack:est l'utilisation normale, comme le dit @Michael - elle repose sur l' inopérateur, plus lisible et plus rapide qu'un appel de méthode.

Si vous avez vraiment besoin d'une méthode au lieu d'un opérateur (par exemple, pour faire un peu bizarre key=pour un type très particulier ...?), Ce serait 'haystack'.__contains__. Mais comme votre exemple est destiné à être utilisé dans un if, je suppose que vous ne pensez pas vraiment ce que vous dites ;-). Ce n'est pas une bonne forme (ni lisible, ni efficace) d'utiliser directement des méthodes spéciales - elles sont plutôt destinées à être utilisées à travers les opérateurs et les commandes intégrées qui leur sont déléguées.


55

in Chaînes et listes Python

Voici quelques exemples utiles qui parlent d'eux-mêmes concernant la inméthode:

"foo" in "foobar"
True

"foo" in "Foobar"
False

"foo" in "Foobar".lower()
True

"foo".capitalize() in "Foobar"
True

"foo" in ["bar", "foo", "foobar"]
True

"foo" in ["fo", "o", "foobar"]
False

["foo" in a for a in ["fo", "o", "foobar"]]
[False, False, True]

Caveat. Les listes sont des itérables, et la inméthode agit sur les itérables, pas seulement sur les chaînes.


1
La liste itérable pourrait-elle être inversée pour rechercher l'une des listes dans une seule chaîne? Ex ["bar", "foo", "foobar"] in "foof":?
CaffeinatedCoder

1
@CaffeinatedCoder, non, cela nécessite une itération imbriquée. Il est préférable de joindre la liste avec les tuyaux "|" .join (["bar", "foo", "foobar"]) et d'en compiler une expression
régulière

2
any ([x in "foof" for x in ["bar", "foo", "foobar"]])
Izaak Weiss

1
@IzaakWeiss Votre one liner fonctionne, mais il n'est pas très lisible et il fait une itération imbriquée. Je
déconseille de

1
@ PiyushS.Wanare qu'entendez-vous par complexité? Le "WTF / min" est beaucoup plus élevé avec regex.
firelynx

42

Si vous êtes satisfait "blah" in somestringmais souhaitez que ce soit un appel de fonction / méthode, vous pouvez probablement le faire

import operator

if not operator.contains(somestring, "blah"):
    continue

Tous les opérateurs en Python peuvent être plus ou moins trouvés dans le module opérateur, y compris in.


40

Donc, apparemment, il n'y a rien de similaire pour la comparaison vectorielle. Une façon évidente de le faire en Python serait:

names = ['bob', 'john', 'mike']
any(st in 'bob and john' for st in names) 
>> True

any(st in 'mary and jane' for st in names) 
>> False

1
En effet, il existe une multitude de façons de créer un produit à partir de variables atomiques. Vous pouvez les bourrer dans un tuple, une liste (qui sont des formes de produits cartésiens et qui viennent avec un ordre implicite), ou elles peuvent être nommées propriétés d'une classe (pas d'ordre a priori) ou des valeurs de dictionnaire, ou elles peuvent être des fichiers dans un répertoire, ou autre chose. Chaque fois que vous pouvez identifier de manière unique (iter ou getitem) quelque chose dans un «conteneur» ou un «contexte», vous pouvez voir ce «conteneur» comme une sorte de vecteur et définir des opérations binaires dessus. en.wikipedia.org/wiki/…
Niriel

inNe vaut rien qui ne devrait pas être utilisé avec des listes car il effectue un balayage linéaire des éléments et est lent à comparer. Utilisez un ensemble à la place, surtout si les tests d'appartenance doivent être effectués à plusieurs reprises.
cs95

22

Vous pouvez utiliser y.count().

Il renverra la valeur entière du nombre de fois qu'une sous-chaîne apparaît dans une chaîne.

Par exemple:

string.count("bah") >> 0
string.count("Hello") >> 1

8
compter une chaîne coûte cher quand on veut juste vérifier si elle est là ...
Jean-François Fabre

3
méthodes qui existent dans le post original de 2010 donc j'ai fini par les éditer, avec le consensus de la communauté (voir meta post meta.stackoverflow.com/questions/385063/… )
Jean-François Fabre

17
non. Mon point est "pourquoi répondre exactement la même chose que d'autres l'ont fait il y a 9 ans"?
Jean-François Fabre

10
car je modère le site ... J'ai posé la question sur meta meta.stackoverflow.com/questions/385063/…
Jean-François Fabre

2
Si vous avez le pouvoir de le supprimer, supprimez-le, sinon faites ce que vous devez et continuez. OMI, cette réponse ajoute de la valeur, ce qui se reflète dans les votes positifs des utilisateurs.
Brandon Bailey

20

Voici votre réponse:

if "insert_char_or_string_here" in "insert_string_to_search_here":
    #DOSTUFF

Pour vérifier si elle est fausse:

if not "insert_char_or_string_here" in "insert_string_to_search_here":
    #DOSTUFF

OU:

if "insert_char_or_string_here" not in "insert_string_to_search_here":
    #DOSTUFF

8

Vous pouvez utiliser des expressions régulières pour obtenir les occurrences:

>>> import re
>>> print(re.findall(r'( |t)', to_search_in)) # searches for t or space
['t', ' ', 't', ' ', ' ']
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.