IronPython - wstęp do języka Python, cz. 1/2  

Udostępnij na: Facebook

Autor: Bartosz Kierun

Opublikowano: 2010-12-29

Artykuł ten jest kolejnym z cyklu artykułów poświęconych wsparciu technologii .NET dla dynamicznych języków programowania na przykładzie języka Python.

IronPython to pełnoprawna implementacja języka Python. Zanim więc poruszone zostaną bardziej zaawansowane możliwości jego użycia lub integracji z platformą .NET, warto pokusić się o bardziej dokładne wprowadzenie do samego języka.

Po przeczytaniu niniejszej publikacji poznasz:

  • System typów języka IronPython.
  • Podstawowe kolekcje.
  • Podstawowe konstrukcje języka, takie jak pętle czy wyrażenia warunkowe.
  • Elementy programowania funkcyjnego, proceduralnego oraz obiektowego.
  • Sposób obsługi błędów.
  • Strukturę programów tworzonych przy użyciu Pythona.
  • Wybrane elementy standardowych bibliotek dostarczanych z tym językiem.

Wstęp

Programowanie to sztuka, której można uczyć się wiele lat. Znajomość składni, konstrukcji i możliwości samego języka to tylko pierwszy krok na długiej i wyboistej drodze do tworzenia eleganckiego i łatwego w rozbudowie oraz utrzymaniu kodu.

Programy pisane w języku Python mogą być zarówno prostymi, złożonymi z paru linijek kodu skryptami, jak i pełną obiektowych wzorców projektowych aplikacją. Choć ten brak narzuconych konwencji wielu programistów mogłoby uznać za wadę, to twórcy tego języka przyświecała raczej inna idea – niech nadrzędną wartością pozostanie po prostu czysta radość programowania, niezależnie od tego, czy tworzymy prosty skrypt, czy zaawansowany system informatyczny (choć niewątpliwie w tym drugim przypadku konstrukcje wspierające naszą zdolność do „zapanowania” nad większą ilością kodu niewątpliwie się przydają). 

Dalsza części artykułu zakłada, że Czytelnik posiada już podstawową znajomość tajników programowania, ale nie jest wymagana dokładna znajomość żadnego konkretnego języka, co powinno sprawić, że zawarte tu informacje mogą przydać się zarówno programiście używającemu do tej pory VBScript, jak i programiście C#.

Podstawowe informacje dotyczące składni języka

Python wspiera kilka technik programistycznych. Wiele prostych programów może poradzić sobie bez żadnej definicji klasy, a w skrajnych przypadkach nawet i funkcji. Dla programistów lubiących tworzyć zaawansowane algorytmy mogą się przydać, wspierane przez język Python, elementy programowania funkcyjnego, choć ich nadużywanie może zmniejszać nieco czytelność kodu. Na szczególnym miejscu w arsenale środków używanych do budowy większych programów stoi zaś niewątpliwie programowanie obiektowe, które to również wspierane jest w omawianym przez nas języku. Dokładniejsza analiza tych technik wykracza jednak poza ramy tego artykułu.

Podstawowy model programowania w języku Python nie różni się zbytnio od modelu znanego z wielu innych imperatywnych języków programowania, takich jak C#, Java czy nawet Visual Basic. Jedną z bardziej odczuwalnych różnic, która może nieco zrazić programistów „dojrzalszych” języków takich jak C++, Java czy C# (ale już nie VB) jest fakt, że do rozdzielania bloków kodu służą po prostu wcięcia. Oto prosty przykład kodu wykorzystujący instrukcję warunkową if:

if warunek == True:

funkcja()

Z raczej oczywistych powodów nie musimy też kończyć każdej linii kodu średnikiem. Jaki jest stosunek twórcy języka do nawiasów klamrowych, można przekonać się, wykonując polecenie:

from __future__ import braces

Po wykonaniu powyższej instrukcji naszym oczom ukaże się komunikat: „not a chance”, co jest jednym z wielu żartobliwych elementów języka Python.

W obiektowych językach programowania wszystko jest obiektem, a każdy obiekt posiada swój typ danych. Te zupełnie podstawowe – takie jak typy numeryczne (integer, float) lub łańcuchy znaków (string) – są dostarczone przez twórcę języka, a te bardziej dostosowane do wymagań naszej aplikacji możemy oczywiście stworzyć sami w postaci klas. Na jednym końcu kolekcji gotowych typów danych mamy typy prymitywne, na jej drugim zaś końcu otrzymujemy bardziej złożone typy danych, takie jak np. kolekcje (lista, słownik, krotka).

Przegląd typów danych

Język Python dostarcza w miarę szeroki wachlarz mniej i bardziej zaawansowanych typów danych, które będziemy wykorzystywać w czasie pisania programów. Zestaw absolutnie podstawowych i niezbędnych w czasie tworzenia niemal każdej aplikacji typów danych znajduje się w poniższej tabeli. Tak egzotycznymi typami danych jak np. frozenset zajmiemy się przy innych okazjach.

**Tabela1. Wybrane wbudowane typy danych języka Python.

       Typ danych         Nazwa typu             Składnia              Uwagi
Łańcuch znaków str, unicode

‘jakiś napis’

‘’jakiś inny napis’’

‘’’a to już wielolinijkowy napis’’’

W IronPythonie oba te typy reprezentowane są przez jeden typ danych – System.String, który natywnie wspiera Unicode.
Liczba całkowita int

3

-3

 
Duża liczba całkowita long

6666666666666666L

3333L

 
Liczba zmiennoprzecinkowa float

0.0

-5.6

3e10

-3.567e-10

 
Liczba zespolona complex

1 + 1j

-3 + 4j

 
Lista list

[]

[2, 4, 6, 7]

[1, ‘a’, 2, ‘b’]

Pusta lista.

Lista z elementami.

Lista z elementami różnego typu.

Krotka tuple

()

(123, 456, ‘spam’)

Pusta krotka

Prosta sekwencja

Tablica asocjacyjna dict

{}

{‘klucz’ : ‘wartość’, ‘klucz2’ : ‘wartość2’}

Pusty słownik

Słownik z elementami

Set set set([‘spam’, ‘eggs’, ‘123’]) Wypełniona kolekcja typu set (nie może zawierać duplikatów)
Pusty element None None Odpowiednik słowa kluczowego null, z wielu innych języków programowania.
Wartość boolowska bool

True

False

 

Powyższa tabela nie wyczerpuje oczywiście listy wszystkich wbudowanych typów danych oferowanych przez język Python, ale powinna wystarczyć do rozpoczęcia pracy z tym językiem oraz zachęcić do dalszej eksploracji, tym razem wyczerpującej dokumentacji języka i bibliotek standardowych. W związku z tym, że język Python jest językiem dynamicznie typowanym, w większości wypadków nie ma potrzeby deklarowania typów zmiennych, co będziemy mogli zaobserwować w dalszych przykładach.

Zajmijmy się zatem nieco szerszym omówieniem wybranych typów danych.

Łańcuchy znaków (Strings)

Do oznaczenia łańcuchy znaków (literałów) używamy pojedynczych lub podwójnych znaków apostrofów, jeżeli zaś chcemy, żeby nasz ciąg znaków zawierał specjalny znak końca linii, używamy potrójnych apostrofów. W języku Python można używać również specjalnych oznaczeń znaków specjalnych:

‘hello\tworld\n’

W standardowej implementacji Pythona należało specjalnie potraktować łańcuchy znaków w standardzie Unicode:

u‘to łańcuch znaków w standardzie Unicode’

Ponieważ jednak IronPython bazuje na znanym z platformy .NET typie System.String, takie rozróżnienie nie jest konieczne, aczkolwiek możemy o nim pamiętać, jeżeli zamierzamy pisać kod absolutnie zgodny z CPython.

Programiści .NET, którzy chcieliby wykorzystać podczas programowania w IronPythonie swoją znajomość bibliotek standardowych, będą mogli to uczynić, analizując poniższy przykład (chociaż jedną z mocniejszych stron Pythona jest właśnie manipulacja łańcuchami znaków):

>>> string = 'Hello IronPython'     #deklarujemy łańcuch znaków

>>> string.ToUpper()

Traceback (most recent call last): #podczas wołania metody ToUpper() występuje błąd

File "<stdin>", line 1, in <module>

AttributeError: 'str' object has no attribute 'ToUpper'

>>> import System                   #importujemy przestrzeń nazw znaną z technologii .NET

>>> System.String                   #widzimy, że typowi 'str' odpowiada w IronPython typ 'System.String'

<type 'str'>

>>> System.String is str

True

>>> System.String is unicode #typowi danych 'unicode' również...

True

>>> string.ToUpper()                #teraz metoda ToUpper() już działa

'HELLO IRONPYTHON'

>>>

Jak widzimy też w powyższym przykładzie, do tworzenia komentarzy w kodzie służy znak ‘#’.

Numeryczne typy danych

Do typów numerycznych zaliczamy: całkowitoliczbowe typy danych (integer, long integer), zmiennoprzecinkowe (float) typy danych oraz liczby zespolone (complex).

W przypadku przekroczenia wielkości dla typu integer, zależnego od danej platformy, Python promuje taki typ danych do typu long integer, którego wielkość jest już limitowana tylko wielkością dostępnej dla programu pamięci komputera.

Wynikiem operacji na typie całkowitoliczbowym i zmiennoprzecinkowym będzie zawsze typ zmiennoprzecinkowy.

Python radzi sobie również świetnie z arytmetyką na liczbach zespolonych oraz jako zaawansowany kalkulator obliczający nawet skomplikowane i złożone wyrażenia:

>>> 2+2

4

>>> -5+1

-4

>>> szerokosc = 5

>>> wysokosc = 6.0

>>> pole = szerokosc * wysokosc            #działania z użyciem nazwanych zmiennych

>>> print pole

30.0

>>> 9/4

2              # wynik dzielenia dwóch liczb całkowitych

>>> 9/4.0

2.25           # a to liczby całkowitej i zmiennoprzecinkowej

>>> 3j + 1 * complex(0,1)    #operacje na liczbach zespolonych

4j

>>> (123+22)*(5*(12.43-1))/133  # obliczenia z użyciem bardziej skomplikowanych wyrażeń

62.306390977443606

>>>

Listy

Lista to jedna z podstawowych i jednocześnie jedna z najbardziej elastycznych i wszechstronnych kolekcji danych. Lista pozwala na przechowywanie obiektów dowolnego typu i nie wymaga deklarowania jej wielkości, pozwala również na łatwą modyfikację jej zawartości oraz dodawanie i usuwanie elementów.

Elementy umieszczone w liście tworzą w niej uporządkowaną sekwencję, a do jej elementów można odwoływać się poprzez numer indeksu (tak jak w tablicach), przy czy początkowym numerem indeksu jest 0. Wyjście poza zakres listy spowoduje oczywiście wystąpienie błędu.

Przyjrzyjmy się zatem kilku przykładom:

>>> lista = ['jeden', 'drugi', 'trzeci', 'czwarty', 5] #utworzenie prostej listy

>>> lista[0]   #odwołanie się do pierwszego elementu

'jeden'

>>> lista[4]

5

>>> lista[1:-1]       #odwołanie się do elementu od 1 do przedostatniego

['drugi', 'trzeci', 'czwarty']

>>> lista[4] = lista[4] + 5 #modyfikacja 5 elementu listy

>>> lista[4]

10

>>> lista[0:2]       

['jeden', 'drugi']

>>> lista[0:2] = [1, 2]   #podmiana dwóch pierwszych elementów

>>> lista[0:2]

[1, 2]

>>> lista[4] = []            #usuniecie 5 elementu

>>> lista

[1, 2, 'trzeci', 'czwarty', []]

>>> lista[4]

[]

>>> lista[5]                 #odwołanie się do elementu spoza zakresu

Traceback (most recent call last):

  File "<stdin>", line 1, in <module>

IndexError: index out of range: 5

>>> lista[1:1] = ['xxx', 'yyy']            #dodanie nowych elementów w miejsce pierwszego

>>> lista

[1, 'xxx', 'yyy', 2, 'trzeci', 'czwarty', []]

>>> len(lista)                                   #odczytanie ilości elementów na liście

7

>>> lista2 = [0, lista, 999]        #umieszczenie elementów listy w innej liście

>>> lista2

[0, [1, 'xxx', 'yyy', 2, 'trzeci', 'czwarty', []], 999]

>>> lista2.append(1000)                          # dodanie kolejnego elementu, tym razem przy użyciu metody

>>> lista2

[0, [1, 'xxx', 'yyy', 2, 'trzeci', 'czwarty', []], 999, 1000]

A teraz garść kolejnych przykładów użycia listy:

>>> stos = [1, 2, 3] #utworzenie listy, lista jako stos ("last in, first out")

>>> stos.append(4)    #dodanie elementu na koniec listy

>>> stos.append(5)

>>> stos

[1, 2, 3, 4, 5]

>>> stos.pop() #zdjęcie ostatniego elementu listy

5

>>> stos.pop()

4

>>> stos

[1, 2, 3]

>>> from collections import deque #zaimportowanie dodatkowej kolekcji

>>> kolejka = deque([1, 2, 3]) #lista jako kolejka ("first in, first out")

>>> kolejka

deque([1, 2, 3])

>>> kolejka.append(4)

>>> kolejka.append(5)

>>> kolejka

deque([1, 2, 3, 4, 5])

>>> kolejka.popleft() #zdjęcie pierwszego elementu listy

1

>>> kolejka.popleft()

2

>>> kolejka

deque([3, 4, 5])

>>>

Krotki (Tuple)

Kolejny typ danych również jest kontenerem na obiekty umożliwiającym odwoływanie się do nich za pomocą numeru indeksu, ale znacznie mniej elastycznym niż lista. Po utworzeniu krotki nie możemy już ani modyfikować w niej elementów, ani ich dodawać lub usuwać. Jaki jest więc sens użycia właśnie tego typu danych? Otóż przez te ograniczenia krotki są znacznie wydajniejsze i zużywają mniej pamięci niż listy. Zawartość krotki jest więc niezmienna (immutable), tak samo jak np. poszczególne znaki w obiekcie typu string.

Jednym z ciekawszych przypadków użycia tego typu danych jest użycie go wtedy, kiedy potrzebujemy zwrócić z danej funkcji kilka elementów, nawet różnych typów danych – zwracamy wtedy po prostu wypełnioną odpowiednią ilością obiektów krotkę.

W praktyce zaś listy wykorzystywane są wtedy, gdy przechowujemy w nich elementy jednego typu danych (np. kolekcja liczb), natomiast krotek – jeśli przechowywane są elementy różnych typów danych, aczkolwiek sam język takiego ograniczenia nie narzuca.

Zobaczmy zatem prosty przykład użycia tego typu danych:

>>> tuple = 234, 45.67, 'abcdef'

>>> tuple[0]

234

>>> tuple2 = tuple, (33, 44, 55) #zagnieżdzanie

>>> tuple2

((234, 45.67, 'abcdef'), (33, 44, 55))

>>>

Słowniki (Dictionary)

W przypadku dwóch poprzednich kolekcji danych mogliśmy odwoływać się do zawartych w nich elementów poprzez numer indeksu, co jest prostą i wydajną operacją. Natomiast gdy potrzebujemy np. tylko sprawdzić, czy dany element znajduje się w danej kolekcji, następowała operacja przeszukania takiej kolekcji, co w skrajnym przypadku może już tak wydajną operacją nie być.

Rozwiązaniem tego typu problemów jest użycie kolekcji typu dictionary. Słowniki, czyli inaczej tablice asocjacyjne pozwalają przechowywać pary wartości, z których jedna pełni rolę klucza i powinna być typem niezmiennym (np. typ string), zaś druga, czyli wartość – to praktycznie dowolny obiekt. Odwoływanie się do elementów takiego słownika następuje więc nie poprzez podanie numeru indeksu, a poprzez podanie wartości klucza.

Przy okazji tej kolekcji warto nadmienić, że znacząca ilość mechanizmów samego języka Python jest oparta właśnie na słownikach.

Zobaczmy zatem kilka prostych przykładów:

>>> telefony = {'ola':'12345', 'ala':'45678', 'janek':'86421'} #utworzenie słownika

>>> telefony['ala'] #odwołanie się do elementu słownika

'45678'

>>> telefony['zuzia']

Traceback (most recent call last):

  File "<stdin>", line 1, in <module>

KeyError: zuzia

>>> del telefony['janek']

>>> telefony

{'ola': '12345', 'ala': '45678'}

>>> telefony.keys() #wypisanie wszystkich kluczy

['ola', 'ala']

>>> 'ala' in telefony #sprawdzenie czy w słowniku znajduje się element o kluczu 'ala

True

>>>

Zbiory (sets)

Ostatnią kolekcją, którą zajmiemy się w niniejszym artykule, jest kolekcja typu set. Umożliwia ona przechowywanie elementów o unikalnych wartościach, można więc użyć jej do eliminacji duplikatów. Dodatkowo ten typ danych wspiera operacje arytmetyczne na kolekcjach typu różnica zbiorów, część wspólna itp.

Spójrzmy na kilka poniższych przykładów:

>>> koszyk = ['gruszka', 'poziomka', 'kiwi', 'poziomka', 'gruszka']

>>> owoce = set(koszyk) # utworzenie zbioru

>>> owoce

set(['kiwi', 'poziomka', 'gruszka'])

>>> 'kiwi' in owoce          # test na obecność w zbiorze

True

>>> x = set('abrakadabra')

>>> y = set('kazaaaa')

>>> x - y                           #operacja na zbiorze: litery w zbiorze x, ale nie w zbiorze y

set(['b', 'r', 'd'])

>>> x | y                           #litery w zbiorze x lub y

set(['b', 'r', 'k', 'z', 'd', 'a'])

>>> x & y                           #litery w zbiorze x i y

set(['k', 'a'])

>>> x ^ y                           #litery w zbiorze x lub y, ale nie w obu

set(['b', 'r', 'z', 'd'])

>>>