def alebo lambda. +---------+
vstup -> | funkcia | -> výstup
+---------+
def ahoj(name="Daniel"):
return f"Ahoj, {name}!"
pozdrav = ahoj
print(pozdrav()) # "Ahoj, Daniel!"
print(pozdrav("Svet")) # "Ahoj, Svet!"
def add(a: int, b: int) -> int:
"""Vráti súčet dvoch čísel."""
return a + b
📚 Dokumentačné reťazce a typové anotácie · 📖 DIP3: Documentation Strings
príklady použitia, ktoré sú zároveň automaticky testovateľné pomocou modulu doctest.
def add(a: int, b: int) -> int:
"""Vráti súčet dvoch čísel.
>>> add(2, 3)
5
>>> add(-1, 1)
0
"""
return a + b
📚 Používateľom definované funkcie · 📖 DIP3: Everything Is An Object
def ahoj(meno="svet"):
print(f"Ahoj {meno}")
pozdrav = ahoj
pozdrav()
Funkcia vráti hodnotu, podľa ktorej sa triedi:
# Triedenie podľa dĺžky slova
words = ["apple", "banana", "cherry", "date"]
def length(word):
return len(word)
sorted_words = sorted(words, key=length)
Ako by sme triedili podľa počtu samohlások?
Máme zoznam n-tíc, ktoré obsahujú meno, známku a vek študenta. Chceme ich zoradiť podľa mena a veku.
student_tuples = [
('john', 'A', 15),
('jane', 'B', 12),
('dave', 'B', 10),
]
# Funkcia vyberie a vráti postupnosť hodnôt, podľa ktorých sa má triediť
def name_and_age(student):
return student[0], student[2]
sorted_students = sorted(student_tuples, key=name_and_age)
Funkciu vieme definovať aj priamo na mieste:
data = [
{"name": "Alice", "age": 30},
{"name": "Bob", "age": 25},
{"name": "Charlie", "age": 35},
]
# sorted je generátor
sorted_data = sorted(data, key=lambda x: x["age"])
# Výraz za dvojbodkou vráti hodnotu, podľa ktorej sa triedi.
📚 Lambda
yielddef generate_numbers(n):
for i in range(n):
yield i * 2
for number in generate_numbers(10):
print(number)
def sum_list(items):
# Ukončovacia podmienka identifikuje triviálny prípad
# V tomto prípade je to prázdny zoznam, ktorý má súčet 0
if not items:
return 0
# Vyberieme posledný prvok bez úpravy vstupného zoznamu
last_item = items[-1]
# výsledok je súčet posledného prvku a súčtu zvyšku zoznamu
# (Všimnite si: items[:-1] kopíruje zoznam — efektívnejšie je pracovať s indexom.)
return last_item + sum_list(items[:-1]) # Rekurzívne volanie funkcie
print(sum_list([1, 2, 3, 4])) # 10
Každá rekurzívna funkcia potrebuje:
def factorial(n):
if n == 0: # ukončovacia podmienka
return 1
return n * factorial(n - 1) # rekurzívny krok
Každé volanie funkcie sa ukladá do pamäte, až kým sa nedosiahne ukončovacia podmienka. Potom sa výsledky postupne vracajú späť.
factorial(4)
= 4 * factorial(3)
= 4 * 3 * factorial(2)
= 4 * 3 * 2 * factorial(1)
= 4 * 3 * 2 * 1 * factorial(0)
= 4 * 3 * 2 * 1 * 1
= 24
Ak je rekurzia príliš hlboká, môže spôsobiť vyčerpanie zásobníka (stack overflow). V takom prípade je vhodné použiť cyklus.
def factorial(n):
result = 1
for i in range(2, n + 1):
result *= i
return result
range je generátor postupností
Funkcionálne programovanie (FP) je štýl programovania, kde:
📚 Functional Programming HOWTO · 📖 DIP3: Closures & Generators
+---------+
vstup -> | funkcia | -> výstup
+---------+
Výhody:
Menia globálne premenné, čítajú alebo zapisujú do súborov, terminálu, siete, databázy...
stav
^ |
| v
+---------+
vstup -> | funkcia | -> výstup
+---------+
|
v
vedľajší efekt
dva zavolania takej funkcie môžu mať dva rôzne výsledky
total = 0
def add(a):
global total
print("Ahoj Svet") # zmení stav terminálu
total += a # zmení stav pamäte
def add(a, b):
return a + b
assert add(2, 3) == 5
def apply_function(f, x):
return f(x)
print(apply_function(lambda x: x * 2, 5))
📚 Functional Programming HOWTO · 📖 DIP3: A List Of Functions
map()filter()reduce()+-----------------+ +---------+ +-----------------+
| vstupný zoznam | → | funkcia | → | výstupný zoznam |
+-----------------+ +---------+ +-----------------+
map()Zoznam vstupov vytvorí zoznam výstupov transformáciou podľa zadanej funkcie.
numbers = [1, 2, 3]
result = list(map(lambda x: x * 2, numbers))
filter()Podľa zadanej funkcie vyberie zoznam vstupov, ktoré spĺňajú podmienku.
numbers = [1, 2, 3, 4]
result = list(filter(lambda x: x % 2 == 0, numbers))
– redukuje zoznam na jednu hodnotu
+---------+ +---------+ +--------+
| zoznam | → | funkcia | → |hodnota |
+---------+ +---------+ +--------+
Funkcia má dva operandy. Prvý operand je akumulátor, druhý je aktuálny prvok zoznamu. Výsledok sa stáva novým akumulátorom.
akumulátor = f(akumulátor, prvok)
reduce()Vypočíta súčet zoznamu.
from functools import reduce
numbers = [1, 2, 3, 4]
result = reduce(lambda x, y: x + y, numbers)
Python má elegantnejšiu alternatívu k map a filter:
numbers = [1, 2, 3, 4]
result = [x * 2 for x in numbers if x % 2 == 0]
Uzáver zostaví funkciu na požiadanie podľa zadaných parametrov.
def multiplier(n):
def multiply(x):
return x * n
return multiply
double = multiplier(2)
print(double(5))
📚 Definovanie funkcií a uzávery · 📖 DIP3: Closures & Generators
Dekorátor je funkcia (uzáver), ktorá modifikuje správanie inej funkcie:
Každé ďalšie volanie funkcie s rovnakými argumentmi vráti uložený výsledok, namiesto opätovného výpočtu.
Funkcia by mala byť čistá (bez vedľajších efektov), aby sa dala bezpečne cache-ovať.
from functools import lru_cache
@lru_cache(maxsize=None)
def fib(n):
if n < 2:
return n
return fib(n - 1) + fib(n - 2)
print(fib(50)) # 12586269025 — okamžite (niekoľko ms)
# bez @lru_cache by trvalo minúty
meranie času vykonania funkcie
import time
def meraj_cas(f):
def obalka(*args, **kwargs):
start = time.perf_counter()
result = f(*args, **kwargs)
end = time.perf_counter()
print(f"{f.__name__}: {end - start:.4f}s")
return result
return obalka
@meraj_cas
def pomaly_vypocet():
total = sum(i ** 2 for i in range(10_000_000))
return total
pomaly_vypocet()
# pomaly_vypocet: 2.1342s
Parametre cez dvojitý uzáver:
def opakovane(n):
def decorator(f):
def obalka(*args, **kwargs):
for _ in range(n):
result = f(*args, **kwargs)
return result # vráti iba výsledok posledného volania
return obalka
return decorator
@opakovane(3)
def ahoj():
print("Ahoj")
ahoj()
# Ahoj
# Ahoj
# Ahoj
* a *** rozbaľuje zoznam alebo n-ticu na jednotlivé argumenty funkcie.** rozbaľuje slovník na jednotlivé argumenty funkcie typu kľúč hodnota.def sum_all(*args, **kwargs):
# povinné argumenty sú v zozname args
print("args:", args)
# nepovinné argumenty s predvolenou hodnotou sú v slovníku kwargs
print("kwargs:", kwargs)
Príklad na dekorátor, ktorý vypíše všetky argumenty funkcie:
def vypis_argumenty(f):
def obalka(*args, **kwargs):
print(f"Volanie funkcie {f.__name__} s argumentami:")
print("args:", args)
print("kwargs:", kwargs)
return f(*args, **kwargs)
return obalka
@vypis_argumenty
def add(a, b=0):
return a + b
add(1, b=2)
Problém: zo zoznamu objednávok odstrániť objednávky s nulovou sumou, vypočítať DPH z každej platnej objednávky a získať súhrnné hodnoty.
Pri peniazoch nepoužívame float, pretože desatinné čísla sa v počítači
nedajú vždy reprezentovať presne. Použijeme Decimal a sadzby zapíšeme
ako reťazce.
from decimal import Decimal
orders = [
{"id": 1, "amount": Decimal("100.00"), "vat_rate": Decimal("0.20")},
{"id": 2, "amount": Decimal("0.00"), "vat_rate": Decimal("0.20")}, # vyraď
{"id": 3, "amount": Decimal("250.00"), "vat_rate": Decimal("0.10")},
{"id": 4, "amount": Decimal("80.00"), "vat_rate": Decimal("0.20")},
]
Očakávaný výsledok pre platné objednávky:
suma bez DPH: 430.00
DPH: 61.00
suma s DPH: 491.00
valid_orders = []
total_net = Decimal("0")
total_vat = Decimal("0")
for order in orders:
if order["amount"] > 0:
vat = order["amount"] * order["vat_rate"]
valid_orders.append({**order, "vat": vat})
total_net += order["amount"]
total_vat += vat
total_gross = total_net + total_vat
valid_orders = [o for o in orders if o["amount"] > 0]
with_vat = [
{**o, "vat": o["amount"] * o["vat_rate"]}
for o in valid_orders
]
total_net = sum((o["amount"] for o in with_vat), Decimal("0"))
total_vat = sum((o["vat"] for o in with_vat), Decimal("0"))
total_gross = total_net + total_vat
from functools import reduce
def is_valid(order):
return order["amount"] > 0
def add_vat(order):
vat = order["amount"] * order["vat_rate"]
return {**order, "vat": vat}
# 1. Odfiltrujeme nulové objednávky.
valid_orders = list(filter(is_valid, orders))
# 2. Vypočítame DPH bez zmeny pôvodných slovníkov.
with_vat = list(map(add_vat, valid_orders))
# 3. Zredukujeme objednávky na súhrnné hodnoty.
summary = reduce(
lambda result, order: {
"net": result["net"] + order["amount"],
"vat": result["vat"] + order["vat"],
},
with_vat,
{"net": Decimal("0"), "vat": Decimal("0")},
)
total_net = summary["net"]
total_vat = summary["vat"]
total_gross = total_net + total_vat
from multiprocessing import Pool
def process_order(order):
if not is_valid(order):
return None
return add_vat(order)
if __name__ == "__main__":
with Pool() as pool:
results = pool.map(process_order, orders)
results = [order for order in results if order is not None]
total_net = sum(
(order["amount"] for order in results),
Decimal("0"),
)
total_vat = sum(
(order["vat"] for order in results),
Decimal("0"),
)
total_gross = total_net + total_vat
Hadoop a Spark sú distribuované výpočtové systémy, ktoré využívajú map a reduce na spracovanie veľkých dát.
Príklad: počítanie počtu IP adries z logov z webového servera
MAP: rozdelíme logy na menšie časti a počítame IP adresy pre pre každú časť osobitne paralelne na viacerých uzloch.
REDUCE: zjednotíme čiastkové výsledky z jednotlivých uzlov do jedného výsledku.
+----------+ +-----+ +--------------------+
| časť dát | → | map | → | čiastkové výsledky |
+----------+ +-----+ +--------------------+
|
v
+--------+ +----------+
| reduce | -> | výsledok |
+--------+ +----------+
^
|
+----------+ +-----+ +--------------------+
| časť dát | → | map | → | čiastkové výsledky |
+----------+ +-----+ +--------------------+
Teaser: Objektovo orientované programovanie sa často zameriava na mutáciu stavu objektov.