IronPython – wstęp do języka Python, cz. 2/2
Autor: Bartosz Kierun
Opublikowano: 2011-01-10
Python – konstrukcje językowe
Po zapoznaniu się z podstawowymi typami danych warto przyjrzeć się typowym konstrukcjom językowym stosowanym w tym i wielu innych językach programowania. To właśnie one stanowią fundament naszych programów.
Do najprostszych konstrukcji językowych należą instrukcje oraz wyrażenia. Instrukcja ma za zadanie wykonać jakąś akcję, podczas gdy wyrażenie zwrócić po prostu jakąś wartość.
>>> i = 256*256 #przypisanie do zmiennej
>>> print i #wypisanie wartości zmiennej
65536
>>> x = y = z = 1 #przypisanie wielu zmiennych
>>>
Wyrażenia warunkowe i pętle
Przyjrzyjmy się teraz najbardziej fundamentalnym konstrukcjom językowym w niemal każdym języku programowania:
>>> x = 5
>>> if x == 0: # instrukcja warunkowa 'if'
... print 'x jest rowne zeru'
... elif x > 0:
... print 'x jest wieksze od zera'
... else:
... print 'x jest mniejsze od zera'
...
x jest wieksze od zera
>>>
>>> x = 10
>>> while x > 0: #pętla 'while'
... print x, x*2, x*3
... x = x - 1
... if x == 5:
... break
...
10 20 30
9 18 27
8 16 24
7 14 21
6 12 18
>>>
>>> seq = [1, 2, 3, 4, 5]
>>> for i in seq:
... print i, i^2
...
1 3
2 0
3 1
4 6
5 7
>>>
>>> x = range(10) #przydatna funkcja do generowania sekwencji liczb
>>> print x
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
>>>
Funkcje
Po podstawowych konstrukcjach, takich jak instrukcje warunkowe czy pętle, nadszedł czas na funkcje, czyli podstawowy mechanizm pozwalający na modularyzację kodu i zwiększenie jego czytelności.
Zamiast rozwodzić się nad tym jednym z bardziej oczywistych elementów w naszych programach, zobaczmy kilka przykładów:
>>> def Wypisz(arg1, arg2 = 0): #definicja funkcji
... print arg1, arg2
...
>>> Wypisz(1, 2)
1 2
>>> Wypisz(1) #opcjonalny parametr
1 0
>>>
>>> def Dodaj(a, b): #funkcja zwracająca wynik dodawania dwóch liczb
... wynik = a + b
... return wynik
...
>>> Dodaj(2, 2)
4
>>>
Domknięcia (Closures)
W języku Python znajduje się wiele mechanizmów znanych z funkcyjnych języków programowania. Domknięcie to obiekt wiążący funkcję oraz środowisko, w którym funkcja ta ma działać. Środowisko przechowuje wszystkie obiekty wykorzystywane przez funkcje spoza globalnego zakresu widoczności.
>>> def Prefixer(prefix):
... def AddString(string):
... return prefix + string
... return AddString
...
>>> string = Prefixer('___')
>>> string('Napis')
'___Napis'
>>>
Dostępne też są oczywiście znane wielu programistom wyrażenia lambda, znacząco ułatwiające pisanie wielu algorytmów i funkcji obliczeniowych:
>>> def Dodawacz(o_ile):
... return lambda wynik: wynik + o_ile
...
>>> func = Dodawacz(5)
>>> func(10)
15
>>> func(-10)
-5
>>>
Wbudowane funkcje
Większość języków programowania byłaby tylko ciekawostką, gdyby nie biblioteki standardowe i/lub przydatne, na co dzień funkcje, ułatwiające programowanie.
Przyjrzyjmy się, zatem kilku dostarczanym razem z językiem Python funkcjom:
Funkcja | Przykład | Opis |
abs | abs(-5) | Zwraca wartość bezwzględną. |
cmp | cmp(1, 2) cmp(‘a’, ‘b’) |
Porównuje dwa obiekty. |
dir | dir dir(x) |
Wywołana z argumentem zwraca listę atrybutów tego obiektu. |
eval | x = 5 print eval(‘x + 5’) |
Pozwala na parsowanie wyrażeń przekazanych jako łańcuch znaków. |
open | open | Pozwala na odczytanie pliku. |
len | len(‘aaaa’) len([1,2,3]) |
Zwraca ilość elementów. |
reversed | seversed(sequence) | Zwraca sekwencję elementów ułożoną w odwrotnej kolejności. |
sorted | sorted(sequence) | Zwraca posortowaną sekwencję elementów. |
sum | sum(sequence) | Zwraca sumę elementów w kolekcji. |
min, max | max([2,5,1]) min([4,8,1,2,6]) |
Zwraca element o maksymalnej lub minimalnej wartości. |
Klasy
W poprzedniej części artykułu omawialiśmy wbudowane, mniej lub bardziej złożone, typy danych. Teraz nadszedł czas na zapoznanie się z esencją programowania obiektowego, czyli klasami.
Klasa pozwala na zdefiniowanie własnego, dostosowanego do naszych potrzeb typu danych. Odpowiednie użycie tej podstawowej techniki w programowaniu obiektowym może znacząco przyczynić się do zwiększenia czytelności naszego programu i jego odpowiedniej modularyzacji. W wielu przypadkach, dzięki użyciu programowania obiektowego, programista jest w stanie stworzyć naprawdę duże programy i zapanować nad nimi. Także dzięki tym technikom programista może stworzyć solidne fundamenty pod swoją aplikację oraz oprzeć część zawartych w niej rozwiązań, wykorzystując gotowe wzorce projektowe (design patterns).
W świecie Pythona klasy składają się zazwyczaj z atrybutów umożliwiających przechowywanie zawartości obiektu danej klasy oraz metod pozwalających na zdefiniowanie funkcjonalności obiektu.
Podstawowymi terminami związanymi z programowaniem obiektowym i możliwym do realizacji właśnie dzięki klasom są:
- Enkapsulacja – pozwalająca na ukrycie przed użytkownikiem obiektu tworzonej przez nas klasy, niepotrzebnych szczegółów jej implementacji.
- Polimorfizm – pozwalający tworzyć metody lub nawet całe struktury danych albo algorytmy bez precyzowania, na jakich dokładnie typach danych one operują.
- Dziedziczenie – umożliwia ponowne użycie istniejącego już kodu poprzez stworzenie nowego typu na bazie typu już istniejącego. Warto tutaj nadmienić, że język Python pozwala na tzw. dziedziczenie wielokrotne, czyli dziedziczenie po więcej niż jednej klasie bazowej.
Przyjrzyjmy się zatem paru przykładom:
# definicja prostej klasy
class Book:
# 'konstruktor' pozwalający na wykonanie własnego kodu podczas inicjacji obiektu
def __init__(self, title='not defined'):
# deklaracja zmiennej prywatnej, nazwę poprzedzamy znakiem '_'
self._zmiennaPrywatna = True
# deklaracja atrybutu
self.title = title
self.pages = 0
self.authors = []
# warto zwrócić uwagę na słowo 'self', będące pomocniczym słowem,
# wykorzystywanym przez jeżyk Python, w celu stwierdzenia, na której
# instancji obiektu wykonać dane metodę
def AddAuthor(self, author):
self.authors.append(author)
# deklaracja kolejnej metody
def GetFormatedTitle(self):
return self.authors[0] + ', ' + self.title
def GetAuthorsCount(self):
return len(self.authors)
# tworzenie obiektów klasy 'Book' i wykorzystanie jej atrybutów i metod
b1 = Book('Cyberiada')
b1.pages = 324
b1.AddAuthor('Stanisław Lem')
print b1.GetFormatedTitle() + ' has ' + str(b1.pages)
b2 = Book('Learning Python: Powerful Object-Oriented Programming')
b2.AddAuthor('Mark Lutz')
print b1
print b2
print b1 is b2 # zwraca False
# proste dziedziczenie
class ScienceFictionBook(Book):
def __init__ (self, title):
self.genre = 'science fiction'
self.title = title
sfb3 = ScienceFictionBook('Czasopodobna nieskończoność')
# Klasa jako prosta struktura danych
class Article:
pass
a1 = Article()
a1.title = "Introduction to Python"
a1.numberOfPages = 12
print a1.title + ' contains: ' + str(a1.numberOfPages) + ' pages'
Choć powyższy przykład nie wyczerpuje możliwości języka Python w kontekście technik programowania obiektowego, powinien pozwolić na tworzenie własnych typów danych i wykorzystywanie ich we własnych programach.
Sposoby obsługi błędów
W przeciwieństwie do większości prostych języków skryptowych, Python wspiera strukturalną obsługę wyjątków (exception handling), która pozwala w bardzo elegancki sposób radzić sobie z mogącymi wystąpić w naszym programie błędami.
Krótki wstęp do obsługi błędów oparty na wyjątkach:
- Wyjątki służą do sygnalizacji wystąpienia jakiegoś błędu.
- Wyjątki zgłaszane są przez system wykonania programu lub same metody wtedy, gdy zaistnieje sytuacja wyjątkowa.
- Programista ma możliwość zgłaszania wyjątków wtedy, gdy zaistnieje warunek wystąpienia błędu lub nasza funkcja lub metoda nie może się wykonać z jakiegokolwiek innego powodu.
- Jeśli nie obsłużymy w jakikolwiek sposób wyjątku, będzie on propagowany wyżej, powodując w skrajnym przypadku zatrzymanie wykonywania całego programu.
# klauzula 'try', umieszczamy w niej kod, który chcemy wykonać i w którym może wystąpić błąd (wyjątek)
>>> try:
... 'napis' + 7
... except: # konstrukcja niezalecana, umożliwia 'połknięcie' wszystkich wyjątków
... pass
...
>>> def jakasFunkcja():
... print 'funkcja'
...
>>> try:
... jakasFunkcja()
# możemy wielokrotnie użyć bloku kodu 'except', aby obsłużyć różne typy błędów
... except (OSError, IOError):
... print 'wystąpił błąd systemowy'
... except TypeError:
... print 'wystąpił błąd niezgodności typów'
... else:
... print 'wszystko w porządku'
...
funkcja
wszystko w porzadku
>>> try:
... 'napis' + 8
# obsługa błędu wraz z dostępem do obiektu reprezentującego dany wyjątek
# można użyc tych informacji np. do logu błędów
... except TypeError, e:
... print e
...
unsupported operand type(s) for +: 'str' and 'int'
>>> try:
... raise Exception('Ups, wystąpił błąd')
# użycie klauzuli 'finally', może ona zawierać np. kod 'czyszczący'
... finally:
... print 'Ten kod wykona się niezależnie od tego czy błąd wystąpił czy nie'
...
Ten kod wykona się niezależnie od tego czy błąd wystąpił czy nie
Traceback (most recent call last):
File "<stdin>", line 2, in <module>
Exception: Ups, wystąpił błąd
# najpełniejsza i najbardziej elegancka forma obsługi błędów
>>> try:
... raise Exception('Ups, wystąpił błąd')
... except:
... print 'Błąd'
... finally:
... print 'Jakiś kod'
...
Błąd
Jakiś kod
>>>
Po krótkim opisie sposobu obsługi błędów czas na opis struktury programów tworzonych w języku Python oraz sposobu ich fizycznej modularyzacji.
Fizyczna struktura programów
Tworzone przez nas programy rzadko składają się z jednego, monolitycznego pliku z kodem (a jeśli tak jest, to czas to zmienić ;)). Bardzo często potrzebujemy wyodrębnić jakoś związane ze sobą klasy i/lub funkcje w celu ponownego ich użycia. Najczęściej robi się to, dzieląc nasz kod na pewną ilość plików. W języku Python taką funkcjonalność zapewniają tzw. moduły (modules).
Moduły (modules) w języku Python są więc zwykłymi plikami tekstowymi z rozszerzeniem *.py. Obiekty znajdujące się w takim pliku mogą być zaimportowane i użyte w jakimkolwiek innym kawałku kodu.
Wyrażenie pozwalające na zaimportowanie modułu może mieć kilka form, których używamy w zależności od tego, czy potrzebujemy zaimportować wszystkie obiekty z danego modułu (pierwsza forma), czy tylko kilka wybranych (druga forma). Aby uniknąć niejednoznaczności w nazwach (np. mogą istnieć dwie klasy o takich samych nazwach znajdujące się w dwóch różnych modułach), można importowanym modułom nadawać aliasy (trzecia forma). Ostatnia przedstawiona poniżej forma importuje wszystkie obiekty znajdujące się w danym module:
import module
from module import nazwa1, nazwa2
from module import nazwa_modułu as inna_nazwa
from module import *
Do wybranej klasy znajdującej się w danym module możemy się też odwoływać poprzez w pełni kwalifikowaną nazwę w postaci moduł.klasa.
Na dalszą modularyzację tworzonego przez nas kodu pozwalają tzw. paczki (packages). Każda taka paczka to katalog z plikami *.py zawierającymi kod oraz wymaganym plikiem __init__.py, który jest wykonywany podczas importowania paczki. Katalog reprezentujący naszą paczkę może zawierać kolejne podkatalogi (które również muszą zawierać wspomniany wcześniej specjalny plik).
Aby zaimportować wybrany moduł z danej paczki, używamy następujących wyrażeń:
import paczka.moduł
from paczka import moduł
W przypadku bardziej zagnieżdżonych bibliotek stosujemy następująca konwencję:
import paczka.paczka2.moduł
from paczka.paczka2 import moduł
Wszystkie standardowe biblioteki dostarczane z językiem Python zorganizowane są właśnie w ten sposób, a podstawowa eksploracja może być dokonywana w jakimkolwiek menedżerze plików.
Biblioteki standardowe Pythona
Najwygodniejszy nawet w użyciu język nie zdobyłby raczej popularności, gdyby nie istniały dostarczane wraz z nim, wygodne biblioteki klas i metod. Choć w przypadku IronPythona istnieje możliwość wykorzystania niezwykle bogatej listy bibliotek standardowych dostarczanych wraz z platformą .NET, to warto pamiętać o tych dostarczanych z samym językiem, gdyż często są one bardzo proste i łatwe w użyciu. Oto krótki spis tych najważniejszych:
Moduł | Opis |
sys | Daje dostęp do ustawień systemowych takich jak ścieżka wykonania programu czy argumenty jego wykonania. |
os | Pozwala na interakcje z systemem operacyjnym, umożliwiając m.in. tworzenie nowych procesów. |
os.path | Dostarcza możliwości pracy z plikami, katalogami czy ścieżkami. |
re | Daje możliwość wykorzystania wyrażeń regularnych. |
math | Udostępnia wiele różnych funkcji matecznych. |
random | Generacja liczb losowych. |
time | Umożliwia pracę z typem data i czas, umożliwia ich formatowanie do postaci tekstowej. |
cPickle | Umożliwia serializację i deserializację obiektów to tekstu lub binariów. |
decimal | Wsparcie dla operacji na typach zmiennoprzecinkowych. |
thread | Umożliwia operacje na wątkach. |
socket | Umożliwia oprogramowanie gniazd sieciowych. |
itertools | Zawiera wiele klas i metod przydatnych przy tworzeniu tzw. iteratorów. |
collecions | Bogaty zestaw kolekcji do wielu różnych zastosowań. |
Podsumowanie
W niniejszym artykule przedstawiliśmy niezbędne minimum wiedzy na temat podstawowych cech języka Python, takich jak podstawowe typy danych i konstrukcje językowe, która powinna umożliwić rozpoczęcie z nim poważniejszej współpracy.
W kolejnych artykułach przedstawione zostaną nieco bardziej zaawansowane konstrukcje języka Python, zademonstrowane na bardziej praktycznych przykładach.