❄️ Wstęp - funkcje
Wstęp
Funkcje pozwalają na organizowanie i strukturyzowanie kodu w logiczne bloki, które można wielokrotnie wywoływać. Dzięki funkcjom możemy uprościć programy, zmniejszyć ilość powtarzającego się kodu, a także sprawić, że nasze rozwiązania staną się bardziej modularne i łatwiejsze do utrzymania.
Zalety używania funkcji:
-
Modularność - dzielisz duży problem na mniejsze części, które są łatwiejsze do zarządzania.
-
Ponowne wykorzystanie - funkcję można wywoływać wielokrotnie w różnych miejscach programu.
-
Łatwiejsze utrzymanie - zmiana logiki w jednym miejscu (w funkcji) automatycznie wprowadza zmiany w całym programie.
-
Czytelność - funkcje pomagają tworzyć bardziej zrozumiały i uporządkowany kod.
import random
def generuj_losowa(seed=None):
# Ustawienie ziarna (seed) generatora liczb losowych
if seed is not None:
random.seed(seed)
# Generowanie losowej liczby z zakresu od 0 do 100
return random.randint(0, 100)
liczba = generuj_losowa(seed=42)
print(f"Wygenerowana liczba: {liczba}")
Trzy kluczowe elementy każdej funkcji:
-
Słowo kluczowe służace definiowaniu funkcji -
def -
Argumenty: definiowanie i podawane wewnątrz
() -
Zwracane wartości - słowo kluczowe
return
📝 Zadania
-
Napisz funkcję
zmien_wartosc(arg), która przyjmuje jeden argument i próbuje zmodyfikować ten argument w różny sposób w zależności od tego, czy jest on niemutowalny (w tym przypadku integerem) czy mutowalny (w tym przypadku listą).-
Jeśli jest listą, wykonaj
arg[0] = 'kalafior'. -
Jeśli jest integerem, wykonaj
arg = 65482652.
-
Wypisz przykłady dla obu przypadków, wypisz wartości przed i po wykonaniu funkcji. Jak się zachowują te obiekty?
Tip
Warto skorzystać z funkcji isinstance().
Teoria: mutowalne i niemutowalne obiekty w funkcjach
Kiedy zmienne są przekazywane do funkcji jako argumenty, Python nie tworzy ich kopii, lecz przekazuje referencję do oryginalnego obiektu. W związku z tym sposób, w jaki te obiekty zachowują się wewnątrz funkcji, zależy od ich typu – mutowalne lub niemutowalne.
Obiekty mutowalne (np. listy, słowniki):
Zmienne tego typu mogą być modyfikowane w miejscu. Jeśli zostaną przekazane jako argumenty do funkcji i ich zawartość zostanie zmieniona, zmiana ta wpłynie na oryginalny obiekt, który istnieje poza funkcją.
Obiekty niemutowalne (np. liczby, napisy, krotki):
Zmienne niemutowalne nie mogą być modyfikowane w miejscu. Każda próba modyfikacji powoduje utworzenie nowego obiektu. Z tego powodu, zmiany wprowadzone wewnątrz funkcji nie wpływają na oryginalny obiekt poza funkcją.
Dopasowywanie argumentów
Funkcje mogą przyjmować argumenty na różne sposoby, co umożliwia elastyczne przekazywanie danych. Kluczowe elementy to: argumenty pozycyjne, argumenty nazwane, wartości domyślne, oraz specjalne operatory *args i **kwargs, które pozwalają na przekazywanie zmiennej liczby argumentów.
# dodanie wartości domyślnych
def dodaj(a = 0, b = 0):
return a + b
# argumenty pozycyjne przekazywane są w podanej kolejności
print(dodaj(3, 5))
# argumenty nazwane można mieszać
print(b=5, a=3)
*args - zmienna liczba argumentów pozycyjnych
Wszystkie dodatkowe argumenty są zbierane w krotkę, dzięki czemu możemy obsłużyć więcej argumentów, niż zdefiniowano w sygnaturze funkcji.
def suma(*liczby):
return sum(liczby)
print(suma(1, 2, 3))
print(suma(10, 20))
**kwargs – zmienna liczba argumentów nazwanych
Argumenty są zbierane w słownik, co umożliwia przekazanie większej liczby argumentów nazwanych, niż przewidziano w sygnaturze funkcji.
def przedstaw_sie(**dane):
for klucz, wartosc in dane.items():
print(f"{klucz}: {wartosc}")
przedstaw_sie(imie="Jan", wiek=30, miasto="Kraków")
Mieszane użycie argumentów nie zawsze jest możliwe
Ważne jest, aby przestrzegać kolejności: najpierw argumenty pozycyjne, potem domyślne, następnie *args, a na końcu **kwargs.
def funkcja_mieszana(a, b=10, *args, **kwargs):
print(f"a: {a}, b: {b}")
print(f"Argumenty dodatkowe (args): {args}")
print(f"Argumenty nazwane (kwargs): {kwargs}")
funkcja_mieszana(1, 2, 3, 4, imie="Ania", wiek=25)
Oraz kilka niepoprawnych wywołań:
funkcja_mieszana()
funkcja_mieszana(1, 2, 3, 4, 5, a=6)
funkcja_mieszana(1, 2, 3, imie="Jan")
funkcja_mieszana(a=1, 20)
Sama definicja również może być niepoprawna:
def funkcja_mieszana(a=10, b):
print(f"a: {a}, b: {b}")
📝 Zadania
-
Napisz funkcję
zamowienie_produktu, która przyjmuje jeden obowiązkowy argument pozycyjnynazwa_produktui dwa obowiązkowe argumenty nazwane:cenaiilosc. Funkcja powinna zwracać text podsumowujący zamówienie, zawierające nazwę produktu, łączną cenę (cena * ilość) oraz ilość zamówionego produktu.-
Stwórz pustą listę, do której wstawisz wartości zwracane przez funkcję dla 3 różnych produktów.
-
Przeiteruj po wypełnionej liście, wyświetl teksty.
-
Zmodyfikuj funkcję tak, żeby oprócz tekstu podsumowującego zwracała także wartość zamówienia.
-
Na koniec wyświetl sumaryczną wartość zamówień (sumę z każdego zamówionego produktu).
-
Dodaj wartość domyślną dla argumentu
iloscrówną 1.
-
Ważna informacja
Wykorzystaj poniższy początek definicji i go nie modyfikuj. Wymusi to podawanie argumentów po gwiazdce jedynie w formie nazwanej.
def zamowienie_produktu(nazwa_produktu, *, cena, ilosc):
- Napisz funkcję
stworz_raport, która przyjmuje dowolną liczbę argumentów pozycyjnych (*args) i nazwanych (**kwargs). Argumenty pozycyjne powinny reprezentować numery ID produktów, a argumenty nazwane - informacje o tych produktach (np. nazwa, cena). Funkcja powinna tworzyć i wyświetlać raport, w którym dla każdego ID produktu podane są szczegółowe informacje na jego temat.
Wywołanie funkcji powinno wyglądać następująco:
stworz_raport(101, 102, nazwa_101="Kubek termiczny", cena_101="45.99 zł", nazwa_102="Długopis", cena_102="4.99 zł")
Funkcje - praktyczne porady
- Funkcje powinny być niezależne od otoczenia - argumenty jako input, return jako output.
- Unikamy zmiennych globalnych.
- Nie modyfikujemy argumentów mutowalnych.
- Funkcja ma być mała i mieć jeden cel.
- Nie zmieniamy wartości zmiennych z innych modułów.
Atrybuty
Każdy obiekt w Pythonie może mieć swoje atrybuty. Służą one przechowywaniu dodatkowych informacji na ich temat lub umożliwiają dostęp do ich stanów wewnętrznych.
Do atrybutów odwołujemy się za pomocą notacji kropkowej, np. obiekt.atrybut. Funkcje, tak samo jak inne obiekty, mogą mieć swoje atrybuty.
def sample_function():
return "Hello, world!"
sample_function.description = "To jest przykładowa funkcja." # Dodanie niestandardowego atrybutu
print(sample_function.description)
Adnotacje
Jest to sposób na dodawanie informacji o typach danych używanych w kodzie. Choć Python jest językiem dynamicznie typowanym i nie wymaga jawnego określania typów, adnotacje dają programiście możliwość wskazania, jakie typy danych powinny być używane, co poprawia czytelność i ułatwia pracę w zespołach.
Idea adnotacji
Adnotacje stanowią coś w rodzaju "podpowiedzi" dla innych programistów oraz narzędzi analizujących kod (np. linterów, IDE), które mogą je wykorzystać do ułatwienia debugowania, uzupełniania kodu, czy znajdowania potencjalnych błędów.
W poniższym przykładzie argument name ma być typu str, tak samo zwracana przez funkcję wartość.
def greet(name: str) -> str:
return f"Hello, {name}!"
Zmienne także mogą posiadać swoje adnotacje.
age: int = 25
name: str = "Alice"
Zaawansowane typy importuje się z modułu typing.
from typing import List, Optional, Dict
def get_user_info(user_id: int) -> Optional[Dict[str, str]]:
if user_id == 1:
return {"name": "Hubert", "email": "hubert@example.com"}
return None # Funkcja może zwrócić słownik lub None