MerixGames

19 maja 2016

/ code & tools

Przetwarzanie tekstu w Pythonie - na czym to polega?

Katarzyna Pieczyńska

Jednym z aspektów programowania jest przetwarzanie tekstu. Programista często musi sprawdzić występowanie słowa, połączyć ze sobą fragmenty tekstu, wyodrębnić jeden z nich czy poddać ciąg znaków walidacji używając wyrażeń regularnych. Z pomocą w tych zadaniach przychodzi Python, czyli jeden z tych języków programowania, które w łatwy i szybki sposób pozwalają pracować z tekstem.

Python w ostatnich latach robi zawrotną karierę. Dzięki przejrzystości składni i bogactwie bibliotek programowanie w Pythonie cechuje szybkość i efektywność. Język ten jest stale rozwijany i cieszy się dużym zainteresowaniem. To wszystko także sprawia, że Python jest on chętnie wybierany do nauki programowania przez osoby, które wcześniej z programowaniem czy informatyką nie miały wiele do czynienia.

Standardowe funkcje

Dzięki Pythonowi bez potrzeby instalowania dodatkowych bibliotek mamy dostęp  do wielu ciekawych funkcji. Zacznijmy zatem od początku.

Konkatenacja

Konkatenację łańcuchów znakowych uzyskuje się w najprostszy sposób używając znaku +.

>>> print("Hello" + " " + "world" + "!")
Hello world!

Można także użyć operatora mnożenia *  - wówczas ciąg znaków zostanie powielony podaną ilość razy.

>>> print ("Hello\n" * 3)
Hello
Hello
Hello

Znaki w łańcuchu

Skoro łańcuch składa się ze znaków, to może chcielibyśmy coś z tymi znakami zrobić, na przykład wyodrębnić je bądź policzyć?

Najpierw policzymy ilość znaków w łańcuchu znakowym (czyli ustalimy jego długość).

>>> len("Hello world")
11

A teraz sprawdzimy jaka jest 3. litera w słowie “Poznan”.

>>> word = "Poznan"
>>> print(word[2])  # indeksujemy od zera!
z

A może chcielibyśmy uzyskać fragment wybranego słowa?

>>> print ( word[:3] )
Poz
>>> print ( word[1:3] )
oz
>>> print ( word[1:] )
oznan

Jak widać odnajdywanie poszczególnych fragmentów i znaków łańcucha jest niezwykle łatwe. Traktujemy go niczym Pythonową listę, w której każdy znak odpowiada jej elementowi. Wartość indeksu w kwadratowym nawiasie wskazuje odpowiedni znak (indeksujemy od zera). Analogiczne jest również wyznaczanie zakresów znaków, które mają być zwrócone. Tak więc liczba przed dwukropkiem wskazuje na indeks początkowy zakresu, za dwukropkiem natomiast - na indeks końcowy. Brak liczby oznacza “zacznij od początku łańcucha”, bądź “idź do samego końca”. Co więcej, tak samo jak ma to miejsce w przypadku listy, dozwolone jest używanie liczb ujemnych w indeksach! Wtedy numer indeksu liczony jest  po prostu od końca.

>>> word = 'Poznan'
>>> word[-1]
'n'
>>> word[:-1]
'Pozna'
>>> word[-3:]
'nan'
>>> word[-3:-1]
'na'

Ale to  nie wszystko. Do wybranego zakresu można dorzucić jeszcze informację, co ile “kroków” znak ma być zwracany.

>>> word[1:5]
'ozna'
>>> word[1:5:2]
'on'
>>> word[::2]
'Pza'

Powiązanie z listą

Wspomniałam o tym, że łańcuch znakowy traktujemy podobnie jak Pythonową listę, ale nie oznacza to, że jest on z nią tożsamy. Nie stanowi to jednak problemu, jeśli chcielibyśmy mieć łańcuch znakowy w tej formie, gdyż  Python umożliwia łatwą konwersję łańcucha znakowego do listy. Wystarczy tylko wywołać na nim funkcję list().

>>> str = "merixstudio"
>>> list(str)
['m', 'e', 'r', 'i', 'x', 's', 't', 'u', 'd', 'i', 'o']

A gdyby tak odwrócić ten proces?

>>> "".join(['m', 'e', 'r', 'i', 'x', 's', 't', 'u', 'd', 'i', 'o'])
'merixstudio'

join() używamy podając dowolny ciąg znaków, który połączy elementy listy (lub bez znaku, tak jak pokazane zostało powyżej). Przydaje się to gdy np. chcemy połączyć elementy listy w łańcuch rozdzielając je jednocześnie  przecinkiem.

>>> ", ".join(['apple', 'pear', 'banana', 'orange'])
'apple, pear, banana, orange'

Do join() przeciwstawna jest funkcja split(). Według podanego separatora tworzy ona listę z łańcucha znakowego.

>>> "apple, pear, banana, orange".split(", ")
['apple', 'pear', 'banana', 'orange']

Wygląd napisu

W Pythonie istnieją również metody zmieniające wygląd łańcuchów znakowych, np.

  • wielkość liter;
>>> "merixstudio".upper()
'MERIXSTUDIO'
>>> "merixstudio".capitalize()
'Merixstudio'
>>> "MERIX".lower()
'merix'
  • ułożenie znaków (w łańcuchu o podanej długości): wyśrodkowanie, wyrównanie do lewej/prawej,;
>>> "merix".center(15)
'     merix     '
>>> "merix".ljust(15)
'merix             '
>>> "merix".rjust(15)
'          merix'
  • występowanie białych znaków - usuwanie ich z początku bądź końca łańcucha.
>>> "   merix   ".strip()
'merix'
>>> "   merix   ".lstrip()
'merix   '
>>> "   merix   ".rstrip()
'   merix'

Inne metody natomiast zwracają nam informacje o łańcuchu.

>>> "merixstudio".isdigit()
False
>>> "merixstudio".islower()
True
>>> "merixstudio".isupper()
False
>>> "merixstudio".count('i')
2

Metoda isdigit() sprawdziła, czy łańcuch zawiera jedynie cyfry, islower()/isupper() czy składa się z małych/wielkich liter, metoda count() policzyła zaś ilość  wystąpień itery “i” w podanym łańcuchu.

Przeszukiwanie

W podstawowym zakresie możliwe jest bez konieczności wykorzystywania wyrażeń regularnych. Oto najprostszy sposób:

>>> 'm' in 'merixstudio'
True
>>> 'a' in 'merixstudio'
False

Natomiast metoda find() zwróci nam numer indeksu, od którego rozpoczyna się odnaleziony ciąg znaków ( przeszukiwanie trwa tylko do pierwszego wyniku). W przypadku braku znaleziska otrzymamy wynik -1.

>>> 'merixstudio'.find('m')
0
>>> 'merixstudio'.find('x')
4
>>> 'merixstudio'.find('mer')
0
>>> 'merixstudio'.find('a')
-1

Standardowo Python umożliwia także prostą podmianę łańcuchów. Służy do tego metoda replace()

>>> "I like cats.".replace("cats", "dogs")
'I like dogs.'

Wyrażenia regularne

Wyrażenia regularne służą do znajdowania fragmentów łańcuchów znakowych pasujących do ustalonego wzorca i wykorzystywane są one zarówno w Pythonie jak i w innych językach programowania. Są niezwykle przydatne do wszelkich, również bardziej skomplikowanych przeszukiwań tekstu, przekształcania go poprzez usuwanie, zamianę określonych fragmentów, dzielenie go na części czy sprawdzania zgodności ze wzorcem, co przydaje się np. podczas walidacji pól formularzy. Z racji, iż temat budowy wyrażeń regularnych jest bardzo obszerny, ograniczę się tylko do opisu podstawowych symboli.

Mówiąc w skrócie, wyrażenia regularne służą do konstruowania wzorców, według których przeszukujemy tekst. W ich skład wchodzą:

  • znaki alfanumeryczne oraz specjalne;
  • alternatywy oznaczone znakiem |, np. good (morning|evening);
  • nawiasy kwadratowe [abc], które dopasowują dokładnie jeden znak z ich środka, np. ‘be[ae]r’, bądź negują jego występowanie - w sytuacji gdy użyjemy znaku ^ wewnątrz nawiasów, np.  c[^a]t;
  • kropka . , która zastępuje każdy znak oprócz nowej linii (poprzedzona backslashem \ staje się zwykłym znakiem);
  • kwantyfikatory:
    1. * - dopełnienie Kleene'ego (ang. Kleene star), dopasowuje 0 lub więcej powtórzeń,
    2. + - dopasowuje jedno lub więcej powtórzeń,
    3. ? - dopasowuje 0 lub 1 powtórzeń,

Kwantyfikatory * oraz + są domyślnie “zachłanne” (ang.greedy), to znaczy, że szukają jak najdłuższego wystąpienia wzorca. Pomimo to, możemy zmienić ich działanie na “leniwe” (ang. lazy), dopisując do nich znak ?. W rezultacie kwantyfikator wyglądać będzie w ten sposób : *? lub +?

  • kotwice:
    1. ^ - dopasowuje początek,
    2. $ - dopasowuje koniec łańcucha znakowego,

     

  • specjalne znaki:
    1. \d - oznacza cyfrę od 0 do 9,
    2. \w - zastępuje dowolną literę, cyfrę, oraz podkreślnik _,
    3. \t - znak tabulacji,
    4. \r - koniec linii,
    5. \n - nowa linia,
    6. \s - dowolny biały znak,
  • nawiasy klamrowe:
    1. {x} - x powtórzeń,
    2. {x, y} - pomiędzy x a y powtórzeń,
    3. {x, } - co najmniej x powtórzeń .

Moduł “re”

Z dobrodziejstw wyrażeń regularnych w Pythonie możemy korzystać po zaimportowaniu modułu re. Poznajmy więc kilka podstawowych metod.

Funkcja match sprawdza występowanie wzorca tylko na początku łańcucha znakowego.

>>> result = re.match(r'Hello', 'Hello there. What a wonderful day.')
>>> result.group()
'Hello'
>>> re.match(r'Hello', 'What a wonderful day. Hello!')  # nie zwróci żadnego wyniku

W powyższym przykładzie wyszukuję słowo “Hello”. Aby wypisać rezultat wyszukiwania korzystam z funkcji group(), o której dowiecie się więcej w dalszej części artykułu.

Funkcja search sprawdza występowanie wzorca w całym ciągu.

>>> result = re.search(r'Hello', 'What a wonderful day. Hello!')
>>> result.group()
'Hello'

We wzorcu przy pomocy okrągłych nawiasów możemy oznaczyć grupy, do których będziemy się odwoływać. group() odnosi się domyślnie do indeksu 0, czyli dopasowania obejmującego cały wzorzec. Kolejne numery indeksów wskazują na odpowiednie grupy znaków wewnątrz nawiasów. groups() natomiast zwraca wszystkie podgrupy zebrane w krotkę (jedna z Pythonowych struktur danych).

>>> result = re.search(r'(.+) there\.\sWhat a (.+) day', 'Hello there. What a wonderful day.')
>>> result.group()
'Hello there. What a wonderful day'
>>> result.group(0)
'Hello there. What a wonderful day'
>>> result.group(1)
'Hello'
>>> result.group(2)
'wonderful'
>>> result.groups()
('Hello', 'wonderful')

match i search mogą być używane wraz z flagami, np.:

  • re.IGNORECASE / re.I - dopasowanie niewrażliwe na wielkość liter (case-insensitive matching);
  • re.MULTILINE / re.M - ^ i $ w wyrażeniu regularnym oznaczają początek i koniec linii, zamiast początku i końca całego łańcucha;
  • re.DOTALL / re.S - znak kropki w wyrażeniu regularnym oznaczający dowolny znak ( zawiera także znak nowej linii).
>>> re.search(r'hello', 'Hello there. What a wonderful day.')  # nie zwróci wyniku
>>> result = re.search(r'hello', 'Hello there. What a wonderful day.', re.I)
>>> result
<_sre.SRE_Match object; span=(0, 5), match='Hello'>
>>> result.group()
'Hello'

Na uwagę zasługuje również możliwość odwołania się do danej grupy w samym wzorcu:

>>> result = re.search(r'(cat)(.+)(\1)', "I like my cat, but my cat does't like me.")
>>> result.groups()
('cat', ', but my ', 'cat')
>>> result.group()
'cat, but my cat'

I wreszcie, aby otrzymać listę wszystkich znalezionych wystąpień wzorca, należy użyć funkcji findall.

>>> result = re.findall(r'([^\w]|^)(\w{3})([^\w]|$)', "I like my cat, but my cat does't like me.")
>>> result
[(' ', 'cat', ','), (' ', 'but', ' '), (' ', 'cat', ' ')]

W ten sposób wyszukaliśmy wszystkie trzyliterowe wyrazy. Ponieważ we wzorcu oznaczone zostały 3 grupy, to każdy wynik jest 3-elementową krotką.

Kolejną, a zarazem ostatnią metodą, którą zamierzam przedstawić jest metoda sub. Wykorzystywana jest ona do zastępowania jednych ciągów innymi - niczym wcześniej wspomniana replace(), jednakże sub dysponuje dużo większymi możliwościami dzięki wykorzystaniu wyrażeń regularnych.

>>> re.sub(r'<.+?>', '', '<tag>Do you like Python?</tag>')
'Do you like Python?'

Jest to również przykład zastosowania “leniwego” kwantyfikatora: +?. Bez znaku zapytania ? przy kwantyfikatorze . (kropka), wzorzec objąłby cały ciąg 

“<tag>Do you like Python?</tag>”.

To tylko niektóre z funkcji, które oferuje Pythonowy moduł re. Aby zapoznać się z resztą jego możliwości, a także z bardziej obszernym opisem wyrażeń regularnych, polecam zapoznać się z dokumentacją Pythona.

Jak mogliśmy sami się przekonać, Python świetnie nadaje się do przetwarzania tekstu. Nic zatem dziwnego, że jest chętnie wykorzystywany przy manipulowaniu danymi tekstowymi. Mam nadzieję, że udało mi się pokazać możliwości Pythona w tej dziedzinie i zachęcić do poznawania tego wspaniałego języka.

Strona używa plików cookies. Wyrażasz zgodę na używanie cookies, zgodnie z aktualnymi ustawieniami przeglądarki.