Modüller | re

re, aslında çok kullanışlı ama bir o kadar karmaşık bir modüldür. Anlaması, öğrenmesi, akılda tutması ve uygulaması gerçekten zordur. Ancak, takıldığınız bir nokta olursa dilediğiniz zaman bu bloga gelebilir veya internette bunun araştırmasını yapabilirsiniz.

re modülünün açılımı "Regular Expression" şeklindedir ve "Regex" olarak da anılır. Bu modül sayesinde biz, bir string içerisinde çeşitli aramalar, eşleştirmeler yapabiliyor; tabiri caizse string ile oyuncak gibi oynuyoruz. Yavaştan başlayalım.

Her şeyden önce bu modülü import ediyorum. Sonrasında bir cümle belirliyorum ve cümle içerisinde aramak istediğim kelimeyi bir değişkene atıyorum.

Şimdi, aradığım kelimeyi cümlemin içinde bulmak için re modülünün 'search' isimli fonksiyonunu kullanacağım. "Search" kelimesi "aramak" anlamına gelir.

re.search() dedikten sonra parantezler içerisinde iki parametre görüyorsunuz. Bunlardan ilki, aradığım kelime iken ikincisi, cümlenin kendisidir. Yani Python'a şunu diyorum: "searchFor kelimesini sentence cümlesinde ara."

Bunu result isimli bir değişkene atadım ve son olarak print(result) komutuyla ekrana verdim. Şimdi, çıktımıza bakalım.

Sırayla gidelim. En sol tarafta 're.Match object' şeklinde bir ifade görüyorsunuz. "Match" kelimesi "uyuşmak, eşleşmek" anlamına gelir. Çıktıda ise bunun bir obje (object) olduğunu söylüyor. Demek ki çıkan sonuç (result) aslında bir obje.

optparse blogundan hatırlarsınız; parser = opt.OptionParser() dediğimiz zaman buradaki parser değişkeni objemiz oluyordu. Sonrasında bu obje sayesinde fonksiyonlara daha kolay erişebiliyorduk. Burada da result değişkeni bir obje konumundaysa aynı şeyi result için de düşünemez miyiz?

Evet, kesinlikle düşünebiliriz. result değişkenimiz bir obje olduğuna göre onun tuttuğu değere ait fonksiyonları result. diyerek kontrol edebiliriz.

Biraz önceki çıktımızda "span=(51, 55)" şeklinde bir ifade gördük. Bu, aranan kelimeyi 51 ile 55. index'ler arasında olduğunu ifade eder. İsterseniz cümledeki karakterleri (boşluklar dahil) sayın; göreceksinizdir.

Eğer result bir obje ise result.span() diyebilir ve bu konumu, spesifik olarak görebiliriz diye düşünüyorum.

Gayet başarılı. Peki, sizce neden 'Atatürk' kelimesinde geçen 'türk' kelimesini görmezden geldi de direkt olarak diğer 'Türk' kelimesini gösterdi?

Çünkü search fonksiyonu, büyük-küçük harf hassasiyetine sahiptir. Bizim aradığımız kelime büyük harf ile başlayan 'Türk' kelimesi iken 'Atatürk' kelimesi içerisinde geçen 'türk', küçük harfle başlıyor. İsterseniz değişkenimizin değerini değiştirip tekrar bakalım.

Gördüğünüz gibi result = "Türk" iken result = "türk" olarak değiştirdim ve sonuç farklı geldi.

result objesinin başka hangi özelliklerine erişebiliriz?

Gördüğünüz gibi end(), start() ve string özelliklerine erişebiliyoruz. Ayrıca print(result.group()) derseniz, aradığınız kelimenin kendisini size gösterecektir. Tabii ki birkaç özelliği daha var ancak şimdilik bu kadarı yeterli. Merak ediyorsanız dir(result) deyip görebilirsiniz.

Aklınıza şöyle bir soru takılabilir: "Büyük-küçük harf hassasiyeti mevcut. Öyleyse 'onurlu bir Türk subayı' cümlesi içerisindeki 'Türk' kelimesini buldu, 'Türkiye' kelimesindeki 'Türk' kelimesini neden bulamadı?".

Çünkü fonksiyonumuz yanlış. re.search() fonksiyonu, eşleşen bir kelime bulur bulmaz bunu size gösterir ve gerisi ile ilgilenmez. Ancak re.findall() derseniz, cümle içerisinde kaç tane eşleşme varsa hepsini size gösterecektir. Buradaki "find all" kalıbı, "hepsini bul" anlamına gelir.

re.search() gibi; re.findall() dedim ve parantezler içerisine ilk önce aranan kelimeyi, sonra da cümleyi verdim. Cümlede iki adet 'Türk' kelimesi geçtiği için bize ['Türk', 'Türk'] şeklinde bir sonuç döndürdü. Dikkat ederseniz; bu bir liste. O zaman ben index kullanabilirim, değil mi?

Kesinlikle evet. Peki, bulunan bu eşleşmelerin index koordinatlarını görmek istersem print(result.span()) diyemez miyim?

Bu noktada sorun yaşıyoruz. Bunun sebebi, birden fazla eşleşme olmasından kaynaklanan ufak bir değişikliktir. Biraz önce result değişkeni tek bir eşleşme tutarken bunun span() özelliğine erişebiliyorduk. Ancak birden fazla eşleşme söz konusu olduğunda result değişkeni, bulunan eşleşmeleri sade bir string olarak bir listede tutar. Bu durum, result değişkenini bir obje olmaktan çıkarır ve onu bir liste konumuna sokar. O zaman biz, yine hepsini bulsun istiyoruz ama aynı zamanda bulunan eşleşmelerin hepsini teker teker kendine obje edinsin istiyoruz. Bu sayede span() ve diğer fonksiyonlara erişmek istiyoruz. Bunu nasıl yaparız?

Bu sorunu aşmak için bir for döngüsünden ve başka bir fonksiyondan faydalanabiliriz.

Dikkat edelim; artık result değişkeniyle bir işimiz yok.

for matches in re.finditer() dedim ve parantezler arasına yine ilk önce aranan kelimeyi, sonra da cümleyi verdim. Bu noktada finditer ifadesi aslında "find iteration" yani "yineleme bul" anlamındadır. Biraz önce, result değişkeninin bulduğu her eşleşme için basit bir string olarak bir listede tuttuğunu söylemiştim. Kurduğumuz bu yapı sayesinde result yerine matches değişkeni mevcut. Bu döngüde bulunan her eşleşme, matches değişkeninin objesi oluyor. Yani matches, bunları kendine bir obje olarak atıyor. Bu sayede matches.span() diyebiliyor ve net sonuçlar elde edebiliyoruz.

Örneğin bir cümlede veya herhangi bir string yapıda belli bir karakteri başka bir karakterle değiştirmek istiyorsunuz. Hadi bunu yapalım.

Bunun için re modülünün sub() fonksiyonunu kullanıyoruz. birthday değişkeninde 1 Ocak 1991 tarihini tutuyorum. Bu tarihteki tire işaretlerini slash işaretine dönüştürmek için ise 'new' isimli bir değişken tanımlıyorum ve ilk önce değiştirilecek karakteri, sonra yerine koyulmasını istediğimiz karakteri, son olarak bu işlemin nerede yapılacağını yazıyoruz re.sub("-", "/", birthday).

\d

Bu ifade, sayısal karakterler için bir kontrol sağlamamıza yarar. Ters Slash için 'AltGr + *'. Örneğin kullanıcıdan doğum yılını girmesini istiyoruz. Normalde girmesini beklediğimiz sayı 4 basamaklı olur (örneğin 1990). Ancak bazı kullanıcılar 1990 yazmak yerine sadece 90 yazıyor. Bunu nasıl engellersiniz?

İlk önce kullanıcıdan doğum yılını girmesini istiyorum ve formatımı belirliyorum:

searchForYear değişkeni içerisinde "\d\d\d\d" şeklinde değişik bir ifade tutuyorum. Bu ifade içerisindeki her bir \d ifadesi, bir sayısal karakteri ifade eder. Bundan dört tane koyduğum için de 4 adet sayısal rakamı ifade ediyor. Şimdi, bunları kontrol etmeye çalışalım.

if re.search(searchForYear, str(birthYear)) ifadesinde, kullanıcıdan aldığım birthYear değerini searchForYear değişkeninde tuttuğum regex kodu ile kontrol ediyorum. Eğer kontrol doğru ise print(f"") şeklinde mesajımızı veriyoruz, değilse de onu uyarıyoruz.

Bu noktada str(birthYear) dememin sebebi, regex'in sadece string'ler üzerinde hakimiyet kuruyor olmasından kaynaklanıyor. Kullanıcıdan int(input()) şeklinde girdi istedik ancak regex bunu kabul etmeyecektir. Dolayısıyla aldığımız değeri bu şekilde geçici olarak string'e çevirmiş oluyoruz.

Şimdi, bu kodu deneyelim.

Gördüğünüz gibi girdi kısmına 90 yazdığımda beni uyardı; 1990 yazdığımda bunu kabul etti. Bu noktada search() fonksiyonunu artık biliyorsunuz. Ancak ben yine de if koşulunu tekrar edeyim.

re.search() dedikten sonra bu fonksiyona ilk önce aradığımız şeyi, sonra da aradığımız yeri veriyorduk. Bu noktada biz, searchForYear derken aslında regex kodunu aramak istediğimizi; str(birthYear) derken de bu regex kodunu, birthYear değişkeninde aramak istediğimizi söylüyoruz.

Bu mantığı oturttuktan sonra oraya bir if koyuyoruz ve koşulun sağlanması durumunda yani bir 'match' söz konusu ise işlemlerimizi gerçekleştiriyoruz.

\s

Bu, boşluk karakterini ifade eder. Normal şartlarda her kelimeden sonra gelen bildiğimiz boşluk, bilgisayar dilinde bir karakter olarak sayılır. İşte \s ile bu boşlukları kontrol ediyoruz. Örneğin bir mail adresinde boşluk olamaz, değil mi? Hadi bunu yapalım.

Kullanıcıdan, mail adresini girmesini istedim. Sonrasında regex kodumu (\s) 'space' isimli bir değişkene verdim.

if bloğunda ise biraz önceki ile aynı mantığı yürüttüm. re.search() fonksiyonu, space değişkenindeki regex kodunu mail değişkeni içerisinde arayacaktır. Eğer mail değişkeninin değeri içerisinde bir boşluk görürse kullanıcıyı uyaracak, göremezse mail adresini olduğu gibi ekrana yazacaktır.

Çıktı kısmına dikkat ederseniz ilk denememizde boşluk kullanmadığımız için mail adresi olduğu gibi ekrana yazıldı. Ancak ikincisinde xxx yyy@gmail.com yazdığımız için burada bir boşluk tespit edildi ve ekrana uyarı verildi.

\w

Bu, bütün karakterler için (sayı, harf vb.) kullanılan bir regex kodudur. Sayı ve harfin yanında özel karakterler arasından sadece alt tire işaretini gözetir. Buna basit bir örnek bulamadığım için MAC adresi üzerinden bir örnek vermek istedim ve Kali Linux'a girdim. Bakalım.

Kırmızı kutucuk içerisine aldığım yer, benim MAC adresimdir. Dikkat ederseniz iki karakterden sonra bir adet iki nokta üst üste işareti geliyor ve tam 6 defa iki karakter geliyor. İsterseniz re ile bunu almaya çalışalım.

Burada hem re hem de subprocess modülünü kullandım. subprocess modülünü kullanmamın sebebi, biraz önce terminalde yazdığım komutu uzaktan kontrol etmek içindi. Bu kontrolü de 'check_output' fonksiyonu ile yerine getirdim.

Sonrasında regex kodumu belirledim ve bunu macAddr isimli bir değişkene atadım. Bu noktada ikişer ikişer gelen karakterlerin arasında hem sayı hem de harf olduğunu görüyorsunuz. Burada bir ayrım yapılmaması için '\w' ifadesini kullanmam gerekiyordu. Her iki karakterden sonra gelen noktalama işaretini ise regex koduna dolaylı olarak yani regex kodu kullanmadan, direkt olarak yazdım ve eşleşen sonuçları bana göstermesini istedim.

Aslında kodumuz başarılı. Ancak ben, sadece MAC adresini görmek istiyorum; diğerleri pek umurumda değil. Çıktıda en sağda bulunan match='...' ifadesine erişmek için blogun başlarında gördüğünüz group() fonksiyonunu kullanmak istiyorum.

findMAC.group() dedikten sonra istediğim şekilde, sade bir çıktı ile karşılaştım. O kadar verinin içinden MAC adresini re modülü sayesinde çektim. İşte, \w regex kodu bu işe yarıyor; herhangi bir karakter gözetmeksizin işlem yapacaktır.

r""

Nasıl ki fstring yapılarında tırnak işaretlerinin başına f harfini koyup f"" şeklinde yazıyorsak regex kodlarını yazarken de r harfini koymamız yani r"" şeklinde yazmamız gerekir. Koymadığımız zaman herhangi bir sorunla karşılaşmayız ancak bu uzun zamandır süregelen bir ifadedir ve başka insanların ne yaptığınızı anlaması açısından önemlidir. Dolayısıyla regex kodlarını yazarken aşağıdaki gibi bir kullanım sergilemeniz daha kabul görür bir davranış olacaktır.

Zaten dikkat ederseniz; r harfini koyduktan sonra regex kodunun rengi değişti.

[abc] veya [123]

Bu şekilde bir kullanım sayesinde belli bir karakter arayabiliyoruz.

re.findall("[abc]", sentence) dedim ve sentence değişkeninin tuttuğu değer içerisinde ne kadar a, b ve c karakteri varsa hepsini bana yazdırdı.

Aynı kullanımı sayılar için de yapabiliriz.

1, 3 ve 8 rakamlarını verdim ve içerisinde ne kadar varsa o kadar yazdırdı. sentence içerisinde 9 rakamı da mevcut ama re.findall() içerisine 9 rakamını vermediğimiz için onu atladı.

[a-z] veya [1-9]

re modülüne aralık da verebilir ve 'şu aralıktaki karakterleri bul' diyebiliriz.

Gördüğünüz gibi yalnızca verdiğimiz aralık içerisindeki karakterlere odaklanarak onları buldu ve değişken içerisinde kaç tane varsa sırasıyla hepsini yazdırdı.

[^abc] veya [^123]

Bu kullanım sayesinde dahil etmek istemediğiniz karakterleri çıkarabilirsiniz. Örneğin;

Gördüğünüz gibi; cümle için a, b, c, d ve e karakterlerini yok sayarken sayılar için 1, 4 ve 7 rakamlarını yok saydı. Şapka işaretini yapabilmek için 'Shift + 3'.

Bu kullanımda bilmeniz gereken kritik bir durum söz konusu. Sayılar için bunu kullanırken yalnızca 0'dan 9'a kadar olan sayıları alıyoruz. Yani '[1-9]' veya '[^1-9]' kullanımı doğrudur. Ancak daha büyük bir sayı verirseniz yazdığınız ikinci sayıdan sonrası aynı kalacaktır. Ne demek istiyorum?

Örneğin [1-268] dediyseniz Python, yalnızca 1 ile 2 arasındaki sayılara odaklanacak; 6 ve 8 rakamlarını kesin olarak koyacaktır. Örneğin siz, 1 ve 4 aralığındaki sayıları almak istiyorsunuz ama her durumda 9 rakamı da gelsin istiyorsunuz. Bu durumda [1-49] şeklinde bir kullanım sağlayabilirsiniz.

Örnekte, 1 ile 2 arasındaki sayıları alacak, diğerlerini atlayacaktır. Ancak 6 ve 8 sayılarını kesinlikle alacaktır.

Nokta (.)

Nokta, bir yer tutucudur. Örneğin;

Normalde name değişkenimin tuttuğu değer CyberWorm. Ancak ben, bu isim içerisindeki b, e ve o harfleri yerine ne koyulursa koyulsun kabul ediyorum; diğer harflerin kesin olarak olmasını istiyorum. Mesela kullanıcıdan bir telefon numarası girmesini isteyelim ve Türkiye'nin alan kodunu (90) zorunlu tutalım.

İlk çalıştırmamızda 90 değeriyle başladığımız için sorun çıkmadı ama ikincisinde 85 ile başladığımız için sorun çıktı. Yer tutucular da bu şekilde.

^a

Bu, hangi harfle veya kelime/cümle ile başlanması gerektiğine karar veren bir yapıdır. Örneğin;

Cümlenin 'Başöğretmen Gazi Mustafa Kemal Atatürk' ile başlayıp başlamadığını kontrol ettik ve eğer cümle öyle başlıyorsa ekrana 'Doğrudur' yazdırdık.

Python, cümle başka bir şey veya başka bir isim ile başlıyorsa ekrana bir şey yazdırmadı. Çünkü o cümle 'Başöğretmen Gazi Mustafa Kemal Atatürk' ile başlıyor. Şapka işaretini koymayı unutmayalım.

Bu sefer tek bir harf üzerinden aynı örneği yapalım.

Cümlenin B ile başlayıp başlamadığını '^B' şeklinde kontrol ettim ve çıktı ona göre geldi.

Bu sefer '^D' ile, cümlenin D harfi ile başlayıp başlamadığını kontrol ettim ve çıktı ona göre geldi.

^a ifadesinin yanında aynı işi yapan bir başka ifademiz '\A' ifadesidir. Bu ifadeyi, string'in başına koymanız gerekir. Örneğin;

re.findall("\ABaşöğretmen")

a$

Bu ifade, bir kelime veya cümlenin neyle bittiğini sorgulamamıza izin verir.

Gördüğünüz üzere 'Python' kelimesi 'P' ile bitmediği için boş bir liste döndürüldü.

Bu örnekte de 'n$' ifadesini kullandık. 'Python' kelimesi 'n' harfi ile bittiği için içi dolu bir liste döndürüldü.

Burada basit bir kısıtlama uygulaması görüyorsunuz. İnceleyip siz de deneyebilirsiniz.

a$ ifadesiyle birlikte aynı işi yapan bir ifademiz daha mevcut: '\Z'. Bu ifadeyi, string'in sonuna yerleştirmemiz gerekir. Örneğin;

re.findall("@gmail.com\Z")

[0-9]{3}

Yukarıdaki başlık temsilidir. Normalde harfler veya rakamlar için bir aralık belirtebiliyorduk. Bu aralıkları belirtirken yanın bir süslü parantez açıp bir değer girebiliyorsunuz. Sonuçlarına beraber bakalım.

numbers isminde bir değişken tanımladım ve içine 3 ve 4 basamaklı sayılar, bir de 2 basamaklı bir sayı ekledim. findall([0-9]) dediğim zaman aslında oradaki tüm sayıları tek tek seçeceğini biliyorsunuz. Ancak {3} ifadesi sayesinde yalnızca 3 basamaklı sayıları çektiğini görüyorsunuz.

Burada, 'en az x, en fazla y' şeklinde bir şey de diyebiliyoruz.

[0-9]{3,4} ifadesi ile şunu demiş olduk: "en az 3, en fazla 4 basamaklı sayıları getir". Dikkat ederseniz 23 sayısı alınmadı.

a|b

Bildiğiniz üzere programlama dünyasında 'and / ve' ile 'or / ya da' koşul ifadeleri mevcuttur. Orada gördüğünüz düz çizgi (pipe) or yani ya da anlamına gelir. Bu ifade ile, bir kelime veya cümlede belli ifadelerin geçip geçmediğini kontrol edebilirsiniz. Örneğin;

Burada, 'Python' kelimesi içerisinde a ya da b harfinin mevcut olup olmadığını sorguladık. Bu noktada bu iki harften yalnızca birinin var olması, bu kod satırının True sonuç döndürmesi için yeterlidir. Bu konu hakkında bilginiz az ise lütfen şu blogu ziyaret edin: 'Python ve Mantık'.

a ya da n sorgusu yapalım:

Peki, kelime veya cümle kontrolü yapabiliyor muyuz?

Birden fazla kelimeyi veya cümleyi de bu yöntemle aratabilirsiniz.

re modülü bu şekildeydi. Bu blogun, ek bilgi içeren güncelleme alma olasılığı yüksektir.


Yayınlanma Tarihi: 2022-11-29 18:19:54

Son Düzenleme Tarihi: 2022-12-04 13:40:15