Czy Python ma ciąg „zawiera” metodę podciągów?

3599

Szukam metody string.containslub string.indexofjęzyka Python.

Chcę zrobić:

if not somestring.contains("blah"):
   continue
Blankman
źródło

Odpowiedzi:

6257

Możesz użyć inoperatora :

if "blah" not in somestring: 
    continue
Michał Mrożek
źródło
230
Pod maską Python użyje __contains__(self, item), __iter__(self)iw __getitem__(self, key)tej kolejności, aby ustalić, czy element leży w danym zawiera. Zaimplementuj co najmniej jedną z tych metod, aby inudostępnić niestandardowy typ.
BallpointBen,
27
Tylko upewnij się, że coś nie będzie Żaden. W przeciwnym razie dostanieszTypeError: argument of type 'NoneType' is not iterable
Big Pumpkin
5
FWIW, to idiomatyczny sposób na osiągnięcie tego celu.
Trenton
6
Czy w przypadku łańcuchów inoperator Python używa algorytmu Rabin-Carp?
Sam Chats,
3
@SamChats patrz stackoverflow.com/questions/18139660/ ... szczegóły implementacji (w CPython; afaik specyfikacja języka nie wymaga tutaj żadnego konkretnego algorytmu).
Christoph Burschka
667

Jeśli jest to tylko wyszukiwanie podciągów, możesz użyć string.find("substring").

Trzeba być trochę ostrożny z find, indexi inchoć są one podciąg wyszukiwania. Innymi słowy, to:

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

Wypisałby się Found 'is' in the string.podobnie, if "is" in s:oceniałby True. To może być lub nie być to, czego chcesz.

eldarerathis
źródło
78
+1 za wyróżnienie błędów związanych z wyszukiwaniem podciągów. oczywistym rozwiązaniem jest to, if ' is ' in s:co powróci, Falsejak się spodziewano (prawdopodobnie).
aaronasterling
94
@aaronasterling Oczywiście może być, ale nie do końca poprawny. Co zrobić, jeśli masz interpunkcję lub jest ona na początku lub na końcu? Co z dużymi literami? Lepszym byłoby wyszukiwanie wyrażeń regularnych bez rozróżniania wielkości liter \bis\b(granice słów).
Bob
2
@JamieBull Jeszcze raz musisz rozważyć, czy chcesz wstawić interpunkcję jako separator słowa. Podział miałby w dużej mierze taki sam efekt, jak naiwne rozwiązanie sprawdzania ' is ', w szczególności, że nie zostanie on złapany This is, a comma'lub 'It is.'.
Bob
7
@JamieBull: Wątpię, czy jakikolwiek prawdziwy podział danych wejściowych podzieliłby się s.split(string.punctuation + string.whitespace)choć raz; splitnie jest jak rodzina funkcji strip/ rstrip/ lstrip, dzieli się tylko wtedy, gdy widzi wszystkie znaki separatora, w sposób ciągły, w dokładnie takiej kolejności. Jeśli chcesz podzielić na klasy znaków, wrócisz do wyrażeń regularnych (w tym momencie wyszukiwanie r'\bis\b'bez podziału jest łatwiejszą i szybszą drogą).
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, punkt wzięty. To jest teraz śmieszne ...
Jamie Bull
190

Czy Python ma ciąg zawierający metodę podciągów?

Tak, ale Python ma operator porównania, którego należy użyć zamiast tego, ponieważ język zamierza go używać, a inni programiści oczekują, że będziesz go używać. To słowo kluczowe jest inużywane jako operator porównania:

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

Przeciwnie (uzupełnienie), o które prosi pierwotne pytanie, jest not in:

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

Jest to semantycznie to samo, not 'foo' in '**foo**'ale o wiele bardziej czytelne i wyraźnie przewidziane w języku jako poprawa czytelności.

Unikaj używania __contains__, findorazindex

Zgodnie z obietnicą, oto containsmetoda:

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

zwraca True. Możesz także wywołać tę funkcję z instancji superstringu:

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

Ale nie rób tego. Metody rozpoczynające się od podkreślenia są uważane za semantycznie prywatne. Jedynym powodem, dla którego można to wykorzystać, jest rozszerzenie funkcji ini not in(np. W przypadku podklasy 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')

i teraz:

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

Unikaj również następujących metod ciągu:

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

Inne języki mogą nie mieć żadnych metod bezpośredniego testowania podciągów, więc musisz użyć tego rodzaju metod, ale w Pythonie znacznie bardziej wydajne jest użycie inoperatora porównania.

Porównanie wydajności

Możemy porównać różne sposoby osiągnięcia tego samego celu.

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

A teraz widzimy, że używanie injest znacznie szybsze niż inne. Im mniej czasu na wykonanie równoważnej operacji, tym lepiej:

>>> 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}
Aaron Hall
źródło
6
Dlaczego należy unikać str.indexi str.find? Jak inaczej zasugerowałbyś komuś znalezienie indeksu podciągów zamiast tego, czy istnieje? (czy miałeś na myśli unikanie ich zamiast zawiera - więc nie używaj s.find(ss) != -1zamiast ss in s?)
coderforlife
3
Dokładnie tak, chociaż zamiar zastosowania tych metod można lepiej rozwiązać poprzez eleganckie użycie remodułu. Nie znalazłem jeszcze zastosowania dla str.index lub str.find siebie w żadnym kodzie, który napisałem.
Aaron Hall
Rozszerz swoją odpowiedź na odradzanie używania str.countrównież ( string.count(something) != 0). Dreszcz
CS95
Jak działa operatorwersja modułu ?
jpmc26,
@ jpmc26 jest taki sam jak in_powyżej - ale z ramką stosu, więc jest wolniejszy: github.com/python/cpython/blob/3.7/Lib/operator.py#L153
Aaron Hall
175

if needle in haystack:jest normalnym zastosowaniem, jak mówi @Michael - zależy od inoperatora, jest bardziej czytelny i szybszy niż wywołanie metody.

Jeśli naprawdę potrzebujesz metody zamiast operatora (np. Aby zrobić coś dziwnego key=dla bardzo osobliwego rodzaju ...?), Byłoby to możliwe 'haystack'.__contains__. Ale ponieważ twój przykład jest przeznaczony do użycia if, myślę, że tak naprawdę nie masz na myśli tego, co mówisz ;-). Nie jest dobrą formą (ani czytelną, ani wydajną) używanie specjalnych metod bezpośrednio - są one przeznaczone do użycia przez operatorów i wbudowane w nie funkcje, które je delegują.

Alex Martelli
źródło
55

in Ciągi i listy w języku Python

Oto kilka przydatnych przykładów, które mówią same za siebie w odniesieniu do inmetody:

"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]

Zastrzeżenie Listy są iterowalne, a inmetoda działa na iterowalne, a nie tylko na ciągi znaków.

firelynx
źródło
1
Czy iterowalną listę można przełączyć, aby wyszukać dowolną listę w jednym ciągu? Ex: ["bar", "foo", "foobar"] in "foof"?
CaffeinatedCoder,
1
@CaffeinatedCoder, nie, wymaga to zagnieżdżonej iteracji. Najlepiej zrobić to, łącząc listę z potokami „|” .join ([„bar”, „foo”, „foobar”]) i kompilując z niej wyrażenie regularne, a następnie dopasowując „foof”
firelynx
2
dowolna ([x w „foof” dla x w [„bar”, „foo”, „foobar”]])
Izaak Weiss
1
@IzaakWeiss Twój jeden liner działa, ale nie jest zbyt czytelny i zagnieżdża iterację.
Odradzałbym
1
@ PiyushS. Czy rozumiesz, co rozumiesz przez złożoność? „WTF / min” jest znacznie wyższy w przypadku wyrażenia regularnego.
firelynx,
42

Jeśli jesteś zadowolony, "blah" in somestringale chcesz, aby było to wywołanie funkcji / metody, prawdopodobnie możesz to zrobić

import operator

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

Wszystkich operatorów w Pythonie można mniej więcej znaleźć w module operatora, w tym in.

Jeffrey04
źródło
40

Najwyraźniej więc nie ma nic podobnego w porównaniu wektorowym. Oczywistym sposobem na zrobienie tego byłoby:

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
Ufos
źródło
1
Jest tak, ponieważ istnieją bajillionowe sposoby tworzenia produktu ze zmiennych atomowych. Możesz upchnąć je w krotkę, listę (które są formami produktów kartezjańskich i są dostarczane z dorozumianą kolejnością), lub można je nazwać właściwościami klasy (bez zamówienia a priori) lub wartościami słownikowymi, lub mogą być plikami katalog lub cokolwiek innego. Ilekroć możesz jednoznacznie zidentyfikować (iterować lub uzyskać) coś w „kontenerze” lub „kontekście”, możesz zobaczyć ten „kontener” jako rodzaj wektora i zdefiniować na nim operacje binarne. en.wikipedia.org/wiki/…
Niriel,
Nie warto nic, inczego nie należy używać z listami, ponieważ wykonuje liniowy skan elementów i jest powolny w porównaniu. Zamiast tego użyj zestawu, szczególnie jeśli testy członkostwa mają być powtarzane.
cs95
22

Możesz użyć y.count().

Zwraca wartość całkowitą liczby przypadków, gdy podciąć pojawia się w ciągu.

Na przykład:

string.count("bah") >> 0
string.count("Hello") >> 1
Brandon Bailey
źródło
7
liczenie łańcucha jest kosztowne, gdy chcesz po prostu sprawdzić, czy tam jest ...
Jean-François Fabre
3
metody, które istnieją w oryginalnym poście z 2010 roku, więc skończyłem je edytować, za zgodą społeczności (patrz meta post meta.stackoverflow.com/questions/385063/... )
Jean-François Fabre
17
Nie. Chodzi mi o to „po co odpowiadać dokładnie tak samo, jak inni 9 lat temu”?
Jean-François Fabre
10
ponieważ moderuję
Jean-François Fabre
2
następnie Jeśli masz uprawnienia do usunięcia go, usuń go, w przeciwnym razie zrób to, co musisz, i przejdź dalej. IMO ta odpowiedź dodaje wartości, co znajduje odzwierciedlenie w głosowaniu użytkowników.
Brandon Bailey
20

Oto twoja odpowiedź:

if "insert_char_or_string_here" in "insert_string_to_search_here":
    #DOSTUFF

Aby sprawdzić, czy jest to fałsz:

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

LUB:

if "insert_char_or_string_here" not in "insert_string_to_search_here":
    #DOSTUFF
ytpillai
źródło
8

Możesz użyć wyrażeń regularnych, aby uzyskać wystąpienia:

>>> import re
>>> print(re.findall(r'( |t)', to_search_in)) # searches for t or space
['t', ' ', 't', ' ', ' ']
Muszkiety
źródło