Poradnik funkcji w Pythonie dla początkujących: przykład kodu z def, parametrami i return

Funkcje w Pythonie dla początkujących

Jeśli masz za sobą choćby kilka linijek kodu w Pythonie, to z funkcji już korzystasz. print() jest funkcją. Tak samo len(), input() i int(). Ktoś je kiedyś napisał, nadał im nazwy i teraz możesz ich używać, w ogóle nie zaglądając do środka.

Ten artykuł jest o pisaniu własnych funkcji. To moment, w którym większość osób przestaje kopiować gotowe fragmenty z tutoriali i zaczyna budować rzeczy naprawdę swoje, więc nie będziemy się spieszyć. Zaczniemy od funkcji, która prawie nic nie robi, a skończymy na małym programie, który odpowiada na całkiem konkretne pytania o Marsa i Księżyc. Po drodze zatrzymamy się przy błędach, które u początkujących widzę co tydzień.

Każdy przykład poniżej działa bez żadnych zmian. Otwórz edytor i przepisuj kod, zamiast tylko czytać. To, co napiszesz własnymi palcami, zostaje w głowie dużo lepiej, a kilka literówek nauczy cię więcej niż cały ten tekst.

Czym właściwie jest funkcja

Funkcja to blok kodu z nazwą, który wykonuje jedno zadanie. Piszesz go raz, a potem uruchamiasz, po prostu używając jego nazwy.

Pomyśl o liście kontrolnej przed startem rakiety. Nikt w centrum kontroli lotów nie pisze jej od nowa przed każdym startem. Leży w jednym miejscu, ma swoją nazwę i kiedy ktoś mówi „przechodzimy listę przedstartową”, wszyscy dokładnie wiedzą, o które kroki chodzi. Z funkcjami jest tak samo. Opisujesz kroki raz, nadajesz im nazwę i od tej pory sama nazwa wystarczy.

Po co się w to bawić? Powodów jest kilka:

  • Przestajesz się powtarzać. Jeśli te same pięć linijek siedzi w trzech miejscach programu, to aż się proszą, żeby zrobić z nich funkcję.
  • Poprawki robisz w jednym miejscu. Błąd w funkcji poprawiasz raz. Błąd w skopiowanym kodzie trzeba wyłapać w każdej kopii. Którąś na pewno przeoczysz. Każdemu się to zdarza.
  • Kod zaczyna wyglądać jak plan. Linijka fuel = calculate_fuel(distance) od razu mówi, co się dzieje. Dwadzieścia linijek obliczeń już nie.

Twoja pierwsza funkcja

def greet_crew():
    print("Dzień dobry, załogo.")
    print("Wszystkie systemy sprawne.")

Linijka po linijce:

  • def mówi Pythonowi, że za chwilę zdefiniujesz funkcję. To skrót od angielskiego „define”.
  • greet_crew to nazwa. Obowiązują te same zasady co przy zmiennych: małe litery, słowa łączone podkreślnikiem, bez spacji i bez cyfry na początku.
  • Nawiasy () to miejsce na dane wejściowe. Ta funkcja żadnych nie przyjmuje, ale nawiasy i tak muszą być.
  • Dwukropek na końcu linii jest obowiązkowy. Jego brak to chyba najczęstszy błąd składni w całym tym temacie.
  • Wszystko, co jest wcięte pod linią z def, to ciało funkcji. Po wcięciach (standardem są cztery spacje) Python rozpoznaje, gdzie funkcja się zaczyna, a gdzie kończy.

Teraz uruchom ten kod. Nic się nie dzieje.

To nie błąd, choć prawie każdego za pierwszym razem to zbija z tropu. Zdefiniowanie funkcji przypomina zapisanie przepisu na kartce. Od samego zapisania obiad się nie ugotuje. Żeby kod faktycznie się wykonał, musisz funkcję wywołać, czyli napisać jej nazwę z nawiasami:

def greet_crew():
    print("Dzień dobry, załogo.")
    print("Wszystkie systemy sprawne.")

greet_crew()
greet_crew()

Wynik:

Dzień dobry, załogo.
Wszystkie systemy sprawne.
Dzień dobry, załogo.
Wszystkie systemy sprawne.

Dwa wywołania, dwa powitania. Możesz wywołać funkcję sto razy, a kroki i tak są opisane tylko raz.

Jedna zasada do zapamiętania: Python czyta plik od góry do dołu, więc def musi się pojawić przed pierwszym wywołaniem. Jeśli wstawisz greet_crew() nad definicją, dostaniesz NameError: name 'greet_crew' is not defined, bo w tym momencie Python naprawdę jeszcze o tej funkcji nie słyszał.

Przekazywanie danych: parametry i argumenty

Funkcja, która za każdym razem robi dokładnie to samo, czasem się przydaje, ale zwykle chcesz jej coś przekazać i pozwolić, żeby na tym pracowała.

def greet_astronaut(name):
    print(f"Na pokład wchodzi {name}. Witamy!")

greet_astronaut("Ada")
greet_astronaut("Mirek")

Wynik:

Na pokład wchodzi Ada. Witamy!
Na pokład wchodzi Mirek. Witamy!

Tutaj name to parametr. Potraktuj go jak puste miejsce, zmienną, która istnieje tylko wtedy, gdy funkcja działa. Kiedy wywołujesz greet_astronaut("Ada"), wartość "Ada" to argument. Python wkłada ją do parametru name na czas tego jednego wywołania, a przy kolejnym trafi tam to, co przekażesz następnym razem.

Ludzie ciągle mylą „parametr” z „argumentem”, nawet ci, którzy programują od lat, więc nie ma się czym przejmować. W skrócie: parametr to puste miejsce w definicji, a argument to konkretna wartość, którą wysyłasz przy wywołaniu.

Więcej niż jeden parametr

Kolejne parametry oddzielasz przecinkami:

def describe_planet(name, position):
    print(f"{name} to {position}. planeta od Słońca.")

describe_planet("Mars", 4)
describe_planet("Ziemia", 3)

Wynik:

Mars to 4. planeta od Słońca.
Ziemia to 3. planeta od Słońca.

Argumenty trafiają do parametrów według kolejności: pierwszy argument do pierwszego parametru, drugi do drugiego i tak dalej. Zamień je miejscami, a Python nie zaprotestuje. Po prostu zrobi to, co mu kazano:

describe_planet(4, "Mars")
4 to Mars. planeta od Słońca.

Python nie ma pojęcia, że „Mars” ma być nazwą, a 4 liczbą. Pilnowanie kolejności to twoje zadanie (da się to zrobić bezpieczniej, zaraz do tego dojdziemy).

Za to przy złej liczbie argumentów Python zatrzyma cię od razu:

greet_astronaut()
TypeError: greet_astronaut() missing 1 required positional argument: 'name'

Jak na komunikat o błędzie, ten jest wyjątkowo życzliwy. Mówi, której funkcji dotyczy, ilu argumentów brakuje i jak się nazywają. Komunikaty Pythona są po angielsku, ale naprawdę warto nauczyć się je czytać. Zwykle mówią dokładnie, co poszło nie tak.

return, czyli jak odebrać wynik

Jeśli masz przeczytać jakąś część tego artykułu dwa razy, niech to będzie ta. Różnica między print a return myli prawie każdego, kogo uczyłem. Zwykle wygląda to tak: „funkcja działa, widzę wynik, ale nie mogę nic z nim zrobić”.

Oto funkcja, która oblicza, co pokaże łazienkowa waga na Marsie. Grawitacja jest tam równa mniej więcej 38% ziemskiej, więc mnożymy przez 0.38. (Fizycy, wiem, masa się nie zmienia. Zmienia się liczba na wadze i to ją właśnie liczymy.)

def weight_on_mars(earth_weight):
    print(earth_weight * 0.38)

weight_on_mars(70)
26.6

Od razu jedna rzecz, która dotyczy szczególnie nas, piszących po polsku: w Pythonie część dziesiętną oddziela się kropką, nie przecinkiem. Jeśli z przyzwyczajenia napiszesz return earth_weight * 0,38, Python nie zgłosi żadnego błędu. Zwróci za to (0, 38), bo przecinek tworzy tu krotkę, o której jeszcze będzie mowa. Na taki błąd da się stracić pół wieczoru.

Wracając do funkcji. Na pierwszy rzut oka wszystko gra. Spróbuj jednak użyć tej liczby do czegoś więcej, na przykład dodać do niej 5 kg sprzętu:

result = weight_on_mars(70)
print(result + 5)
26.6
TypeError: unsupported operand type(s) for +: 'NoneType' and 'int'

Funkcja wypisała 26.6, więc liczba najwyraźniej istnieje. Dlaczego w takim razie result jest pusty? Bo print() tylko pokazuje wartość na ekranie. Niczego nie oddaje kodowi, który funkcję wywołał. Funkcja, która nigdzie nie używa return, zwraca specjalną wartość None, czyli „nic”. A do niczego nie da się dodać pięciu.

Poprawiona wersja:

def weight_on_mars(earth_weight):
    return earth_weight * 0.38

mars_weight = weight_on_mars(70)
print(mars_weight)
print(mars_weight + 5)
26.6
31.6

Na zajęciach tłumaczę to tak: print to centrum kontroli lotów, które odczytuje liczbę przez radio. Wszyscy słyszą, nikt nie zapisuje i sekundę później liczby już nie ma. return to podanie komuś kartki z zapisaną liczbą. Może ją zachować, coś z nią policzyć albo przekazać dalej.

Dobra zasada na start: jeśli funkcja coś oblicza, powinna zwrócić wynik przez return i zostawić decyzję, co z nim zrobić, kodowi, który ją wywołał. Może go wypisać, zapisać albo użyć w kolejnym obliczeniu. Funkcje, które oddzielają liczenie od wyświetlania, dużo łatwiej wykorzystać ponownie.

return kończy działanie funkcji

Gdy Python trafi na return, funkcja natychmiast się kończy. Wszystko, co jest dalej, w tym wywołaniu zostaje pominięte. Bardzo się to przydaje przy sprawdzaniu warunków:

def check_fuel(level):
    if level < 20:
        return "Przerwać start: za mało paliwa"
    return "Paliwo OK"

print(check_fuel(12))
print(check_fuel(85))
Przerwać start: za mało paliwa
Paliwo OK

Zwróć uwagę, że nie ma tu else. Jeśli paliwa jest za mało, pierwszy return kończy funkcję i ostatnia linijka się nie wykona. Jeśli paliwa wystarczy, Python pomija blok if i dochodzi do return "Paliwo OK". Ten wzorzec zobaczysz w prawdziwym kodzie na każdym kroku.

Wartości domyślne

Czasem parametr ma wartość, która w większości przypadków jest właściwa. Możesz ustawić ją jako domyślną i wtedy podaje się ją tylko wtedy, gdy potrzeba czegoś innego:

def weight_on_planet(earth_weight, gravity=0.38):
    return round(earth_weight * gravity, 1)

print(weight_on_planet(70))         # bez gravity, więc liczy dla Marsa (0.38)
print(weight_on_planet(70, 0.165))  # Księżyc
26.6
11.6

Przy okazji wynik jest zaokrąglony przez round(..., 1). Komputery przy mnożeniu ułamków potrafią wypluć coś w rodzaju 26.599999999999998, a zaokrąglenie do jednego miejsca po przecinku sprawia, że wynik da się normalnie przeczytać.

Jedna zasada: parametry z wartościami domyślnymi muszą stać po tych, które ich nie mają. Zapis def weight_on_planet(earth_weight=70, gravity): skończy się błędem SyntaxError z informacją, że parametr bez wartości domyślnej stoi za parametrem z wartością domyślną. Jak się nad tym zastanowić, to ma sens: Python nie wiedziałby, do którego miejsca przypisać pojedynczy argument.

Argumenty nazwane

Pamiętasz zamienione miejscami „Mars” i 4? Argumenty nazwane (ang. keyword arguments) rozwiązują ten problem. Zamiast polegać na kolejności, przy wywołaniu podajesz nazwę parametru:

def launch(rocket, destination, crew_size):
    print(f"Startuje {rocket}. Cel: {destination}. Załoga: {crew_size}.")

launch("Kestrel", "Księżyc", 4)
launch(destination="Mars", crew_size=6, rocket="Halcyon")
launch("Kestrel", crew_size=3, destination="Księżyc")
Startuje Kestrel. Cel: Księżyc. Załoga: 4.
Startuje Halcyon. Cel: Mars. Załoga: 6.
Startuje Kestrel. Cel: Księżyc. Załoga: 3.

Przy argumentach nazwanych kolejność nie ma znaczenia, a samo wywołanie mówi, co jest czym. Za pół roku, patrząc na launch("Halcyon", "Mars", 6), możesz się zastanawiać, co oznaczała ta szóstka. Przy crew_size=6 takiego problemu nie ma.

Oba sposoby można mieszać, tak jak w trzecim wywołaniu. Jedyna zasada: argumenty pozycyjne idą na początku.

Zwracanie kilku wartości naraz

Funkcja może oddać kilka wartości jednocześnie. Ta oblicza, ile czasu sygnał radiowy potrzebuje, żeby dolecieć do Marsa:

def signal_delay(distance_km):
    speed_of_light = 299_792  # km na sekundę
    seconds = distance_km / speed_of_light
    minutes = seconds / 60
    return seconds, minutes

sec, mins = signal_delay(225_000_000)
print(f"Wiadomość leci na Marsa około {round(mins, 1)} minuty.")
Wiadomość leci na Marsa około 12.5 minuty.

Kilka rzeczy wartych wyjaśnienia. Podkreślniki w 299_792 i 225_000_000 są tylko po to, żeby duże liczby łatwiej się czytało. Python je ignoruje. Z kolei 225 mln km to typowa odległość do Marsa, ale w rzeczywistości ciągle się ona zmienia, bo obie planety krążą wokół Słońca. Waha się od około 55 do około 400 mln km. Dlatego komenda wysłana do łazika marsjańskiego leci czasem około 3 minut, a czasem ponad 20.

Kiedy piszesz return seconds, minutes, Python pakuje obie wartości w krotkę (ang. tuple), czyli małą, niezmienną paczkę wartości. Gdybyś wypisał cały wynik, zobaczyłbyś (750.5203607834766, 12.50867267972461). Linijka sec, mins = signal_delay(...) rozpakowuje tę paczkę do dwóch osobnych zmiennych za jednym zamachem. Pilnuj tylko, żeby po lewej stronie było tyle nazw, ile wartości wraca z funkcji.

Zasięg zmiennych: co się dzieje w funkcji, zostaje w funkcji

def calculate_orbit():
    altitude = 400
    print(f"Wewnątrz funkcji altitude wynosi {altitude} km")

calculate_orbit()
print(altitude)
Wewnątrz funkcji altitude wynosi 400 km
NameError: name 'altitude' is not defined

Międzynarodowa Stacja Kosmiczna krąży mniej więcej 400 km nad Ziemią, ale poza tą funkcją zmienna altitude po prostu nie istnieje. Zmienne utworzone wewnątrz funkcji są lokalne. Pojawiają się, gdy funkcja startuje, i znikają, gdy kończy pracę. To dobra wiadomość. Dzięki temu dwie różne funkcje mogą mieć zmienną o nazwie total i nie będą sobie wchodzić w drogę.

Problemy zaczynają się w drugą stronę. Funkcja może odczytać zmienną z zewnątrz, ale jeśli spróbuje ją zmienić, robi się nieciekawie:

fuel = 100

def burn(amount):
    fuel = fuel - amount

burn(30)
UnboundLocalError: cannot access local variable 'fuel' where it is not associated with a value

Ponieważ wewnątrz funkcji jest przypisanie do fuel, Python uznaje fuel za zmienną lokalną w całej funkcji. A potem okazuje się, że próbujesz od niej odjąć, zanim w ogóle ma jakąś wartość. Na forach znajdziesz rady, żeby naprawić to słowem kluczowym global. Moja rada: na razie daj sobie z tym spokój i zrób tak:

def burn(fuel, amount):
    return fuel - amount

fuel = 100
fuel = burn(fuel, 30)
print(fuel)
70

Dane wchodzą przez parametry, wyniki wychodzą przez return. Trzymaj się tego nawyku, a twoje funkcje będą przewidywalne. Każdą z nich przetestujesz osobno, bez zastanawiania się, co w tym czasie robi reszta programu.

Nazwy funkcji i docstringi

Dobre nazwy funkcji to zwykle czasowniki albo krótkie wyrażenia, które od czasownika się zaczynają, bo funkcja coś robi: calculate_fuel, get_planet_name, print_report. W funkcjach, które odpowiadają na pytanie „tak czy nie”, dobrze sprawdza się przedrostek is_ albo has_. Zapis if is_ready_for_launch(crew): czyta się prawie jak zdanie. Nazwy w stylu do_stuff, func2 czy x za miesiąc nie powiedzą ci kompletnie nic.

Pewnie zauważasz, że nazwy w tym artykule są po angielsku. To celowe. Tak wygląda kod w prawdziwych projektach i w dokumentacji bibliotek, z których będziesz korzystać, więc lepiej przyzwyczaić się od początku. Teksty, które program wyświetla użytkownikowi, możesz spokojnie pisać po polsku.

Mam też taki prosty test: jeśli nie potrafisz opisać, co robi funkcja, bez użycia słowa „i”, to pewnie robi dwie rzeczy naraz i lepiej byłoby ją rozbić na dwie.

Funkcję możesz też opisać docstringiem, czyli tekstem w potrójnym cudzysłowie umieszczonym zaraz pod linią z def:

def weight_on_planet(earth_weight, gravity=0.38):
    """Zwraca, ile coś ważyłoby na innej planecie.

    gravity to grawitacja na powierzchni planety w stosunku do ziemskiej.
    Dla Marsa to około 0.38, dla Księżyca około 0.165.
    """
    return round(earth_weight * gravity, 1)

help(weight_on_planet)
Help on function weight_on_planet in module __main__:

weight_on_planet(earth_weight, gravity=0.38)
    Zwraca, ile coś ważyłoby na innej planecie.

    gravity to grawitacja na powierzchni planety w stosunku do ziemskiej.
    Dla Marsa to około 0.38, dla Księżyca około 0.165.

Python nie wykonuje docstringa, tylko go przechowuje. help() wyświetla go razem z parametrami i ich wartościami domyślnymi, a większość edytorów pokazuje go po najechaniu kursorem na nazwę funkcji. Przy małych funkcjach w zupełności wystarczy jedna linijka.

Błędy, które widzę co tydzień

1. Brak nawiasów przy wywołaniu

Samo greet_crew, bez nawiasów, nie wywołuje funkcji. Jedynie się do niej odwołuje. Python nie zgłosi błędu, po prostu nic się nie wykona. A jeśli to wypiszesz, dostaniesz coś tajemniczego:

print(greet_crew)
 

Liczba na końcu będzie u ciebie inna. Jeśli kiedykolwiek zobaczysz coś takiego w wyniku, znaczy to, że gdzieś brakuje ().

2. „None” pojawia się w wyniku

To bliski krewny pomyłki z print i return. Zdarza się, kiedy wypisujesz wynik funkcji, która sama już coś wypisuje:

def greet_crew():
    print("Dzień dobry, załogo.")

print(greet_crew())
Dzień dobry, załogo.
None

Funkcja wypisuje powitanie, potem zwraca None, a zewnętrzny print grzecznie wypisuje i to. Albo wywołaj funkcję bez zewnętrznego print, albo zmień ją tak, żeby zwracała tekst, zamiast go wypisywać.

3. return wcięty o jeden poziom za daleko

def total_fuel(tanks):
    total = 0
    for tank in tanks:
        total = total + tank
        return total

print(total_fuel([100, 250, 80]))
100

Powinno wyjść 430. Ponieważ return siedzi wewnątrz pętli for, funkcja kończy się już w pierwszym obiegu. Przesuń go o jeden poziom w lewo, tak żeby stał w jednej linii z for:

def total_fuel(tanks):
    total = 0
    for tank in tanks:
        total = total + tank
    return total

Teraz funkcja czeka, aż pętla się skończy. Kiedy dostajesz podejrzanie mały wynik, najpierw sprawdź, gdzie jest wcięty return.

4. Funkcja napisana, ale nigdy niewywołana

„Program się uruchamia, ale nic się nie dzieje.” W dziewięciu przypadkach na dziesięć funkcja jest zdefiniowana bezbłędnie, tylko nigdzie nie ma linijki, która by ją wywołała. Zjedź na dół pliku i sprawdź.

Wszystko razem

Oto mały program, który korzysta z większości rzeczy omówionych wyżej. Drukuje krótki raport dla członków załogi lecących w różne miejsca: co pokaże tam ich waga i ile czasu wiadomość radiowa będzie leciała do domu.

SPEED_OF_LIGHT = 299_792  # km na sekundę


def weight_on_planet(earth_weight, gravity):
    """Zwraca wagę na innym ciele niebieskim, zaokrągloną do 1 miejsca."""
    return round(earth_weight * gravity, 1)


def signal_delay_seconds(distance_km):
    """Zwraca, ile sekund sygnał radiowy potrzebuje na pokonanie distance_km."""
    return distance_km / SPEED_OF_LIGHT


def format_delay(seconds):
    """Zamienia liczbę sekund na czytelny tekst."""
    if seconds < 60:
        return f"{round(seconds, 1)} sekundy"
    return f"{round(seconds / 60, 1)} minuty"


def mission_report(name, earth_weight, destination, gravity, distance_km):
    """Wypisuje krótki raport dla jednego członka załogi."""
    weight = weight_on_planet(earth_weight, gravity)
    delay = format_delay(signal_delay_seconds(distance_km))

    print(f"Raport misji: {name}")
    print(f"  Cel: {destination}")
    print(f"  Wskazanie wagi na miejscu: {weight} kg")
    print(f"  Opóźnienie łączności z Ziemią: {delay}")
    print()


mission_report("Ada", 62, "Mars", 0.38, 225_000_000)
mission_report("Mirek", 75, "Księżyc", 0.165, 384_400)
Raport misji: Ada
  Cel: Mars
  Wskazanie wagi na miejscu: 23.6 kg
  Opóźnienie łączności z Ziemią: 12.5 minuty

Raport misji: Mirek
  Cel: Księżyc
  Wskazanie wagi na miejscu: 12.4 kg
  Opóźnienie łączności z Ziemią: 1.3 sekundy

Poświęć chwilę na przeczytanie mission_report. Ta funkcja sama niczego nie liczy. Pyta inne funkcje o wyniki i układa je na ekranie. Każda z mniejszych funkcji robi jedną rzecz i da się ją sprawdzić osobno. Wpisz w edytorze print(format_delay(90)) i zobacz, czy dostajesz 1.5 minuty, zanim w ogóle zajrzysz do raportu.

Taki podział opłaca się, kiedy coś trzeba zmienić. Chcesz pokazywać opóźnienie w godzinach dla czegoś tak odległego jak Neptun? Zmieniasz format_delay i nic więcej. Chcesz dodać Jowisza? Dopisujesz jedno wywołanie mission_report z jego grawitacją (około 2.53) i odległością od Ziemi, którą zostawiam ci do sprawdzenia. Większe programy powstają w dużej mierze właśnie tak: z małych funkcji, z których każda robi jedno i które wywołują się nawzajem.

Wielkie litery w SPEED_OF_LIGHT to umowna konwencja w Pythonie dla wartości, które ustawia się raz i potem już się ich nie zmienia. Python tego nie pilnuje. To informacja dla każdego, kto będzie czytał twój kod, łącznie z tobą za kilka miesięcy.

Projekty na początek

Czytanie o funkcjach zaprowadzi cię tylko do pewnego momentu. Prawdziwa nauka zaczyna się wtedy, gdy siedzisz nad własnym kodem i zastanawiasz się, skąd się wzięło to None. Oto sześć projektów, mniej więcej od najłatwiejszego do najtrudniejszego. Zrób przynajmniej dwa pierwsze, nawet jeśli wydają się zbyt proste.

1. Planetarna waga

Zapytaj użytkownika o jego wagę za pomocą input(), a potem pokaż, co wskazałaby waga na Księżycu, Marsie i Jowiszu. Napisz jedną funkcję, która liczy, i osobną, która wypisuje wyniki.

Podpowiedź: input() zawsze zwraca tekst, więc przed obliczeniami zamień go na liczbę przez float(). Jeśli użytkownik wpisze liczbę z przecinkiem, float() zgłosi błąd, więc możesz wcześniej zamienić przecinek na kropkę metodą .replace(",", "."). Wersja ambitniejsza: dodaj funkcję, która odrzuca liczby ujemne i pyta jeszcze raz.

2. Odliczanie

Napisz countdown(start), która wypisuje liczby od start w dół do 1, a na końcu „Start!”. Potem dodaj drugi parametr na ten końcowy komunikat, z wartością domyślną „Start!”.

Wersja ambitniejsza: dodaj parametr step, tak żeby countdown(100, step=10) odliczało 100, 90, 80 i tak dalej.

3. Przelicznik temperatur

Naukowcy i agencje kosmiczne często podają temperatury w kelwinach. Napisz trzy małe funkcje: celsius_to_kelvin, kelvin_to_celsius i celsius_to_fahrenheit. Każda przyjmuje jedną liczbę i zwraca jedną liczbę. Żadnego wypisywania w środku.

Podpowiedź: kelwiny to stopnie Celsjusza plus 273,15, a stopnie Fahrenheita to stopnie Celsjusza razy 9/5 plus 32. Wersja ambitniejsza: napisz kelvin_to_fahrenheit, wywołując dwie funkcje, które już masz, zamiast pisać nowy wzór.

4. Generator znaków wywoławczych

Napisz make_callsign(first_name, last_name), która zwraca coś w rodzaju "ADA-LOV": pierwsze trzy litery imienia i nazwiska, wielkimi literami, połączone myślnikiem.

Podpowiedź: poczytaj o wycinaniu fragmentów tekstu (name[:3]) i o metodzie .upper(). Wersja ambitniejsza: użyj modułu random, żeby dodać na końcu dwie losowe cyfry.

5. Decyzja: startujemy czy nie?

Napisz is_go_for_launch(fuel, wind_speed, crew_ready), która zwraca True tylko wtedy, gdy paliwa jest co najmniej 90%, wiatr wieje słabiej niż 30 km/h, a załoga jest gotowa. W przeciwnym razie zwraca False. Potem napisz drugą funkcję, która ją wywołuje i wypisuje wyraźne „START” albo „WSTRZYMANY”.

Podpowiedź: wczesne return, jak w check_fuel wyżej, sprawią, że kod będzie czytelny. Wersja ambitniejsza: niech druga funkcja mówi też, dlaczego start został wstrzymany.

6. Quiz kosmiczny

Napisz ask_question(question, correct_answer), która wyświetla pytanie, wczytuje odpowiedź i zwraca True albo False. Potem napisz run_quiz(), która zadaje pięć pytań, liczy punkty i na końcu podaje wynik.

Podpowiedź: przed porównaniem użyj .strip().lower() na obu odpowiedziach, żeby „Mars”, „mars ” i „MARS” liczyły się jako poprawne. Ten projekt łączy prawie wszystko z tego artykułu, więc nie spiesz się.

Najczęściej zadawane pytania

Czym różni się parametr od argumentu?

Parametr to nazwa w definicji funkcji, jak name w def greet_astronaut(name):. Argument to konkretna wartość przekazana przy wywołaniu, na przykład "Ada". W luźnej rozmowie te słowa często używane są zamiennie i nikt nie będzie miał ci tego za złe.

Kiedy warto zamienić kod w funkcję?

Są dwa sygnały. Pierwszy: te same linijki, albo prawie te same, pojawiają się w kodzie więcej niż raz. Drugi: fragment kodu da się sensownie nazwać. Nawet jeśli print_report() uruchamia się tylko raz, przeniesienie tego kodu do funkcji skraca główną część programu i ułatwia jej czytanie. Nie przesadzaj jednak w drugą stronę. Funkcja powinna wykonywać zadanie, które warto nazwać.

Czy każda funkcja musi mieć return?

Nie. Funkcje, które coś robią, na przykład wypisują raport albo zapisują plik, często niczego nie zwracają i to jest w porządku. Python i tak po cichu zwróci za nie None. Za to funkcje, które coś obliczają, prawie zawsze powinny zwracać wynik.

Skąd w moim wyniku bierze się „None”?

Prawie zawsze stąd, że wypisujesz wynik funkcji, która nic nie zwraca, na przykład print(greet_crew()). Funkcja sama coś wypisuje, potem oddaje None, a twój zewnętrzny print je pokazuje. Szczegóły znajdziesz w błędzie numer 2 powyżej.

Czy funkcja może wywołać inną funkcję?

Tak, i to bez przerwy. mission_report z przykładu wyżej wywołuje trzy inne. Funkcja może nawet wywołać samą siebie. Nazywa się to rekurencją i przy niektórych problemach bardzo się przydaje, ale to temat na później.

Ile parametrów może mieć funkcja?

W praktyce nie ma limitu, ale powyżej czterech, pięciu parametrów wywołania robią się nieczytelne i łatwo o pomyłkę. Długa lista parametrów często oznacza, że funkcja próbuje robić za dużo. Jeśli naprawdę potrzebujesz kilku, argumenty nazwane bardzo pomagają.

Czym różni się funkcja od metody?

Metoda to funkcja należąca do obiektu, którą wywołujesz po kropce: "mars".upper() albo crew.append("Ada"). Działa tak samo jak funkcja: przyjmuje argumenty i może zwracać wartości. Własne metody zaczniesz pisać, gdy dojdziesz do klas.

Co to są *args i **kwargs?

Pozwalają funkcji przyjąć dowolną liczbę argumentów pozycyjnych albo nazwanych. Tak działa na przykład print() i dlatego możesz mu przekazać tyle wartości, ile chcesz. Zobaczysz je w cudzym kodzie, ale na razie nie są ci potrzebne. Najpierw dobrze opanuj zwykłe parametry.

Co to jest lambda?

Malutka funkcja bez nazwy, zapisana w jednej linijce, na przykład lambda x: x * 0.38. Używa się jej głównie wtedy, gdy potrzebna jest funkcja na chwilę, na przykład przy sortowaniu. Wszystko, co zrobi lambda, zrobi też zwykły def, a wersja z def zwykle czyta się łatwiej. Lambdy zostaw sobie na później.

Czy w funkcjach używać zmiennych globalnych?

Na początku lepiej nie. Odczytanie stałej wartości, takiej jak SPEED_OF_LIGHT, jest w porządku. Natomiast zmienianie zmiennych globalnych z wnętrza funkcji sprawia, że błędy bardzo trudno wyśledzić, bo wartość mogła zmienić dowolna funkcja w dowolnym miejscu. Przekazuj dane jako argumenty, a wyniki oddawaj przez return.

Jak na jedno posiedzenie to sporo, więc nie martw się, jeśli jeszcze nie wszystko się ułożyło. Jeśli masz zapamiętać tylko dwie rzeczy, niech to będą te: funkcja nic nie robi, dopóki jej nie wywołasz, a return oddaje wartość, podczas gdy print tylko ją pokazuje. Reszta przyjdzie z praktyką, a projektów do ćwiczeń masz wyżej pod dostatkiem.

SPEED_OF_LIGHT = 299_792  # km na sekundę


def weight_on_planet(earth_weight, gravity):
    """Zwraca wagę na innym ciele niebieskim, zaokrągloną do 1 miejsca."""
    return round(earth_weight * gravity, 1)


def signal_delay_seconds(distance_km):
    """Zwraca, ile sekund sygnał radiowy potrzebuje na pokonanie distance_km."""
    return distance_km / SPEED_OF_LIGHT


def format_delay(seconds):
    """Zamienia liczbę sekund na czytelny tekst."""
    if seconds < 60:
        return f"{round(seconds, 1)} sekundy"
    return f"{round(seconds / 60, 1)} minuty"


def mission_report(name, earth_weight, destination, gravity, distance_km):
    """Wypisuje krótki raport dla jednego członka załogi."""
    weight = weight_on_planet(earth_weight, gravity)
    delay = format_delay(signal_delay_seconds(distance_km))

    print(f"Raport misji: {name}")
    print(f"  Cel: {destination}")
    print(f"  Wskazanie wagi na miejscu: {weight} kg")
    print(f"  Opóźnienie łączności z Ziemią: {delay}")
    print()


mission_report("Ada", 62, "Mars", 0.38, 225_000_000)
mission_report("Mirek", 75, "Księżyc", 0.165, 384_400)