Zanurkuj w Pythonie/plural.py, etap 5

Z Wikibooks, biblioteki wolnych podręczników.

Usunęliśmy już z kodu powtórzenia i dodaliśmy wystarczająco dużo abstrakcji, aby reguły zamiany rzeczownika na liczbę mnogą znajdowały się na liście napisów. Następnym logicznym krokiem będzie umieszczenie tych napisów (definiujących reguły) w osobnym pliku, dzięki czemu będzie można utrzymywać listę reguł niezależnie od używającego ją kodu.

Na początku utworzymy plik tekstowy zawierający reguły. Nie ma tu złożonych struktur, to po prostu napisy rozdzielone spacjami (lub znakami tabulacji) w trzech kolumnach. Plik ten nazwiemy rules.en; "en" oznacza "English", reguły te dotyczą bowiem rzeczowników języka angielskiego. Później będziemy mogli dodać pliki z regułami dla innych języków.

Przykład 17.15. rules.en

[sxz]$                  $               es
[^aeioudgkprt]h$        $               es
[^aeiou]y$              y$              ies
$                       $               s

Zobaczmy, jak możemy użyć tego pliku.

Przykład 17.16. plural5.py

import re
import string                                                                     

def buildRule((pattern, search, replace)):                                        
    return lambda word: re.search(pattern, word) and re.sub(search, replace, word) #(1)

def plural(noun, language='en'):                             #(2)
    lines = file('rules.%s' % language).readlines()          #(3)
    patterns = map(string.split, lines)                      #(4)
    rules = map(buildRule, patterns)                         #(5)
    for rule in rules:                                      
        result = rule(noun)                                  #(6)
        if result: return result
  1. W dalszym ciągu używamy tutaj techniki dopełnień (dynamicznego budowania funkcji, które używają zmiennych zdefiniowanych na zewnątrz funkcji), jednak teraz połączyliśmy osobne funkcje dopasowującą oraz modyfikującą w jedną. (Powód, dla którego to zrobiliśmy, stanie się jasny w następnym podrozdziale). Pozwoli to nam osiągnąć ten sam cel, jaki osiągaliśmy przy użyciu dwóch funkcji, będziemy jedynie musieli, jak zobaczymy za chwilę, trochę inaczej tę nową funkcję wywołać.
  2. Funkcja plural pobiera teraz opcjonalny parametr, language, który ma domyślną wartość en.
  3. Parametru language używamy do skonstruowania nazwy pliku, następnie otwieramy ten plik i wczytujemy jego zawartość do listy. Jeśli language ma wartość en, wówczas zostanie otworzony plik rules.en, wczytana jego zawartość, podzielona na podstawie znaków nowego wiersza, i zwrócona w postaci listy. Każda linia z pliku zostanie wczytana jako jeden element listy.
  4. Jak widzieliśmy wcześniej, każda linia w pliku zawiera trzy wartości, które są oddzielone białymi znakami (spacją lub znakiem tabulacji, nie ma to znaczenia). Odwzorowanie powstałej z wczytania pliku listy przy pomocy funkcji string.split pozwoli na utworzenie nowej listy, której elementami są trzyelementowe krotki. Linia taka, jak: "[sxz]$ $ es" zostanie zamieniona w krotkę ('[sxz]$', '$', 'es')[1] Oznacza to, że w zmiennej patterns znajdą się trzyelementowe krotki zawierające napisy, dokładnie tak, jak to wcześniej, na etapie 4, zakodowaliśmy na sztywno.
  5. Jeśli patterns jest listą krotek, to rules będzie listą funkcji zdefiniowanych dynamicznie przy każdym wywołaniu buildRule. Wywołanie buildRule(('[sxz]$', '$', 'es')) zwróci funkcję, która pobiera jeden parametr, word. Kiedy zwrócona funkcja jest wywoływana, zostanie wykonany kod: re.search('[sxz]$', word) and re.sub('$', 'es', word).
  6. Ponieważ teraz budujemy funkcję, która łączy w sobie dopasowanie i modyfikację, musimy trochę inaczej ją wywołać. Jeśli wywołamy tę funkcję i ona coś nam zwróci, to będzie to forma rzeczownika w liczbie mnogiej; jeśli zaś nie zwróci nic (zwróci None), oznacza to, że reguła dopasowująca nie zdołała dopasować wyrażenia do podanego rzeczownika i należy w tej sytuacji spróbować dopasować kolejną regułę.

Poprawa kodu polegała na tym, że udało nam się całkowicie wyłączyć reguły tworzenia liczby mnogiej do osobnego pliku. Dzięki temu nie tylko będzie można utrzymywać ten plik niezależnie od kodu, lecz także, dzięki wprowadzeniu odpowiedniej notacji nazewniczej, używać funkcji plural z różnymi plikami zawierającymi reguły dla różnych języków.

Wadą tego rozwiązania jest fakt, że ilekroć chcemy użyć funkcji plural, musimy na nowo wczytywać plik z regułami. Myślałem, że uda mi się napisać tę książkę bez używania frazy: "zostawiam to jako zagadnienie jako ćwiczenie dla czytelników", ale nie mogę się powstrzymać: zbudowanie mechanizmu buforującego dla zależnego od języka pliku z regułami, który automatycznie odświeża się, jeśli plik z regułami zmienił się między wywołaniami, zostawiam jako ćwiczenie dla czytelników. Bawcie się dobrze!

Przypisy

  1. Z tego co sie orientuje to zarówno metoda 'split' obiektu 'str' jak i funkcja 'split' modułu 'string' zwracają listy nie krotki. W rezultacie otrzymany: [['[sxz]$', '$', 'es'], ['[^aeioudgkprt]h$', '$', 'es'], ['[^aeiou]y$', 'y$', 'ies'], ['$', '$', 's']]. Czyżby autor sie pomylił?