Programowania funkcyjnego z Pythonem spotkania

Jacek Laskowski opublikował wczoraj na swoim blogu ciekawą notkę Programowania funkcyjnego z Clojure początki niełatwe (szczególnie mentalnie). Myśli o programowaniu funkcyjnym siedzą mi w głowie od ponad roku, więc przeczytałem ją z zainteresowaniem. Na końcu zamieścił małe wyzwanie: by przepisać podanego przez niego jednolinijkowca do Javy.

(doseq [linia (map (fn [[h s]] (str h " (" s ")")) (partition 2 [1 2 3 4 5 6 7 8 9 0]))] (println linia))

Java to nie jest mój ulubiony język, więc postanowiłem sprawdzić jak by dało się to zrobić w Pythonie (który posiada lekkie wsparcie tego paradygmatu funkcyjnego).

Do dzieła

Na początek mały problem – Python nie posiada wbudowanej funkcji partition. Trzeba ją samemu napisać. Na przykład tak:

def partition(size, seq):
    result = []
    for i in seq:
        result.append(i)
        if len(result) == size:
            yield tuple(result)
            result = []

Uwaga: to jest odpowiednik clojure.core/partition(n coll). Nie obsługuje ani step ani pad.

Potem już idzie z górki:

print "\n".join(map(lambda t: "%i (%i)" % t, partition(2, [1, 2, 3, 4, 5, 6, 7, 8, 9, 0])))

Można to napisać nawet bardziej w stylu Pythona (poprzez zastąpienie funkcji map i lambda przez listę składaną):

print "\n".join(["%i (%i)" % t for t in partition(2, [1, 2, 3, 4, 5, 6, 7, 8, 9, 0])])

Programowanie funkcyjne?

Kod, który w Clojure zajmował 106 znaków dało się skrócić do odpowiednio 92 i 87 znaków.

Co więcej udało się z niego usunąć efekty uboczne i sekwencyjność wprowadzoną przez doseq. Cały napis jest budowany w pamięci a dopiero następnie wyświetlany – co jest bardziej funkcyjne.

W Clojure dało by się to zrobić za pomocą kodu:

(println (apply str (interpose "\n" (map (fn [[h s]] (str h " (" s ")")) (partition 2 [1 2 3 4 5 6 7 8 9 0])))))

Przy okazji: to jest prawdopodobnie pierwsza linijka mojego kodu w tym języku więc mogła by być lepsza.

Automatyzacja Komodo Edit/IDE – makro w Pythonie

Często zdarza się, że w trakcie programowania występuje potrzeba wykonania prostej acz pracochłonnej i czasochłonnej czynności. Dobre edytor umożliwia automatyzację takich zadań.

Mam ostatnio okazję pracować z Komodo Edit (darmowa wersja Komodo IDE). By nie marnować czasu napisałem proste makra, których szkielet przedstawię poniżej.

Dodawanie makra

Dodanie makra sprowadza się do wykonania następujących czynności:

  1. Wyświetlamy Toolbox (View > Tabs & Sidebars > Toolbox)
  2. W Toolbox klikamy na przycisk Add Item to Toolbox a następnie wybieramy New Macro
  3. Zmieniamy język na Python, ustawiamy nazwę makra. Możemy też zmienić jego ikonę, ustawić dla niego skrót klawiaturowy. Dodajemy kod makra i zapisujemy je klikając OK.
  4. Makro uruchamia się klikając na nie w Toolbox lub korzystając z przypisanego skrótu klawiaturowego.

Kod makra

W praktyce używam tylko dwóch rodzajów makr. Pierwsze z nich przetwarzają („filtrują”) zaznaczony tekst. Drugie „wykonują” polecenia zawarte w aktualnej linii.

Poniżej zawarty jest kod realizujący oba te sposoby.

import komodo

def replace_selection(fn):
    scimoz = komodo.editor
    sel_start = min(scimoz.currentPos, scimoz.anchor)
    text = fn(scimoz.selText)
    scimoz.replaceSel(text)
    scimoz.anchor = sel_start
    scimoz.currentPos = sel_start + len(text)

def replace_line(fn):
    scimoz = komodo.editor
    scimoz.lineEnd()
    scimoz.homeExtend()
    text = fn(scimoz.selText)
    scimoz.replaceSel(text)

# Jedną z poniższych linii należy zakomentować
replace_selection(lambda t: t.replace(u' ',u'\n'))
replace_line(lambda t: u'>>' + t)

Więcej możliwości

Oczywiście powyższe przykłady zastosowań są bardzo proste. W praktyce można napisać dowolną funkcję, robiącą dokładnie to czego chcemy.

Pełną dokumentację można znaleźć na stronie Macro API Reference.

Jeśli napiszemy więcej makr lub często z nich korzystamy to możemy stworzyć dla nich własny Toolbar (New Custom Toolbar) lub Menu (New Custom Menu).

Curing Python’s Neglect – polemika

Wczoraj Zed Shaw na swoim blogu napisał artykuł Curing Python’s Neglect. Jak można się domyślić z tytułu ma on na celu pokazanie niedbałości w języku. Z niektórymi uwagami muszę się zgodzić, lecz spora ich część jest nieprawdziwa lub nieaktualna.

Operacje na listach

Pierwszy z przykładów omawia operowanie na listach:

mystuff.append(mything) 
mystuff.remove(mything) 
# to ponoć jest nielogiczne
del mystuff[4]

Absolutnie nie mogę się zgodzić z tym zarzutem. Metody append(), remove(), index() jako parametr dostają wartość, która ma być (odpowiednio) dołączona, usunięta lub znaleziona w liście.

Natomiast jeśli mamy zamiar korzystać z indeksu elementu w liście należy użyć nawiasów kwadratowych i to nie zależnie czy chcemy pobrać, ustawić czy usunąć element o danym numerze.

foo = ['ala', 'ma']
# wszystkie poniższe metody mają parametr będący wartością
foo.append('kota') 
foo.remove('ala') 
foo.index('ma')
foo.extend(('i', 'kota'))
foo.count('kota')

# natomiast tak natomiast pracuje się z indeksami
a = foo[3]
foo[3] = 'psa'
del foo[1]

Obsługa argumentów linii poleceń

Podobnie nie rozumiem narzekań w sprawie modułu optparse. Zasadniczo Python udostępnia dwie standardowe metody obsłużenia argumentów.

Pierwszym z nich jest moduł getopt, który jest wzorowany na Unixowej funkcji getopt(), jest też dostępny w Bashu czy Perlu. Dzięki temu programiści znający te języki mają o wiele łatwiej przenieść się do Pythona. Dodatkowo jest on bardzo prosty w użyciu.

Istnieje też moduł optparse optparse, udostępniający większe możliwości kosztem większej złożoności.

Obsługa daty

Rzeczywiście, obsługa dat nie należy do najprzyjemniejszych rzeczy jakie może mieć programista do zrobienia. Lecz nie widzę aby cokolwiek miało czynić ją trudniejszą w Pythonie – wręcz przeciwnie, biblioteka standardowa bardzo ułatwia sprawę. Moduł odpowiadający za nią nazywa się datetime

Autor narzekał brak możliwości parsowania daty dziewięć la temu. Teraz jest to trywialnie proste, dla przykładu:

from datetime import datetime

a = datetime.strptime('2009-05-31', '%Y-%m-%d')
print a   # 2009-05-31 00:00:00

b = datetime.strptime('05/31/2009 22:08:23', '%m/%d/%Y %H:%M:%S')
print b   # 2009-05-31 22:08:23

Podsumowanie

Nie ma idealnego języka programowania więc i w Pythonie znajdą się niedociągnięcia i błędy projektowe. Lecz niestery Zed Shaw powołuje się na język taki jakim był on w roku 2000, a to cała epoka do tyłu. Popełnia też standardowy błąd człowieka dopiero poznającego dany język, czyli: ja bym to zrobił lepiej, na przykład tak jak jest w moim ulubionym języku.

Co do sugestii o tym, że część języka i biblotek wymaga poważnych zmian – zostały one już dawno zauważone. Efektem tego jest powstanie wersji 3.0 języka, która nie jest zgodna wstecznie, ale za to naprawia wiele błędów projektowych.