Bir dizide istediğimiz alt dizinin var olup olmadığını kontrol etmek istiyoruz. Başka bir dile aşina iseniz, bu işlemi .contains
veya substring
benzeri bir yöntem ile yaparız. Peki Python’da bir alt dizinin varlığını nasıl kontrol edeceğiz? Bir alt-dizeyi nasıl arayacağız ya da sorgunun hangi indexte var olduğunu nasıl bulacağız?
Python string için substring metodu var mı?
İlgileneceğimiz durumların neredeyse %99‘unda in
ifadesi işimizi görecektir. Bu ifade True
veya False
döndürür:
'substring' in any_string
Eğer bir index döndürmek istiyorsak ise str.find
kullanırız. Aradığımız substring yoksa -1 dönecektir:
start = 0
stop = len(any_string)
any_string.find('substring', start, stop)
Ya da str.index
kullanabiliriz. find
‘a benzer ancak sonuç bulamadığında bir ValueError verir:
start = 100
end = 1000
any_string.index('substring', start, end)
Neden “in” kullanımını tercih etmeliyiz
in
kullanımını şu iki sebeple tercih ederiz:
- dilin kendisi bu ifadenin amacını buna göre tanımlamış ve
- diğer Python programcıları da in kullanmanızı bekleyecektir.
>>> 'foo' in '**foo**'
True
in için karşıt kontrol ise not in
ile yapılır:
>>> 'foo' not in '**foo**' # False döndürür
False
Bu ifade semantik olarak not 'foo' in '**foo**'
ile eş değerdir ancak daha okunabilir olduğu için dilin yapısında bu şekilde verilir.
__contains__
kullanmaktan kaçının
“contains” metodu in
‘in davranışını belirler. Örneğin,
str.__contains__('**foo**', 'foo')
ifadesi True
döndürür. Bu ifadeyi aynı zamanda bir superstring ile de çağırabilirdik.
'**foo**'.__contains__('foo')
Ancak bunu yapmaktan kaçınırız! çift alt çizgi ile başlayan metotlar public olarak değerlendirilmez. Bu metotların tek kullanım yerleri varolan çalışma prensibinin değiştirilmesi içindir in
veya not in
ifadelerini kendiniz implement etmek isterseniz kullanırsınız:
class NoisyString(str):
def __contains__(self, other):
print(f' "{self}" içinde "{other}" var mı kontrolü')
return super(NoisyString, self).__contains__(other)
ns = NoisyString('a string with a substring inside')
Çıktı:
>>> 'substring' in ns
"a string with a substring inside" içinde "substring" var mı kontrolü
True
Alt dizinin varlığını kontrol etmek için find
veya index
kullanmayın
Bir alt dizinin dizi içinde olup olmadığını kontrol etmek için aşağıdakileri kullanmaktan kaçının:
>>> '**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
Diğer dillerde substring aramak için herhangi bir metot bulunmayabilir. Dolayısıyla diğer dillerde benzer bir işlem için index benzeri bir metot çağırabiliriz. Ancak Python, bu bağlamda, çok daha iyi performansı olan in
operatörünü sunar.
Ayrıca, bu metotlar in
‘in alternatifi değildirler. Bir exception döndürebilirler ki bunu kontrol etmeniz gerekir (ya da -1
). Daha da önemlisi eğer substring ilk pozisyonda bulunursa 0
döndürürler ki bu da True
olması gerekirken False
olarak değerlendirilebilir.
in, contains, find ve index için performans karşılaştırması
Bu metotların in’e göre performanslarını karşılaştıralım:
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şağıdaki sonuçlara bakarsanız in
‘in çok daha hızlı olduğunu görürsünüz. Yani aynı işi daha iyi ve daha hızlı yapar:
>>> 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}
Eğer "in"
ifadesi __contains__
kullanıyorsa __contains__
‘e göre nasıl daha hızlı olabilir?
Bu soru önemli.
Fonksiyonu parçalara ayırarak bakalım:
>>> 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
Görünüşe göre .__contains__
metodu Python sanal makinası tarafından ayrı bir şekilde çağırılıyor. Bu da aradaki farkı açıklıyor.