Python Normal İfadeleri

Normal ifadeler, metin örüntülerini eşleştirmek için güçlü bir dildir. Bu sayfada, Python alıştırmalarımız için yeterli olan normal ifadelerle ilgili temel bilgiler verilmiş ve normal ifadelerin Python'da nasıl çalıştığı gösterilmektedir. Python "yeniden" modülü, normal ifade desteği sağlar.

Python'da bir normal ifade araması genellikle şu şekilde yazılır:

match = re.search(pat, str)

re.search() yöntemi, bir normal ifade kalıbı ile bir dizeyi alıp bu dize içinde bu kalıbı arar. Arama başarılı olursa search(), bir eşleştirme nesnesi, aksi takdirde None değerini döndürür. Bu nedenle, arama genellikle hemen ardından, aramanın başarılı olup olmadığını test etmek için bir if (eğer) ifadesi gelir. Aşağıdaki örnekte gösterildiği gibi, "word:" kalıbını arayan ve ardından 3 harfli bir kelime aranır (ayrıntılar aşağıda verilmiştir):

import re

str = 'an example word:cat!!'
match = re.search(r'word:\w\w\w', str)
# If-statement after search() tests if it succeeded
if match:
  print('found', match.group()) ## 'found word:cat'
else:
  print('did not find')

match = re.search(pat, str) kodu, arama sonucunu "match" adlı bir değişkende depolar. Daha sonra şart ifadesi eşleşmeyi test eder.Arama başarılı olduysa ve match.group() eşleşen metinse (ör. "word:cat"). Eşleşme yanlışsa (Daha ayrıntılı bilgi için yok) arama başarılı olmamıştır ve eşleşen metin yoktur.

Kalıp dizesinin başındaki "r", ters eğik çizgileri değiştirmeden geçen bir python "ham" dizesini belirtir. Bu, normal ifadeler için çok kullanışlıdır (Java, bu özelliğe çok ihtiyaç duyar!). Alışkanlık olarak her zaman "r" ile kalıp dizeleri yazmanızı öneririz.

Temel Kalıplar

Normal ifadelerin gücü, sadece sabit karakterleri değil, kalıpları da belirtebilmeleridir. Tek karakterlerle eşleşen en temel kalıplar şunlardır:

  • a, X, 9, < -- sıradan karakterler yalnızca kendileriyle tam olarak eşleşir. Özel anlamları olduğu için kendileriyle eşleşmeyen meta karakterler şunlardır: . ^ $ * + ? { [ ] \ | ( ) (ayrıntılar aşağıda verilmiştir)
  • . (nokta) -- yeni satırı '\n' hariç herhangi bir tek karakterle eşleşir
  • \w -- (küçük w), bir "word" karakteriyle eşleşir: bir harf veya rakam ya da alt çubuk [a-zA-Z0-9_]. "Kelime" bunun için anlam ifade etse de, bir kelime ile değil, yalnızca tek bir kelime karakteri ile eşleşir. \W (büyük W harfi), kelime olmayan herhangi bir karakterle eşleşir.
  • \b -- kelime ve kelime olmayan arasındaki sınır
  • \s -- (küçük harfle s) tek bir boşluk karakteriyle eşleşir: boşluk, yeni satır, dönüş, sekme, form [ \n\r\t\f]. \S (büyük S harfi), boşluk olmayan herhangi bir karakterle eşleşir.
  • \t, \n, \r -- sekme, yeni satır, Return
  • \d -- ondalık basamak [0-9] (bazı eski normal ifade yardımcı programları \d'yi desteklemez, ancak tümü \w ve \s'yi destekler)
  • ^ = başlangıç, $ = bitiş -- dizenin başlangıcı veya sonu ile eşleşir
  • \ -- karakterin "uzmanlığını" engeller. Bu nedenle, örneğin, noktayla eşleştirmek için \., eğik çizgiyle eşleştirmek için \\ kullanın. Bir karakterin '@' gibi özel bir anlamı olup olmadığından emin değilseniz, önüne eğik çizgi koymayı (\@) deneyebilirsiniz. Bu, \c gibi geçerli bir kaçış dizisi değilse python programınız bir hatayla duracaktır.

Temel Örnekler

Şaka: Üç gözlü domuza ne denir? piiig!

Bir dize içindeki kalıp aramasının temel kuralları şunlardır:

  • Arama, dize boyunca baştan sona ilerler ve bulunan ilk eşleşmede durur
  • Dizenin tamamı değil, kalıbın tamamı eşleştirilmelidir
  • match = re.search(pat, str) başarılı olursa eşleşme Hiçbiri değildir ve özellikle match.group() eşleşen metindir.
  ## Search for pattern 'iii' in string 'piiig'.
  ## All of the pattern must match, but it may appear anywhere.
  ## On success, match.group() is matched text.
  match = re.search(r'iii', 'piiig') # found, match.group() == "iii"
  match = re.search(r'igs', 'piiig') # not found, match == None

  ## . = any char but \n
  match = re.search(r'..g', 'piiig') # found, match.group() == "iig"

  ## \d = digit char, \w = word char
  match = re.search(r'\d\d\d', 'p123g') # found, match.group() == "123"
  match = re.search(r'\w\w\w', '@@abcd!!') # found, match.group() == "abc"

Tekrar

Kalıpta tekrarlamayı belirtmek için + ve * kullandığınızda işler daha ilginç hale gelir

  • + -- Sol tarafında kalıp 1 veya daha fazla kez tekrarlanır, ör. 'i+' = bir veya daha fazla i's
  • * -- Sol tarafında kalıp 0 veya daha fazla kez tekrarlanır
  • ? -- kalıbın 0 veya 1 oluşumunu sol tarafından eşleştirin

En Sol ve En Büyük

İlk olarak, arama, kalıp için en soldaki eşleşmeyi bulur, ikinci olarak ise dizenin mümkün olduğunca büyük bir kısmını kullanmaya çalışır. Örneğin, + ve * mümkün olduğunca uzaklaştırır (+ ve * karakterlerinin "açaklı" olduğu söylenir.

Tekrarlama Örnekleri

  ## i+ = one or more i's, as many as possible.
  match = re.search(r'pi+', 'piiig') # found, match.group() == "piii"

  ## Finds the first/leftmost solution, and within it drives the +
  ## as far as possible (aka 'leftmost and largest').
  ## In this example, note that it does not get to the second set of i's.
  match = re.search(r'i+', 'piigiiii') # found, match.group() == "ii"

  ## \s* = zero or more whitespace chars
  ## Here look for 3 digits, possibly separated by whitespace.
  match = re.search(r'\d\s*\d\s*\d', 'xx1 2   3xx') # found, match.group() == "1 2   3"
  match = re.search(r'\d\s*\d\s*\d', 'xx12  3xx') # found, match.group() == "12  3"
  match = re.search(r'\d\s*\d\s*\d', 'xx123xx') # found, match.group() == "123"

  ## ^ = matches the start of string, so this fails:
  match = re.search(r'^b\w+', 'foobar') # not found, match == None
  ## but without the ^ it succeeds:
  match = re.search(r'b\w+', 'foobar') # found, match.group() == "bar"

E-posta Örnekleri

'xyz alice-b@google.com mor maymun' dizesinin içindeki e-posta adresini bulmak istediğinizi varsayalım. Daha fazla normal ifade özelliğini göstermek için bunu çalışan bir örnek olarak kullanacağız. Aşağıda r'\w+@\w+' kalıbını kullanmayı deneyin:

  str = 'purple alice-b@google.com monkey dishwasher'
  match = re.search(r'\w+@\w+', str)
  if match:
    print(match.group())  ## 'b@google'

Böyle bir durumda arama, \w ile adresteki '-' veya '.' işareti eşleşmediğinden e-posta adresinin tamamını almaz. Aşağıdaki normal ifade özelliklerini kullanarak bu sorunu gidereceğiz.

Köşeli Parantezler

Bir karakter kümesini belirtmek için köşeli parantez kullanılabilir; dolayısıyla [abc] ifadesi "a", "b" veya "c" ile eşleşir. \w, \s vb. kodları, nokta (.) yalnızca değişmez nokta anlamına gelen tek istisna dışında köşeli parantez içinde de çalışır. E-postalarla ilgili sorunda, @ işaretinin etrafında, r'[\w.-]+@[\w.-]+ kalıbıyla gelebilecek karakter kümesine '.' ve '-' ekleyerek e-posta adresinin tamamını almak için köşeli parantezleri kullanabilirsiniz:

  match = re.search(r'[\w.-]+@[\w.-]+', str)
  if match:
    print(match.group())  ## 'alice-b@google.com'
(Daha fazla köşeli parantez özelliği) Bir aralığı belirtmek için kısa çizgi de kullanabilirsiniz. Böylece [a-z] tüm küçük harfle eşleşir. Bir aralık belirtmeden kısa çizgi kullanmak için tireyi en sonda yerleştirin (ör. [abc-]). Kare parantez grubunun başındaki yukarı (^) işareti bunu tersine çevirir. Dolayısıyla [^ab], "a" veya "b" dışındaki herhangi bir karakter anlamına gelir.

Gruptan Ayıklama

Normal ifadenin "gruplama" özelliği, eşleşen metnin bölümlerini seçmenize olanak tanır. E-posta sorunu için kullanıcı adını ve barındırıcıyı ayrı ayrı almak istediğinizi varsayalım. Bunu yapmak için, kalıpta kullanıcı adının ve ana makinenin etrafına parantez ( ) ekleyin. Örneğin: r'([\w.-]+)@([\w.-]+)'. Bu durumda parantezler, kalıbın neyle eşleştiğini değiştirmez. Bunun yerine, eşleşme metninin içinde mantıksal "gruplar" oluştururlar. Başarılı bir aramada, match.group(1) 1. sol paranteze karşılık gelen eşleme metni, match.group(2) ise 2. sol paranteze karşılık gelen metindir. Düz match.group() yine her zamanki gibi tam eşleşme metnidir.

  str = 'purple alice-b@google.com monkey dishwasher'
  match = re.search(r'([\w.-]+)@([\w.-]+)', str)
  if match:
    print(match.group())   ## 'alice-b@google.com' (the whole match)
    print(match.group(1))  ## 'alice-b' (the username, group 1)
    print(match.group(2))  ## 'google.com' (the host, group 2)

Normal ifadelerle yaygın olarak kullanılan bir iş akışı, aradığınız şey için bir kalıp yazmanız ve istediğiniz bölümleri çıkarmak için parantez grupları eklemenizdir.

bulun

findall(), muhtemelen yeniden modüldeki en güçlü işlevdir. Yukarıda bir kalıpla ilgili ilk eşleşmeyi bulmak için re.search() işlevini kullandık. findall(), *tüm* eşleşmeleri bulur ve bunları bir dize listesi olarak döndürür. Her dize bir eşleşmeyi temsil eder.
  ## Suppose we have a text with many email addresses
  str = 'purple alice@google.com, blah monkey bob@abc.com blah dishwasher'

  ## Here re.findall() returns a list of all the found email strings
  emails = re.findall(r'[\w\.-]+@[\w\.-]+', str) ## ['alice@google.com', 'bob@abc.com']
  for email in emails:
    # do something with each found email string
    print(email)

Dosyalı Finder

Dosyalar için, dosyanın satırları üzerinde yinelemek amacıyla bir döngü yazma alışkanlığınız olabilir ve daha sonra, her satırda findall() çağrısı yapabilirsiniz. Bunun yerine, iterasyon işlemini sizin için findall() yapmasına izin verin; bu çok daha iyidir! Tüm dosya metnini findall() içine girin ve tek bir adımdaki tüm eşleşmelerin listesini döndürmesine izin verin (f.read() fonksiyonunun dosyadaki tüm metni tek bir dizede döndürdüğünü unutmayın):

  # Open file
  f = open('test.txt', encoding='utf-8')
  # Feed the file text into findall(); it returns a list of all the found strings
  strings = re.findall(r'some pattern', f.read())

findall ve Gruplar

Parantez ( ) grup mekanizması findall() ile birleştirilebilir. Kalıp 2 veya daha fazla parantez grubu içeriyorsa, findall() işlevi bir dize listesi döndürmek yerine *tuples* listesi döndürür. Her unsur, kalıptaki bir eşleşmeyi temsil eder. Tuple'ın içinde grup(1), grup(2) verileri bulunur. Dolayısıyla, e-posta kalıbına 2 parantez grubu eklenirse, findall() işlevi, her biri kullanıcı adı ve ana bilgisayarı (ör. "alice", "google.com") içeren bir unsur listesi döndürür.

  str = 'purple alice@google.com, blah monkey bob@abc.com blah dishwasher'
  tuples = re.findall(r'([\w\.-]+)@([\w\.-]+)', str)
  print(tuples)  ## [('alice', 'google.com'), ('bob', 'abc.com')]
  for tuple in tuples:
    print(tuple[0])  ## username
    print(tuple[1])  ## host

Tuple listesini hazırladıktan sonra, her bir değnek için hesaplamalar yapmak üzere döngüye geçirebilirsiniz. Kalıp parantez içermiyorsa findall(), önceki örneklerde olduğu gibi bulunan dizelerin bir listesini döndürür. Kalıp tek bir parantez grubu içeriyorsa findall(), bu tek gruba karşılık gelen dizelerin listesini döndürür. (İsteğe bağlı belirsiz özellik: Kalıpta bazen ayraç ( ) gruplandırmaları bulunuyor, ancak bunları çıkarmak istemiyorsunuz. Bu durumda, parantezleri başına ?: ekleyerek yazın, ör. (?: ) ve sol parantez, grup sonucu olarak sayılmaz.)

RE İş Akışı ve Hata Ayıklama

Normal ifade kalıpları yalnızca birkaç karaktere birçok anlam katar, ancak o kadar yoğundurlar ki kalıplarınızdaki hataları ayıklamak için çok zaman harcayabilirsiniz. Bir model çalıştırıp eşleştiğini kolayca yazdırabilmek için çalışma zamanınızı ayarlayın. Örneğin, küçük bir test metninde çalıştırıp findall() işlevinin sonucunu yazdırabilirsiniz. Kalıp hiçbir şeyle eşleşmiyorsa kalıbı zayıflatmayı ve çok fazla eşleşme elde etmeniz için kalıbın bazı bölümlerini kaldırmayı deneyin. Hiçbir şeyle eşleşmediğinde bakılması gereken somut bir şey olmadığından herhangi bir ilerleme kaydedemezsiniz. Eşleşme çok fazla olduğunda, istediğiniz hedefe ulaşmak için bütçeyi kademeli olarak genişletmeye çalışabilirsiniz.

Seçenekler

Yeniden işlevler, kalıp eşleşmesinin davranışını değiştirmek için seçenekler alır. Seçenek işareti, search() veya findall() vb. için ekstra bir bağımsız değişken olarak eklenir, ör. re.search(pat, str, re.IGNORECASE).

  • YOK DURUM -- Eşleşme için büyük/küçük harf farklılıklarını yoksayın. Dolayısıyla "a" hem 'a' hem de 'A' ile eşleşir.
  • DOTALL -- noktanın (.) yeni satırla eşleşmesine izin ver. Normalde yeni satır hariç her şeyle eşleşir. Bu durum sizi yanıltabilir. Sizce .* her şeyle eşleşir, ancak varsayılan olarak bir satırın bitiminden sonraya geçmez. \s (boşluk) karakterinin yeni satırlar içerdiğini unutmayın. Dolayısıyla, yeni bir satır içerebilecek bir boşluk öğesini eşleştirmek isterseniz \s* işaretini kullanabilirsiniz.
  • ÇOK MAKALELİ -- Çok sayıda satırdan oluşan bir dizede ^ ve $ karakterlerinin her bir satırın başlangıcı ve sonuyla eşleşmesine izin verin. Normalde ^/$ yalnızca tüm dizenin başlangıcı ve sonu ile eşleşir.

Açkanlı veya Açısız (isteğe bağlı)

İsteğe bağlı bu bölümde, alıştırmalar için gerekli olmayan daha gelişmiş bir normal ifade tekniği gösterilir.

İçinde etiketler olan bir metin bulunduğunu varsayalım: <b>foo</b> ve <i>bu şekilde</i>.

Etiketleri "(<.*>)" kalıbıyla eşleştirmeye çalıştığınızı varsayalım. Bu model ilk olarak neyle eşleşir?

Sonuç biraz şaşırtıcıdır, ancak .* öğesinin açgözlü yönü, tüm '<b>foo</b> ve <i>bu şekilde</i>' tam olarak eşleşmesine neden olur. Sorun, .* dosyasının ilk başta durdurmak yerine mümkün olduğu kadar ileri gitmesi > (yani "açaklı" olmasıdır).

Normal ifadede, ? veya .*? veya .+? gibi bir ifade kullanarak bunları açgözlü olmayacak şekilde değiştirin. En kısa sürede dururlar. Dolayısıyla, "(<.*?>)" kalıbı yalnızca ilk eşleşme olarak "<b>", ikinci eşleşme olarak "</b>" alır ve sırayla her <..> çifti alınır. Stil, genellikle bir .*? hemen ardından, .*? çalıştırmanın genişlemeye zorlandığı bir somut işaretçi (bu örnekte >) kullanmanızdır.

*? uzantısı Perl'den gelir ve Perl'in uzantılarını içeren normal ifadeler Perl Uyumlu Normal İfadeler (pcre) olarak bilinir. Python, pcre desteğini içerir. Birçok komut satırı yardımcı programı vb. pcre kalıplarını kabul eden bir işaret içerir.

"X'te durmak hariç tüm bu karakterler" fikrini kodlamak için yaygın olarak kullanılan daha eski bir teknik, köşeli parantez stilini kullanır. Yukarıdaki için kalıbı yazabilirsiniz, ancak tüm karakterleri almak için .* yerine [^>]* kullanın. Bu, > olmayan tüm karakterleri atlar (baştaki ^, köşeli parantez grubunu "ters çevirir", böylece parantez içinde olmayan karakterle eşleşir).

İkame (isteğe bağlı)

re.sub(pat, replace, str) işlevi, belirli bir dizedeki tüm kalıp örneklerini arar ve değiştirir. Değiştirme dizesi, orijinal eşleşen metindeki grup(1), grup(2) vb. metinleri ifade eden "\1", "\2" karakterlerini içerebilir.

Tüm e-posta adreslerinin arandığı ve kullanıcıyı (\1) barındıran ancak ana makine olarak yo-yo-dyne.com'un ayarlandığı bir örneği burada bulabilirsiniz.

  str = 'purple alice@google.com, blah monkey bob@abc.com blah dishwasher'
  ## re.sub(pat, replacement, str) -- returns new string with all replacements,
  ## \1 is group(1), \2 group(2) in the replacement
  print(re.sub(r'([\w\.-]+)@([\w\.-]+)', r'\1@yo-yo-dyne.com', str))
  ## purple alice@yo-yo-dyne.com, blah monkey bob@yo-yo-dyne.com blah dishwasher

Tatbikat

Normal ifadelerle ilgili alıştırma yapmak için Bebek Adları Alıştırması'nı inceleyin.