Importowanie w Pythonie
Do tej pory wszystkie nasze funkcje i klasy tworzyliśmy w jednym miejscu: w jednym pliku lub środowisku REPL. Takie podejście jest bardzo przydatne przy nauce, ale w praktyce projekty potrafią mieć setki klas i funkcji. Nie możemy ich wszystkich umieścić w jednym pliku. Pojawia się więc temat organizacji plików w naszym projekcie.
Organizacja projektu
Dobra organizacja projektu nie jest prosta. Trochę przypomina to porządkowanie rzeczy w mieszkaniu. Jeśli wszystko jest w jednym pliku, to tak jakby leżało luzem w jednej szafie. Jest to ok, jeśli rzeczy jest mało. Gdy pojawia się ich nieco więcej, należy porozdzielać je na różne szafy i szuflady. Jeśli za tym podziałem stoi zrozumiała logika, to znalezienie rzeczy będzie łatwiejsze. Jeśli nie, to ten podział nie będzie zbyt przydatny.
O organizacji projektu można by długo pisać. Jest wiele zgodnych i sprzecznych ze sobą ideologii. Ogólnie dążymy do tego, by mocno powiązane ze sobą elementy były bliżej siebie (w tym samym pliku albo przynajmniej folderze), a mniej powiązane, bardziej od siebie oddalone. Przykładowo, jeśli piszemy funkcję, która jest używana przez inne pliki, ale jako jedyna używa innej funkcji, to te dwie funkcje powinny znaleźć się możliwie blisko siebie. Najczęściej dzielimy także projekt na pliki i foldery ze względu na funkcjonalności (na przykład folder tax
może zawierać wszystko, co dotyczy podatków) albo zastosowanie (na przykład folder views
może zawierać wszystkie definicje elementów widoku, czyli określające jak nasza aplikacja powinna wyglądać). Jeśli nad naszym projektem pracują dwa osobne zespoły, dążymy do tego, by pracowali oni na różnych plikach albo folderach.
Gdy już podzielimy projekt na osobne pliki, potrzebujemy sprawić, by mogły one korzystać ze swoich elementów. Bez tego nie możemy dokonać podziału. Aby użyć elementu zdefiniowanego w innym pliku, wykorzystujemy importowanie.
Importowanie pliku
Wyobraźmy sobie, że tworzymy kalkulator wypłat dla pracowników. Będziemy mieli w naszym projekcie dwa pliki:
salaries.py
- zawierające listę pracowników wraz z ich wypłatami. Ten plik jest tworzony i rozwijany przez dział zasobów ludzkich.
finance.py
- zawierające funkcje do obliczania kwoty wypłaty oraz podatków netto i wyświetlania do przelewów na koniec miesiąca. Ten plik jest tworzony i rozwijany przez dział finansów.
Rozdzielenie tych plików jest bardzo ważne. Są one do pewnego stopnia niezależne, a w dodatku są rozwijane przez różne działy. Gdyby były jednym plikiem, powodowałoby to zapewne konflikty, gdy jeden z działów by wprowadzał zmiany, których ten drugi by nie rozumiał.
Jednocześnie finance.py
potrzebuje mieć dostęp do listy pracowników, aby móc wyświetlić ich wypłaty. Aby tego dokonać, potrzebne nam jest importowanie, które przedstawimy za chwilę. Zacznijmy jednak od wyglądu pliku salaries.py
, który to będziemy importować.
# salaries.py
class Person:
def __init__(self, full_name, salary):
self.full_name = full_name
self.salary = salary
def __str__(self):
return self.full_name
workers = [
Person("Aleksander Brown", 3456.78),
Person("Celina Drozd", 4567.89),
# ...
]
W projektach Python każdy plik stanowi moduł, który można zaimportować w innych plikach. Nazwa tego modułu to po prostu nazwa pliku, w którym się on znajduje (bez końcówki ".py"). Chyba że plik ten znajdowałby się w folderze, bo w takim wypadku musielibyśmy zacząć od nazwy folderu i kropki pomiędzy (dla przykładu, jeśli mamy plik A
w folderze a
, to moduł nosi nazwę a.A
).
Najprostszy sposób, aby zaimportować moduł salaries
(czyli plik salaries.py
) to użyć słówka import
oraz nazwy tego modułu, a więc import salaries
. W wyniku działania tej instrukcji otrzymujemy obiekt salaries
, zawierający wszystkie elementy zdefiniowane w pliku salaries.py
.
import salaries
for person in salaries.workers:
print(person)
# Aleksander Brown
# Celina Drozd
print(salaries.Person("Ala Mała", 5678))
# Ala Mała
Dzięki zaimportowaniu plików możemy odnieść się do zmiennej workers
i wykorzystać ją do wyświetlenia listy wypłat:
import salaries
def brutto_to_netto(brutto):
# Znacznie uproszczona funkcja
return brutto * 0.75
if __name__ == '__main__':
for worker in salaries.workers:
salary_netto = brutto_to_netto(worker.salary)
print(f"{salary_netto} dla {worker.full_name}")
# 2592.0 dla Aleksander Brown
# 3425.25 dla Celina Drozd
Importowanie modułu pod zmienioną nazwą
Niejednokrotnie chcemy zaimportować moduł, ale odnosić się do niego pod inną (najczęściej krótszą) nazwą. W naszym przykładzie salaries
to może i dobra nazwa dla modułu, ale niekoniecznie dla obiektu przechowującego to, co w tym module się znajduje. Dla pracowników działu finansów bardziej intuicyjne byłoby, by obiekt ten nazywał się hr
, bo i tak wszyscy mówią, że dane te pochodzą z działu HR1. Aby tego dokonać, po zaimportowaniu możemy dodać słowo kluczowe as
, określające, pod jaką nazwą powinien zaimportowany obiekt figurować. Takie importowanie jest bardzo popularne, gdyż nie tylko jasno pokazuje, skąd dane elementy pochodzą, ale także skraca odnoszenie się do nich.
import salaries as hr
for person in hr.workers:
print(person)
# Aleksander Brown
# Celina Drozd
print(hr.Person("Ala Mała", 5678))
# Ala Mała
Zmieńmy nasz kod obliczający wysokości wypłat:
import salaries as hr
def brutto_to_netto(brutto):
# Znacznie uproszczona funkcja
return brutto * 0.75
if __name__ == '__main__':
for worker in hr.workers:
salary_netto = brutto_to_netto(worker.salary)
print(f"{salary_netto} dla {worker.full_name}")
# 2592.0 dla Aleksander Brown
# 3425.25 dla Celina Drozd
Importowanie elementu z modułu
Zamiast importować cały moduł, możemy zaimportować z niego jeden tylko element. W takim przypadku zaczynamy od słówka from
, następnie podajemy nazwę modułu, używamy słówka import
i podajemy nazwę elementu do zaimportowania. Tak wyglądałoby zaimportowanie klasy Person
:
from salaries import Person
print(Person("Ala Mała", 5678))
# Ala Mała
Tak wyglądałoby zaimportowanie zmiennej workers
:
from salaries import workers
for person in workers:
print(person)
# Aleksander Brown
# Celina Drozd
Tak zaś wyglądałby nasz moduł po zaimportowaniu workers
:
from salaries import workers
def calculate_salary(brutto):
# Znacznie uproszczona funkcja
return brutto * 0.75
if __name__ == '__main__':
for worker in workers:
salary_netto = calculate_salary(worker.salary)
print(f"{salary_netto} dla {worker.full_name}")
# 2592.0 dla Aleksander Brown
# 3425.25 dla Celina Drozd
Dzięki zaimportowaniu elementu workers
, możemy się do niego odnosić bez odnoszenia się do nazwy modułu.
Importowanie pakietów
Różne projekty potrzebują często podobnych klas i funkcji. Aby nie pisać ich na okrągło, powstała koncepcja pakietów2. Pakiet to kod napisany najczęściej przez zupełnie innego programistę, który dołączamy do naszego projektu. Jest wiele twórców pakietów. Oni je tworzą i rozwijają, po czym umieszczają w internecie tak, byśmy łatwo mogli z nich skorzystać.
Python posiada bardzo dobre wsparcie dla udostępniania pakietów do użytku innym programistom. Dzięki temu można w nim zrobić naprawdę wiele w dosłownie kilku liniach kodu. Kilka dobrych przykładów pokazanych będzie w części czwartej, gdzie zobaczymy, jak dzięki pakietom możliwe jest w zaledwie kilku liniach nauczenie sztucznej inteligencji rozpoznawania pisma. Następny rozdział dedykowany jest instalowaniu zewnętrznych pakietów. Na ten moment, aby zobaczyć, jak importować z nich elementy, skorzystajmy z pakietów instalowanych razem z językiem Python.
Na początek jako przykład możemy wykorzystać pakiet math
, zawierający różne funkcje matematyczne. Zawiera ona na przykład funkcję factorial
, którą sami implementowaliśmy w rozdziale Funkcje. Aby zaimportować pakiet, zamiast nazwy pliku, używamy nazwy tego pakietu.
import math
print(math.factorial(5)) # 120
Następnym przykładem może być sin
, czyli bardzo istotna w matematyce funkcja sinus. Zamiast importować cały pakiet, zaimportujemy tylko funkcję sin
poprzez from math import sin
.
from math import sin
print(sin(2.3)) # 0.7457052121767203
Za kolejny przykład może posłużyć pakiet random
, który dostarcza funkcje zwracające losowe wartości. Jeśli na przykład użyjemy funkcji randint
zdefiniowanej w tym pakiecie, to dostaniemy losową liczbę całkowitą w zadanym zakresie.
import random
print(random.randint(1, 10))
# Losowa liczba od 1 do 10 (z 10 włącznie)
Funkcje z random
są powszechnie używane w przeróżnych grach losowych. Gdy ktoś implementuje Quiz, może użyć funkcji shuffle
do potasowania pytań. Zwłaszcza popularna jest funkcja random
, zwracająca liczbę rzeczywistą większą równą 0, ale mniejszą od 1.
from random import random
print(random())
# Liczba większa bądź równa 0, ale mniejsza od 1
Na koniec zobaczmy pakiet datetime
. Pozwala nam on operować na czasie, strefach czasowych, datach itp. Obecna w nim klasa datetime
definiuje datę. Jeśli chcemy ustalić, jaka jest w tym momencie data i godzina (na podstawie czasu naszego komputera), to możemy użyć datetime.now()
. Obiekt datetime
ma między innymi atrybuty year
, month
czy day
, pozwalające na odniesienie się do konkretnej składowej daty.
from datetime import datetime
battle = datetime(1410, 7, 15)
print(battle) # 1410-07-15 00:00:00
print(battle.year) # 1410
print(battle.month) # 7
print(battle.day) # 15
now = datetime.now()
print(now) # np. 2022-02-02 12:23:17.058498
Mnogość pakietów oraz łatwość korzystania z nich stanowią o sile języka Python. Nie mam wątpliwości, że jeszcze się o tym przekonasz.
HR to skrót od "Human Resources", czyli działu zasobów ludzkich.
Pojęcie pakietu jest używane podobnie, jak wiele innych języków programowania używa pojęcia biblioteki. Czasem oba te pojęcia używane są naprzemienne, ale w języku Python raczej przyjęło się mówić o pakietach.