Posts Tagged ‘python’

Python – widziany okiem Javianina

Author: pawel.rychlik (pawel.rychlik) | wrzesień 22nd, 2011
avatar

Całkiem niedawno wpadł mi w ręce podręcznik do Pythona. Książka wyglądała na interesującą, więc długo nie zwlekając wziąłem się za czytanie, chwilę później już pisałem pierwsze Hello-world‘y w dotąd zupełnie mi obcym języku. Z początku nie bardzo wiedziałem, którą wersje Pythona zainstalować – strona python.org oferuje multum możliwości, dostępne jest kilkanaście (kilkadziesiąt?) paczek, przygotowanych pod konkretne platformy i maszyny. Pokierowałem się podpowiedziami znalezionymi w internecie: 3.x to najnowsza odmiana tego języka, 2.x jest starszą wersją (ale wciąż rozwijaną do 2013r.). Aplikacje pisane pod 3.x mogą nie być w pełni kompatybilne z Pythonem serii 2.x (i odwrotnie), ponadto większość zewnętrznych bibliotek była pisana pod starszą wersją, dlatego też zdecydowałem się na „bezpieczne” 2.6.7.

Nagłówek artykułu sugeruje, że będę starał się dokonać porównania Javy i Pythona. Przecież to dwa kompletnie różne światy. Niemniej jednak postaram się w skrócie nakreślić czym cechuje się Python i opisać co poniektóre ciekawsze rozwiązania, z którymi spotkałem się podczas mojej krótkiej i burzliwej znajomości z tym językiem.

Intro

W odróżnieniu do Javy, Python jest językiem typowanym dynamicznie: zmiennych się nie deklaruje i tym samym nie określa się ich typu. Znaczy to mniej więcej tyle, że do tej samej zmiennej można przypisywać obiekty różnych klas bez potrzeby rzutowania, nie narażając się na odpowiednik javowego ClassCastException. Skoro już mowa o klasach – Python jest w pełni obiektowy, a więc typy proste (bool, int, float, complex) również są obiektami. Python jest językiem interpretowanym, nie wymagającym kompilacji (ten krok jest opcjonalny – interpreter może generować skompilowany kod do plików *.pyc). Fakt ten może spodobać się weteranom Javy – przecież nieraz budowanie złożonych projektów potrafiło zająć ładne parenaście minut. Jednak dynamiczne typowanie w połączeniu z brakiem konieczności kompilacji (który de facto często w pewnym stoponiu weryfikuje poprawność kodu) i nieuwagą programisty może przyczynić się do powstawania błędów. Ale to temat na osobną dyskusję.

Liczby.

Cechą, której nie miałem okazji specjalnie wykorzystać, a która bardzo pozytywnie mnie zaskoczyła, jest wbudowana obsługa liczb zespolonych. Dostępny mamy typ prosty complex.

>>> 1j * 1J
(-1+0j)
>>> 1j * complex(0,1)
(-1+0j)
>>> a=1.5+0.5j
>>> a.real
1.5
>>> a.imag
0.5

Kolejną ciekawostką jest fakt, że ograniczeniem długości liczby typu long jest w zasadzie tylko ilość dostępnej pamięci operacyjnej. Dostajemy zatem „out-of-the-box” możliwość wyrażania odległości międzygalaktycznych w milimetrach. Tylko co, jeśli będziemy chcieli przekazać obliczony wynik na inną maszynę?

(), [], {}.

Zdarza się, że potrzebujemy, aby metoda zwróciła więcej niż jeden obiekt. Rzecz jasna mowa tu nie o liście/tablicy, ale o obiektach różnych typów. Jednocześnie nie widzimy sensu tworzenia nowej klasy, która i tak byłaby wykorzystana tylko w tym jednym miejscu. Wtedy (na przykładzie Javy) zaczyna się kombinowanie: może przekazywać jeden obiekt przez parametr, w metodzie wypełniać go danymi, a drugą część informacji zwracać przez return statement?; może przejrzeć pakiet *utils w poszukiwaniu generycznej klasy Pair<A,B> lub Trio<A,B,C> i we wspomnianej metodzie zwracać właśnie obiekt takiej klasy? a może spróbować użyć mapę?

Zdaje się, że m.in. wówczas przydałyby się znane z Pythona krotki – niemodyfikowalne sekwencje referencji dostępnych niezależnie. Dostęp do poszczególnych elementów odbywa się przez podanie indeksu (numeracja od zera).

>>> tuple = 12345, 54321, 'hello!'
>>> tuple
(12345, 54321, 'hello!')
>>> tuple = (12345, 54321, 'hello!')
>>> tuple
(12345, 54321, 'hello!')
>>> tuple[0]
12345

Idąc za ciosem – kolejnym typem danych jest lista, oznaczana przy pomocy nawiasów kwadratowych. Bardzo podobna do krotki, jednak daje możliwość modyfikacji swoich danych.

>>> list = ['spam', 'eggs', 100, 1234]
>>> list
['spam', 'eggs', 100, 1234]

Słowniki, które w Pythonie tworzy się przy użyciu nawiasów klamrowych, prezentują nieco inny sposób grupowania danych. Podczas gdy krotki i listy są numerowane na podstawie indeksów liczbowych, słowniki do indeksacji stosują nazwy – mogą to być litery, ciągi znaków lub symbole. Słowniki można porównać do java.util.Map, dane wkładamy podając klucz oraz odpowiadającą mu wartość, klucze muszą być unikatowe, jednak pod jednym kluczem wystąpić może wiele wartości.

>>> tel = {'jack': 4098, 'sape': 4139}
>>> tel['guido'] = 4127
>>> tel
{'sape': 4139, 'guido': 4127, 'jack': 4098}
>>> tel['jack']
4098
>>> del tel['sape']
>>> tel['irv'] = 4127
>>> tel
{'guido': 4127, 'irv': 4127, 'jack': 4098}

Ciekawym niuansem jest możliwość dostępu do elementów sekwencji podając ujemną wartość indeksu. W Javie chcąc pobrać ostatni element listy, potrzeba mniej więcej takiego kodu: list.get(list.size() – 1); podczas gdy w Pythonie wystarczy: list[-1]. Przechodząc kolejno elementy listy użylibyśmy indeksów od 0 do len(list)-1. Ten sam efekt można osiągnąć iterując od -len(list) do -1. Taka mała rzecz, a cieszy.

Istnieją również skróty na „krojenie” sekwencji. W Javie skorzystalibyśmy z metody List.subList(), która zwraca widok oryginalnej listy. Odpowiadająca temu instrukcja Pythona to zaledwie: list[a:b], gdzie a i b to indeks początkowy i końcowy nowej listy.

s = "ABCDEF"
s[2:5]  --> "CDE"
s[4:]   --> "EF"
s[:2]   --> "AB"
s[:-1]  --> "ABCDE"
s[-1:]  --> "F"
s[:]    --> "ABCDEF"

Kolejny interesujący feature to funkcja range(a, b, c), która zwraca listę liczb od a do b (z opcjonalnym krokiem c), i która często przydaje się przy definiowaniu pętli for..in. Funkcja xrange() spełnia taką samą rolę, jednak poleca się ją stosować przy dużych rozmiarach list: zamiast alokować zbędnie duży fragment pamięci tylko po to, aby trzymać w niej uporządkowane wartości – xrange() na potrzeby iteracji tworzy tzw. lazy-list, która generuje i udostępnia jedynie elementy potrzebne w danej chwili.

# liczenie silni ze 100:
product = 1L
for i in xrange(1, 100):
    product = product * i

Pozostałe małe plusiki

Bardzo istotnym elementem kodu Pythona są białe znaki, a konkretnie wcięcia. To one określają bloki kodu. Z jednej strony jest to plus – mniej pisania, mniej linii kodu. Jednak błędnie zaaplikowane wcięcia w kodzie (np. wynikające z popularnej techniki copy-paste) mogą spowodować ciężkie do wytropienia błędy.

Świetnym ułatwieniem, które przydałoby się w innych językach, jest możliwość szeregowania (chaining) operatorów porównań:

>>> x = 5
>>> 1 < x < 10
True

Porównania wartości String’ów w Pythonie to kwestia użycia operatorów == lub !=. Daje to sporo poziom przejrzystości kodu, w porównaniu do Javy, gdzie wykorzystalibyśmy String.equals(), compare() lub compareTo().

Podobnie jak w Scali, w Pythonie możliwe jest deklarowanie funkcji wewnątrz funkcji. Zamiast powielać ten sam fragment kodu, lepiej zdefiniować i wywołać nową funkcję. Taka fragmentacja szybko mogłaby doprowadzić do zmniejszenia czytelności interfejsu danej klasy (multum krótkich metod), toteż warto zastanowić się nad umieszczeniem nowej funkcji w ciele funkcji, w której będzie ona używana. Korzystać również możemy z funkcji lambda (któtkich funkcji anonimowych), znanych m.in. z C# i Scali.

„Talk less, more action”

Podaje się, że pisząc w Pythonie, można być 5-10 razy bardziej produktywnym niż w Javie. Trudno się temu dziwić, jeśli otwarcie i odczytanie pliku tekstowego w Pythonie sprowadza się do:

file = open('plik')
file.read()

Podczas gdy w Javie należałoby zadeklarować odpowiedni bufor, utworzyć strumień podając ścieżke pliku, prawdopodobnie czytać fragmentami w pętli, całość okraszając tłustą warstwą try {} catch().

Python pozwala na dużą swobodę. Jest językiem skryptowym, ale nie oznacza to, że nie można pisać w nim kodu zorientowanego obiektowo. Oferuje zaawansowaną modularność (wprowadza pojęcie modułu i pakietu), choć właściwie całą, nawet bardziej skomplikowaną aplikację można zawrzeć w jednym pliku *.py.

Python daje duże możliwości. Może być stosowany przy projektach z wielu dziedzin, jako język podstawowy, jak i uzupełniający. Zbudujemy w nim GUI (pyGTK), napiszemy testy jednostkowe (pyUnit), utworzymy zwięzły i treściwy skrypcik użytkowy, dostarczymy plugin do Firefox’a, tudzież rozszerzenie do Blender’a, połączymy się z relacyjną bazą danych, obsłużymy dokumenty XML (wbudowane parsery SAX i DOM, narzędzia walidacji, transformacji XML, dodatkowy pakiet PyXML), wreszcie w parudziesięciu liniach zmieścimy prostego klienta email, czy komunikator sieciowy, postawimy aplikację webową (framework Django), w międzyczasie korzystając z bibliotek pisanych w C lub C++.

Ok. Gdzie jest haczyk?

Python może nie wydawać się stabilną platformą, jeśli w danym momencie korzysta się z elementów, które z czasem się zmieniają. Jeśli chce się budować większą aplikację, warto trzymać się jednej wersji języka. Niestety takie podejście też nie jest najlepsze, bo:

  • Nowsze wersje bibliotek zazwyczaj powstają tylko dla najnowszych wersji Pythona
  • Pewne nowe elementy Pythona mogą spowodować subtelne zmiany w działaniu aplikacji, które nie dadzą się łatwo wykryć. To z pewnością nie sprzyja biznesowi.

Weźmy konkretny przykład. W Pythonie 2.3.4 i wcześniejszym poniższy snippet powoduje wygenerowanie wyniku, którego oczekuje się po programie napisanym w C:

>>> print "%x" % -2
fffffffe

Poczynając od wersji 2.4 widzimy z goła odmienny wynik:

>>> print "%x" % -2
-2

Przedstawiona zmiana może wydawać się błaha, ale tak nie jest. W nowszej wersji Pythona zmianie uległ sposób traktowania liczb całkowitych bez znaku. Ponieważ z liczb całkowitych korzysta się nawet w najmniejszym skrypcie, tego rodzaju zmiana może nieść kolosalne konsekwencje.

Innymi słowy – Python jest na tyle młodym językiem, że jego twórcy na razie nie przejmują się kwestiami zgodności i stabilności (przynajmniej nie tak bardzo jak w Javie, w której cykle życia oprogramowania obejmują niejednokrotnie 10 lat).

Python to nie tylko maszyna wirtualna, API i składnia – to także zbiór bibliotek otaczających podstawowe funkcje systemu operacyjnego. Z założenia skrypty pythonowe mają być przenośne pomiędzy systemami operacyjnymi i hardware’m. Często ochrona przed różnicami pomiędzy systemami jest iluzoryczna, zwłaszcza w kwestii obsługi protokołów sieciowych i wielowątkowości. Np. interfejs gniazd sieciowych, stosowany w wielu programach, zgłasza inne wyjątki pod Windows, a inne pod *nix. Więc albo wyłapuje się wszystkie wyjątki, albo stosuje się osobny kod dla tych przypadków.

Z mojego, subiektywnego punktu widzenia mechanizm dołączania dodatkowych zewnętrznych bibliotek do instancji Pythona jest niekiedy mocno przesadzony. Nie wszystkie biblioteki wymagają instalacji – niektóre zwyczajnie wrzucamy do katalogu PythonXX\Lib\site-packages i po wykonaniu odpowiedniej instrukcji importu od razu są widoczne z poziomu interpretera. Inne biblioteki dostarczają cały instalator, który teoretycznie powinien wszystkim się zająć.
Ku przestrodze: Instalacja Pythona 2.6.7 pod 64bitowym Windows7 nie powinna stwarzać problemów. Jednak potem doinstalowanie dodatkowych modułów może okazać się już nie takie proste – biblioteka szuka w rejestrze informacji o ścieżkce instalacji wymaganej wersji Pythona – i nic nie znajduje. Wtedy niezwykle irytujący staje się fakt, że byle biblioteka (której kod pewnie mieści się na paru ekranach), z wielce wyszukanym instalatorem, jest nie do zainstalowania, choćbyśmy ściągali coraz to inne wersje i samego Pythona i rozszerzeń. Jakby tego było mało – deinstalacja pod Win7 trwa ~8minut, i zostawia po sobie multum śmieci, m.in. w katalogach systemowych.

Dużym minusem jest brak możliwości debuggowania wątków (oczywiście można próbować to „obejść” przy pomocy logger’a, lub pisząc dokładne testy jednostkowe).

Outro

Python jest narzędziem zorientowanym na maksymalną zwięzłość, prostotę i wysoki stopień abstrakcji. Dzięki temu pozwala na szybkie i efektywne tworzenie aplikacji. Pozwala skupić się na samej funkcjonalności programu, odkładając kwestie niskopoziomowe na dalsze tło. Byłby świetnym wyborem, kiedy przyjdzie potrzeba napisania na szybko prostego skryptu, czy niewielkiej aplikacji w ramach uczelnianego projektu. Zdaje się, że nie jest to jeszcze język na tyle dojrzały, ustabilizowany i konsekwentny, aby na jego podstawie pisać rozbudowane aplikacje biznesowe o długim cyklu życia. Chyba, że jako wyjątkowo wysoko-poziomowy język uzupełniający, stanowiący niewielką część całości aplikacji.

Polecam

http://stackoverflow.com/questions/101268/hidden-features-of-python

Why I hate Django

Author: admin (admin) | październik 29th, 2008
avatar

DjangoCon i Cal Henderson z Flickr, zobaczcie bo warto:
(więcej…)