article banner (priority)

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.

1:

HR to skrót od "Human Resources", czyli działu zasobów ludzkich.

2:

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.