Udostępnij za pośrednictwem


          

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

Udostępnij na: Facebook

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.