Programowanie funkcyjne a obiektowe¶
W tej części omówiono programowanie funkcyjne. Na zakończenie warto porównać ten paradygmat programowania. do przedstawionego w poprzedniej części programowania obiektowego. W tym celu posłużymy się zadaniem, którego celem jest znalezienie wyrazów, które najczęściej występują w książce J.R.R. Tolkiena „Władca Pierścieni” tom I, dostęnej jako plik tekstowy.
Problem¶
Dany jest plik tekstowy lotr1-utf8.txt. Należy:
odczytać tekst
podzielić go na słowa
oczyścić ze znaków interpunkcyjnych
zamienić na małe litery
odfiltrować krótkie słowa
policzyć częstość występowania
wypisać 30 najczęstszych
Styl deklaratywny¶
Najbardziej bezpośrednie rozwiązanie polega na napisaniu kodu krok po kroku:
1def sort_by_count(key):
2 return word_counts[key]
3
4
5plik = open("lotr1-utf8.txt", "r")
6word_counts = {}
7for line in plik:
8 tokens = line.split()
9 for word in tokens:
10 word = word.lower()
11 word = word.strip(":;,.'`[]()-_")
12 if len(word) < 3: continue
13 if word in word_counts:
14 word_counts[word] += 1
15 else:
16 word_counts[word] = 1
17
18counted_items = word_counts.keys()
19result = sorted(counted_items, key=sort_by_count, reverse=True)
20print(result[:30])
21
Charakterystyka
jawne pętle
forręczne zarządzanie strukturami danych (słownik)
instrukcje warunkowe i zmiana stanu
kolejność operacji dokładnie określona
Zalety
prostota i czytelność dla początkujących
pełna kontrola nad wykonaniem
łatwe debugowanie krok po kroku
Wady
trudniej wydzielić logiczne etapy przetwarzania
kod mniej modularny
Styl obiektowy¶
W podejściu obiektowym organizujemy kod wokół obiektu, który przechowuje stan i udostępnia operacje:
1
2class LiczSlowa:
3 def __init__(self):
4 self.__counts = {}
5
6 def add(self, word):
7 word = word.strip(":,;'`.[]()-_").lower()
8 if word not in self.__counts: self.__counts[word] = 0
9 self.__counts[word] += 1
10
11 def words_sorted(self, if_reverse=True):
12 counted_items = self.__counts.keys()
13 return sorted(counted_items, key=self.__sort_by_count, reverse=if_reverse)
14
15 def __getitem__(self, word):
16 return self.__counts[word]
17
18 def __sort_by_count(self, key):
19 return self.__counts[key]
20
21
22if __name__ == "__main__":
23
24 licznik = LiczSlowa()
25
26 plik = open("lotr1-utf8.txt", "r")
27 for line in plik:
28 tokens = line.split()
29 for word in tokens:
30 licznik.add(word)
31 print(licznik.words_sorted()[:30])
Charakterystyka
dane (słownik) są ukryte wewnątrz klasy
operacje są metodami obiektu
enkapsulacja (prywatne pole
__counts)interfejs:
add(),words_sorted()
Zalety
dobra organizacja kodu
enkapsulacja danych
możliwość rozszerzania (np. inne metody analizy)
Wady
dużo więcej kodu do napisania
operacje są „rozproszone” po metodach
trudniej zobaczyć cały przepływ danych
Styl funkcyjny¶
W podejściu funkcyjnym program jest budowany jako łańcuch transformacji danych. Program ten deklaruje operacje
map() transformujące dane oraz filtr pomijający wyrazy zbyt krótkie. Samo zlicznie wykonuje operacja reduce().
Operacje układają się w protokół:
plik → słowa → usunięcie interpunkcji → filtr długości → lower() → zliczanie → sortowanie
1from functools import reduce
2
3
4def read_words(fname: str):
5 with open(fname, "r", encoding="utf-8") as f:
6 for line in f:
7 for word in line.strip().split():
8 yield word
9
10
11punct_to_remove = ",.;:()[]-_"
12def clean_punc(word: str):
13 return word.rstrip(punct_to_remove)
14
15
16# Reducer function
17def count_words(acc, word):
18 acc[word] = acc.get(word, 0) + 1
19 return acc
20
21
22no_punc_words = map(clean_punc, read_words("lotr1-utf8.txt"))
23long_enough = filter(lambda w: len(w)>2, no_punc_words)
24all_lowercase = map(lambda w: w.lower(), long_enough)
25word_counts = reduce(count_words, all_lowercase, {})
26
27top_words = sorted(word_counts.items(), key=lambda item: item[1], reverse=True)[:30]
28
29# Print the top 10 words
30for word, count in top_words:
31 print(f"{word}: {count}")
32
Charakterystyka
brak jawnych pętli (zastąpione przez
map,filter,reduce)dane przepływają przez kolejne transformacje
funkcje są przekazywane jako argumenty
Zalety
wyraźne oddzielenie etapów przetwarzania
kod bardziej deklaratywny („co” zamiast „jak”)
łatwe komponowanie operacji
dobre dopasowanie do przetwarzania danych
Wady
dla początkujących mniej intuicyjny, np operacja
reduce()debugowanie kodu może być trudniejsze
nie w pełni idiomatyczny w Pythonie
Podsumowanie¶
Jak widzisz, każdy z trzech stylów reprezentuje inne podejście do programowania:
styl deklaratywny: opis krok po kroku
styl obiektowy: organizacja wokół obiektów i stanu
styl funkcyjny: transformacje danych przez funkcje