koddla

Yazılımcıları bilgi ile güçlendirir.

Python’da string içinde arama – kontrol etme

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:

  1. dilin kendisi bu ifadenin amacını buna göre tanımlamış ve
  2. 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.

Bir cevap yazın

E-posta hesabınız yayımlanmayacak. Gerekli alanlar * ile işaretlenmişlerdir

Back to top