Zanurkuj w Pythonie/plural.py, etap 1

Patrzymy na słowa, które - przynajmniej w języku angielskim - składają się z liter. Dysponujemy też regułami, które mówią, że musimy znaleźć w słowie pewne kombinacje liter, a następnie odpowiednio to słowo zmodyfikować. Brzmi to dokładnie jak zadanie dla wyrażeń regularnych.

Przykład 17.1. plural1.py

import re

def plural(noun):                            
    if re.search('[sxz]$', noun):             #(1)
        return re.sub('$', 'es', noun)        #(2)
    elif re.search('[^aeioudgkprt]h$', noun):
        return re.sub('$', 'es', noun)       
    elif re.search('[^aeiou]y$', noun):      
        return re.sub('y$', 'ies', noun)     
    else:                                    
        return noun + 's'
  1. Rzeczywiście, jest to wyrażenie regularne, jednak używa ono składni, jakiej w rozdziale 7 (Wyrażenia regularne) nie widzieliście. Nawias kwadratowy oznacza: "dopasuj dokładnie jeden z wymienionych tu znaków". A więc [sxz] oznacza "s albo x, albo z", ale tylko jeden znak na raz. Znak $ powinien być wam znany; dopasowuje się on do końca napisu. A więc sprawdzamy tutaj, czy rzeczownik kończy się na jedną z liter s, x lub z.
  2. Funkcja re.sub dokonuje podstawienia w oparciu o wyrażenie regularne. Przyjrzyjmy się jej bliżej.

Przykład 17.2. Wprowadzenie funkcji re.sub

>>> import re
>>> re.search('[abc]', 'Mark')   #(1)
<_sre.SRE_Match object at 0x001C1FA8>
>>> re.sub('[abc]', 'o', 'Mark') #(2)
'Mork'
>>> re.sub('[abc]', 'o', 'rock') #(3)
'rook'
>>> re.sub('[abc]', 'o', 'caps') #(4)
'oops'
  1. Czy napis Mark zawiera jedną z liter a, b lub c? Tak, zawiera a.
  2. W porządku; znajdź a, b lub c i zastąp je literą o. Mark zmienia się w Mork.
  3. Ta sama funkcja zmienia rock w rook.
  4. Może się wydawać, że ta linia zmieni caps w oaps, jednak dzieje się inaczej. Funkcja re.sub zastępuje wszystkie wystąpienia, nie tylko pierwsze. Caps zmienia się w oops ponieważ zarówno c jak i a zostają zastąpione literą o.

Przykład 17.3. Z powrotem do plural1.py

import re

def plural(noun):                            
    if re.search('[sxz]$', noun):             #(1)
        return re.sub('$', 'es', noun)        
    elif re.search('[^aeioudgkprt]h$', noun): #(2)
        return re.sub('$', 'es', noun)        
    elif re.search('[^aeiou]y$', noun):       #(3)
        return re.sub('y$', 'ies', noun)      
    else:                                    
        return noun + 's'
  1. Wróćmy do funkcji plural. Co robimy? Zamieniamy końcówkę napisu na "es". Innymi słowy, dodajemy "es" do napisu. Moglibyśmy osiągnąć ten cel używając dodawania napisów, na przykład stosując wyrażenie: rzeczownik + "es", jednak tutaj wyrażeń regularnych będę używał do wszystkiego, ze względu na spójność oraz z innych powodów, które zostaną wyjaśnione w dalszej części rozdziału.
  2. Patrzcie uważnie, to kolejna nowość. Znak ^ znajdujący się wewnątrz nawiasów kwadratowych oznacza coś szczególnego: negację. [^abc] oznacza "dowolny znak oprócz a, b oraz c". Wyrażenie [^aeioudgkprt] oznacza "każdy znak za wyjątkiem a, e, i, o, u, d, g, k, p, r oraz t. Po tym znaku powinien znaleźć się znak h kończący napis. Tutaj szukamy słów kończących się na H, które można usłyszeć.
  3. Tutaj podobnie: dopasowujemy słowa kończące się na Y, przy czym znak stojący przed Y musi być dowolnym znakiem za wyjątkiem a, e, i, o oraz u. Szukamy słów, które kończą się na Y i brzmią jak I.

Przykład 17.4. Więcej na temat negacji w wyrażeniach regularnych

>>> import re
>>> re.search('[^aeiou]y$', 'vacancy') #(1)
<_sre.SRE_Match object at 0x001C1FA8>
>>> re.search('[^aeiou]y$', 'boy')     #(2)
>>> 
>>> re.search('[^aeiou]y$', 'day')
>>> 
>>> re.search('[^aeiou]y$', 'pita')    #(3)
>>> 
  1. Wyrażenie zostanie dopasowane do "vacancy" ponieważ słowo to kończy się na cy, a c nie jest a, e, i, o ani u.
  2. Nie zostanie dopasowane do "boy", który kończy się na oy, a powiedzieliśmy wyraźnie, że znakiem stojącym przed y nie może być o. "day" nie zostanie dopasowane, ponieważ kończy się na ay.
  3. "pita" również nie zostanie dopasowana, ponieważ nie kończy się na y.

Przykład 17.5. Więcej na temat re.sub

>>> re.sub('y$', 'ies', 'vacancy')              #(1)
'vacancies'
>>> re.sub('y$', 'ies', 'agency')
'agencies'
>>> re.sub('([^aeiou])y$', r'\1ies', 'vacancy') #(2)
'vacancies'
  1. To wyrażenie regularne przekształca "vacancy" w "vacancies" oraz "agency" w "agencies", dokładnie tak, jak chcemy. Zauważmy, że wyrażenie to przekształciłoby "boy" w "boies", gdyby nie fakt, że w funkcji użyliśmy wcześniej re.search, aby dowiedzieć się, czy powinniśmy również dokonać podstawienia przy użyciu re.sub.
  2. Chciałbym nadmienić mimochodem, że możliwe jest połączenie tych dwóch wyrażeń regularnych (jednego, które sprawdza, czy pewna zasada ma zastosowanie, i drugiego, które faktycznie tę zasadę stosuje) w jedno. Wyglądałoby ono dokładnie tak. Większość powinna być już wam znana: aby zapamiętać znak, który stoi przed y, używamy zapamiętanej grupy, o której była mowa w podrozdziale 7.6 (Analiza przypadku: Przetwarzanie numerów telefonów). W podstawianym napisie pojawia się nowa składnia, \1, które oznacza: "czy pamiętasz grupę numer 1? wstaw ją tutaj". W tym przypadku jako znak stojący przed y zostanie zapamiętane c, po czym dokonane zostanie podstawienie c w miejsce c oraz ies w miejsce y. (Jeśli potrzebujemy więcej niż jednej zapamiętanej grupy, używamy \2, \3 itd.)

Podstawienia wyrażeń regularnych stanowią niezwykle silny mechanizm, a składnia \1 czyni go jeszcze silniejszym. Z drugiej strony przedstawienie całej operacji w postaci jednego wyrażenia regularnego sprawiłoby, że stałaby się ona mało czytelna i niewiele by miała wspólnego ze sposobem, w jaki na początku opisywaliśmy sposób konstruowania liczby mnogiej. Utworzyliśmy reguły takie jak "jeśli słowo kończy się na S, X lub Z, dodaj ES" i kiedy teraz patrzymy na funkcję plural, widzimy dwie linijki kodu, które mówią "jeśli słowo kończy się na S, X lub Z, dodaj ES". Nie można tego zrobić bardziej bezpośrednio.