Podstawy Pythona
Python obecnie jest jednym z najbardziej popularnych języków programowania. Za jego suksesem stoi między innymi przejrzysty i prosty kod źródłowy, otwarte źródła i łatwe możliwości rozszerzania bibliotek. Z pewnością Python jest jednym z najbardziej przyjaznych języków do nauki programowania.
Praca z Pythonem
Istnieje kilka sposobów wygodnej pracy z Pythonem, w zależności od upodobań, rodzaju problemu i dostępnych narzędzi
Bezpośrednia praca z interpreterem (REPL - read-eval-print loop), w tym przypadku wygodniejszy od standardowego interpretera jest
ipython
,Pisanie skryptów w ulubionym edytorze (gnome-text-editor, vim, emacs, nano) w pliku "program.py" oraz uruchamianie w terminalu poleceniem
python program.py
Używanie notatnika
jupyter
(lub jego wersji Google-Colab) - odpowiedników notatników w MathematiceUżywanie środowiska programistycznego (np. VS Code) z odpowiednimi wtyczkami do obsługi Pythona
Interpreter
Python jest językiem interpretowanym. Oznacza to, że programów nie trzeba kompilować, a specjalny program - interpreter - tłumaczy na bieżąco polecenia na język maszynowy. Bazowy interpreter można uruchomić poleceniem
$ python
Ale nie jest to jedyna opcja. Bardziej zaawansowany interpreter, który ma podobne możliwości, jak Mathematica lub Matlab, to:
$ ipython
Jeszcze inna wersja to Jupyter Notebook, który działa w przeglądarkach i umożliwia tworzenie notatek - dokumentów, zawierających kod i tekst. Odmianą tego projektu jest Google Colab. Instrukcje w tych intepreterach można wydawać po kolei i śledzić kolejne wyniki (tzw. REPL - Read Evaluate Print Loop, ten sam schemat używa Mathematica, Matlab, itd.). Na przykład po uruchomieniu interpretera ipython zobaczymy coś następującego
In [1]: a = 1
In [2]: b = 5
In [3]: a**2 + b**2
Out[3]: 26
W ten sposób można szybko wykonywać obliczenia, sprawdzać działanie fragmentów kodu itd.
Ale program w pythonie może mieć również postać pliku z rozszerzeniem *.py
, który jest wywoływany przez interpreter:
$ python moj_program.py
Uwaga! Na starszych komputerach można spotkać ciągle Pythona2, który ma inną składnię niż Python3. W tych wypadkach często równolegle jest zainstalowany Python3 i można go uruchomić poleceniem python3
.
Zmienne
Python jest językiem dynamicznym i sam rozpoznaje typ zmiennej:
a = 1
x = 12.345
Ta sama zmienna może za chwilę zmienić się w coś zupełnie innego:
a = "Łańcuch znaków"
i nie będzie to błędem.
Pomimo tego trzeba pamiętać, że zmienne jednak mają swój typ i może to być typ prosty (int, float, bool, string) lub złożony (tuple, dictionary...) i wpływa to na sposób działania kodu.
>>> a = 5
>>> b = "Ala"
>>> a * b
>>> a = True
>>> a * b
Typy proste, o których na razie musimy wiedzieć, to liczby całkowite (int
), logiczne (bool
) i liczby zmiennoprzecinkowe (float
). Z typów złożonych przyda nam się lista:
a = [3, 5, 8]
która jest po prostu uporządkowaną sekwencją jakichkolwiek obiektów. Mogą być to liczby, napisy, inne listy:
a = [12.3, [0, 1, 2], "Ala ma kota", []]
Ważne jest to, że możemy się odwoływać do kolejnych elementów za pomocą indeksów (zaczynających się od zera dla pierwszego elementu).
>>> a[0]
12.3
>>> a[1]
[0, 1, 2]
Ciekawostką są możliwości indeksowania. Ujemne liczby oznaczają liczenie elementów od końca, więc:
a[-1]
to ostatni wyraz. Wybierać można również pewien zakres elementów - poprzez składnię:
a[0:2]
przy czym początek należy do podzbioru, a koniec nie, czyli w podanym przykładzie podzbiór będzie miał elementy a[0] i a[1].
Bieżącą długość listy można sprawdzić poleceniem len
:
n = len(a)
Wypisywanie na ekranie
Służy do tego instrukcja print ()
, która - jak łatwo się domyśleć - wypisuje coś na ekranie. Jej składnia jest bardzo prosta: można podawać jej tekst lub liczby, a kolejne elementy oddziela się przecinkami. Print sam wstawia enter na końcu i spacje pomiędzy elementami!
print ("x =", x)
Ciekawe są możliwości formatowania liczb i innych zmiennych za pomocą specjalnej składni. Powiedzmy, że chcemy wypisać wynik z dokładnością do 3 miejsc po przecinku, ale tak, aby cały napis mieścił się na 8 znakach i był wyjustowany w prawą stronę.
print('{: >8.3f}'.format(10 / 3))
Nawiasy klamrowe mówią pythonowi, że tu ma wstawić coś, co będziemy formatować. Po dwukropku podajemy spację, którą wstawi w puste miejsca (można użyć innych znaków), znak >
mówiący, że napis jest wyjustowany na prawo (inne możliwości to < i ^) oraz 8.3f
, który mówi, że pól jest 8, miejsc po przecinku 3, a liczba jest typu float. Uwaga: wynik dzielenia 10 / 3 zostanie automatycznie zrzutowany na typ float
!
Instrukcje kontrolne
Instrukcje kontrolne - to wszelkie elementy języka, które pozwalają na zmiany zachowania się kodu w zależności od wyników obliczeń, wprowadzonych danych, powtarzanie operacji itd.
Polecenie if
if x > 0:
print("x jest dodatni")
elif x < 0:
print("x jest ujemny")
else:
print("x jest zerem")
Przy okazji zwróćmy uwagę na kilka istotnych cech składni pythona:
brak nawiasów
brak średników na końcach linii
bloki są definiowane przez wcięcia
na końcu linii z warunkiem jest dwukropek.
Wcięcia wymuszają poprawne formatowanie kodu. Ważne jest, aby wcięć dokonywać za pomocą ustalonej liczby spacji, np. 4. W edytorach można ustawić automatyczne wcinanie kodu, więc nie trzeba ich liczyć ręcznie. Podobnie można ustawić, aby edytor wpisywał odpowiednią liczbę spacji po naciśnięciu klawisza tabulatora. Możliwe jest też stosowanie znaku tabulacji jako wcięcia, ale ważne jest, że nie można ze sobą mieszać spacji i tabulatorów!
Instrukcja while
x = 1
y = 8
while x < 100:
print (x)
if x % y == 0:
break
x += 1
else:
print (x, 'nie dzieli się przez', y)
W pętli while
operacje wewnątrz są powtarzane, dopóki spełniony jest warunek. Warto zwrócić uwagę na to, że cały blok 'while' musi być wcięty, a instrukcja wewnątrz warunku 'if' - podwójnie wcięta (jest to odpowiednik nawiasów w nawiasach).
Instrukcje +=
i break
mają odpowiedno znaczenie sumowania (x += 1
jest tożsame z x = x + 1
) oraz wyskoczenia z pętli. Ciekawostką jest instrukcja else
przy pętli while
. Będzie ona wykonana tylko wtedy, gdy while
zakończy działanie na skutek spełnienia warunku. Jeżeli z pętli while
wyskoczymy poleceniem break
, to kod w bloku else
nie będzie wykonany.
Instrukcja for
Instrukcja for w pythonie wygląda następująco
for i in [0, 2, 4, 6]:
print (i)
W pętli for
także możemy stosować polecenie else
, na takich samych zasadach jak w pętli while
.
Dla tej pętli podajemy zbiór, po którym ma się poruszać iterator (tutaj i
) i będzie to robić po kolei, chyba, że jej przerwiemy poleceniem break. Ponieważ często chcemy poruszać się po pewnym zakresie, na przykład od 1 do 10, w pythonie mamy polecenie range()
, które taką listę zrobi od ręki. Ważne, aby pamiętać, że pierwsza podana wartość będzie użyta, a ostatnia jest górną granicą ostrą, czyli jej samej nie będzie. Poniższy kod pokaże nam tabliczkę mnożenia do 10.
for i in range (1, 11):
for j in range (1, 11):
print ( '{: >4d}'.format(i * j) , end='')
print ()
Formatowanie napisów
W powyższym przykładzie użyte zostało bardziej zaawansowane formatowanie wypisywanych na ekranie wyników. Użyta została tu metoda, należąca do obiektu typu ciąg znaków, format
. Jest to bardzo elastyczne polecenie, pozwalające na zdefiniowanie wielu aspektów napisu. Najważniejsze dla nas informacje to:
W ciągu znaków używamy nawiasów
{}
, które oznaczają kolejno podawane później w poleceniuformat
wartościW nawiasach
{:}
pod dwukropku możemy podać formatowanie danej wartości{:d}
liczba całkowita{:f}
liczba zmiennoprzecinkowa{:e}
liczba zmiennoprzecinkowa w formacie "naukowym"{:s}
ciąg znaków
Dodatkowo możemy podać szerokość napisu, dokładność (liczbę miejsc po przecinku oraz wyjustowanie
{: ^8.3f}
wyjustowanie do środka spacjami, szerokość 8 znaków, 3 miejsca po przecinku{:0>4d}
wyjustowanie w prawo zerami, szerokość 4 znaki
Można zwrócić też uwagę na słówko end=''
w instrukcji print
, które powoduje, że nie będzie wstawiany znak nowej linii (domyślne działanie), którą chcemy wstawić dopiero na koniec dziesiątki - i stąd puste print ()
, dające tylko enter.
Funkcje
Jeżeli stworzymy w programie jakąś procedurę, którą będziemy często powtarzać, zamiast kopiować i wklejać fragment kodu, warto jest stworzyć z niej funkcję (zwaną także procedurą lub subrutyną). Funkcję definiujemy w następujący sposób
def pitagoras(a, b):
c = a**2 + b**2
return c**(1/2)
Zgodnie ze wcześniejszą składnią stosujemy dwukropek i wcięcie. Jeżeli nie użyjemy słówka return
, funkcja automatycznie zwróci wartość None
(nic).
Zmienne wewnątrz funkcji są widoczne tylko w niej, nawet jeżeli nazywają się tak samo jak zmienne globalne. Na przykład
def f(x):
x = x + 1
print('f:', x)
return x**3
x = 1
y = f(x)
print('main:', x)
rezultatem działania będzie
f: 2
main: 1
Pomimo iż dodajemy jedynkę do x
wewnątrz funkcji, jest to inny x
(lokalny) niż ten, który deklarujemy w głównej części programu! Dlatego po wyjściu z funkcji x
globalny jest dalej równy 1.
Uwaga! Inaczej będą się zachowywały listy. Z powodu sposobu w jaki Python przekazuje zmienne do funkcji, lista zmodyfikowana wewnątrz funkcji, po wyjściu z niej nadal będzie zmodyfikowana. Jeżeli chcemy, aby lista nie ulegała zmianom, nie wystarczy przypisać wartość listy nowej zmiennej (bo wskazuje ona ciągle na ten sam obiekt!). Musimy zrobić jej kopię (np. wewnątrz funkcji B = A.copy()
).
def f(A):
A[0] += 1
print('f:', A)
# To jest niewłaściwe podejście
def g(A):
B = A
B[0] += 1
print('f:', B)
def h(A):
B = A.copy()
B[0] += 1
print('g:', B)
A = [0, 0, 0]
f(A)
print('main:', A)
rezultatem działania będzie
f: [1, 0, 0]
main: [1, 0, 0]
g: [2, 0, 0]
main: [2, 0, 0]
h: [3, 0, 0]
main: [2, 0, 0]
Zarówno f
, jak i g
modyfikują oryginalną listę A
. Dopiero podejście w funkcji h
pokazuje jak uniknąć tego efektu, jeżeli taki mamy zamiar.
Operacje I/O
Pliki
Funkcja open
zwraca obiekt powiązany z plikiem, w zależności od trybu otwarcia może być on do odczytu (r), pisania (w, istniejący plik zostaje skasowany), dopisywania (a), odczytu i zapisu (r+). Dodanie litery b
do trybu oznacza tryb binarny, w innym przypadku jest to tryb tekstowy.
data_file = open('data.txt', 'r')
Funkcja read
służy do czytania danych, read()
wczyta cały plik, read(size)
wczyta size
bajtów.
data_file.read(10)
Funkcja readline
wczytuje jedną linię tekstu (łącznie ze znakiem końca linii).
data_file.readline()
for line in data_file:
print(line, end='')
write
służy do pisania w pliku, natomiast seek
i tell
odpowiednio przesuwają i zwracają położenie bieżącej pozycji w pliku.
data_file.write('{} {} {} \n'.format(0, 0, 1)
data_file.seek(10)
data_file.tell()
Błędy
Podczas pisania programów prędzej czy później popełnimy jakiś błąd i działanie skończy się komunikatem o problemie. Ważne jest, aby umieć odpowiednio odczytywać, naprawiać lub radzić sobie ze zgłoszonym problemem w inny sposób.
Oto prosty program z drobnym błędem
l = [0, 1, 2]
if l > 0:
l.pop()
i próba uruchomienia
Traceback (most recent call last):
File "test.py", line 2, in <module>
if l > 0:
TypeError: '>' not supported between instances of 'list' and 'int'
Jest to typowy komunikat błędu. Po pierwsze mówi nam o pliku (test.py) w którym nastąpił problem. Dalej mamy numer linii kodu, w której wystąpił błąd (numer 2), jej treść (if l > 0:) oraz typ błędu, który w tym przypadku oznacza: nie można porównać listy i liczby za pomocą operatora ">".
Wszelkie błędy są zwracane w postaci wyjątków (Exception), należących do odpowiednich klas o czym niżej. Powyżej mamy błąd typu (TypeError). Program wcale nie musi przerywać działania w takiej sytuacji, możliwa jest odpowiednia obsługa wyjątku. Na przykład poniższy kod podczas czytania pliku zigoruje przypadki, kiedy napisu w danej linii nie daje się zinterpretować jako liczby całkowitej.
data_file = open('data.txt')
counter = 0
for line in data_file:
try:
counter += int(line)
except TypeError:
continue
print(counter)
Programowanie obiektowe
Podejście obiektowe jest próbą opisu skomplikowanych systemów za pomocą abstrakcji.
Tworzymy obiekty, z którymi się komunikujemy (i które mogą się komunikować między sobą), które realizują zadania dzięki wewnętrznym metodom.
Problem dzielimy na pewne zadania, które przypisujemy obiektom i w pewnym sensie nie interesuje nas jak dany obiekt rozwiąże zadanie, o ile robi to zgodnie z oczekiwaniami. Niepotrzebne informacje są ukryte wewnątrz obiektu.
Każdy obiekt należy do pewnej klasy.
Każdy obiekt ma swoje zmienne i procedury.
Obiekty można ponownie używać w innych programach.
Wewnętrzną strukturę obiektów można modyfikować, uzupełniać i poprawiać, o ile nie wpływa to na widziane z zewnątrz własności.
Obiekty mogą dziedziczyć (łączyć się hierachicznie) po bardziej ogólnych klasach (np. samochód, czy rower jest bardziej szczegółową realizacją klasy pojazd).
Obiekty mogą zawierać inne obiekty (np. samochód zawiera koła, czujnik prędkości itd.).
W Pythonie wszyscy członkowie klasy są publiczni, a wszystkie funkcje są wirtualne (według terminologii C++).
Klasy
Poniższy przykład tworzy dwie klasy obiektów matematycznych: Point oraz Circle. Do zdefiniowania punktu na płaszczyźnie potrzebujemy jego współrzędnych. Tworzymy też metodę tej klasy, distance
, która mierzy odległość między dwoma punktami. Druga klasa - Circle - wymaga podania położenia środka okręgu oraz jego promienia. Środek okręgu jest punktem, więc możemy użyć obiektu poprzednio stworzonej klasy Point (taka konstrukcja nazywa się kompozycją).
import math
class Point:
def __init__(self, x, y):
self.x = x
self.y = y
def distance(self, point):
return math.sqrt((self.x - point.x)**2 + (self.y - point.y)**2)
class Circle:
def __init__(self, x, y, r):
self.center = Point(x, y)
self.r = r
def distance_to_perimeter(self, point):
return math.sqrt((self.center.x - point.x)**2 +
(self.center.y - point.y)**2) - self.r
p1 = Point(0, 0)
p2 = Point(1, 2)
c1 = Circle(1, 1, 1)
print(p1.distance(p2))
print(c1.distance_to_perimeter(p2))
print(p1.distance(c1.center))
Gdybyśmy chcieli stworzyć więcej kształtów geometrycznych i nasz kod miałby elastycznie z nimi pracować możnaby rozważyć utworzenie klasy Shape, po której dziedziczą kolejne elementy. Powiedzmy, że chcemy, żeby każdy kształt miał jakiś kolor. Możemy wtedy zrobić tak.
import math
class Shape:
def __init__(self, color="black"):
self.color = color
class Point:
def __init__(self, x, y):
self.x = x
self.y = y
class Circle(Shape):
def __init__(self, x, y, r, color="black"):
super().__init__(color)
self.center = Point(x, y)
self.r = r
class Triangle(Shape):
def __init__(self, points, color="black"):
super().__init__(color)
self.points = points
c1 = Circle(1, 1, 1)
c2 = Circle(0, 0, 1, "red")
t1 = Triangle([Point(0, 0), Point(1, 1), Point(0, 1)], "green")
print(c1.color)
print(c2.color)
print(t1.color)
Jeżeli wpadnie nam do głowy, że oprócz koloru przydałby się np. krój linii (kreski, kropki, itp.) teraz wystarczy dołożyć taki element do macierzystej klasy Shape i wszystkie dziedziczące po niej klasy będą taką cechę posiadały.
Wyjątki - jeszcze raz
Znając mechanizm dziedziczenia może na chwilę wrócić do sprawy wyjątków, zwracanych w przypadku gdy coś w kodzie poszło źle. Nowe wyjątki, obsługujące nasze własne przypadki, można tworzyć korzystając z właśnie z mechanizmu dziedziczenia. Wszystkie wyjątki w Pythonie dziedziczą bowiem po bazowej klasie Exception
i zaczynając od niej można dodawać kolejne klasy.
class NuclearError(Exception):
def __init__(self, msg = ''):
self.msg = msg
def __str__(self):
return self.msg
try:
if temp > temp_max:
raise NuclearError('Max temp reached')
except NuclearError as err:
print(err)