Příprava na backendový pohovor z Pythonu

Otázky a odpovědi k pohovoru z Pythonu pro rok 2026

Cílená sada otázek k pohovoru z Pythonu pro backendové vývojáře. Každou odpověď využijte jako strukturu pro mluvený projev: začněte s popisem konceptu, přidejte konkrétní příklad a následně zmiňte kompromisy.

Spustit zkušební pohovor z PythonuKreditní karta není potřeba. K dispozici je 1 bezplatná lekce.
Procvičování technických pohovorů v angličtiněRežim pro lidi, pro které angličtina není rodným jazykem, určený k procvičování technických pohovorů.

Základní datový model Pythonu

1Jaké vestavěné datové typy má Python a které z nich jsou měnitelné (mutable)?

Python má skalární typy jako NoneType, bool, int, float a complex; textové a binární typy jako str, bytes a bytearray; sekvenční typy jako list, tuple a range; a typy kolekcí jako dict, set a frozenset. Důležitým rozdílem u pohovorů je měnitelnost (mutability): list, dict, set a bytearray lze měnit na místě (in-place), zatímco čísla, bool, str, bytes, tuple, range, frozenset a None jsou neměnitelné (immutable). Užitečnou nuancí je, že vlastní odkazy na prvky u typu tuple jsou pevně dané, ale vnitřní stav odkazovaného měnitelného objektu se stále může změnit.

Zkusit odpovědět na tuto otázku s trenérem AI

2Jak se v reálném kódu liší typy list, tuple, set a dict v Pythonu?

Typ list je uspořádaná měnitelná (mutable) sekvence, takže se hodí pro fronty úkolů, shromážděné výsledky a uspořádaná data, která se mění. Typ tuple je uspořádaný, ale neměnitelný (immutable), což je užitečné pro pevné záznamy, návratové hodnoty funkcí a hodnoty, které by neměly být znovu přiřazovány. Typ set ukládá unikátní hašovatelné (hashable) objekty a je ideální pro kontrolu příslušnosti k množině nebo pro odstranění duplicit. Typ dict mapuje hašovatelné klíče na hodnoty a představuje běžnou strukturu pro indexy, vyhledávání a data podobná formátu JSON. V moderním Pythonu dict zachovává pořadí vložení, ale jeho hlavním účelem zůstává stále přístup na základě klíče.

items = ["a", "b", "a"]
unique = set(items)
index = {"user_id": 42}
point = (10, 20)
Zkusit odpovědět na tuto otázku s trenérem AI

3Jaký je rozdíl mezi operátory `is` a `==`?

Operátor `==` se ptá, zda jsou si dva objekty rovny z hlediska hodnoty, obvykle pomocí metody `__eq__`. Operátor `is` zjišťuje, zda dvě reference ukazují na naprosto stejný objekt v paměti. V produkčním kódu by měl být operátor `is` vyhrazen především pro kontroly singletonů, jako například `value is None`, `value is True` (pouze pokud na identitě skutečně záleží), nebo pro objekty typu sentinel (strážní objekty). Nespoléhejte se na implementační detaily, jako je mezipaměť malých celých čísel (small integer caching) nebo interning řetězců (string interning); díky těmto detailům se může zdát, že `is` v příkladech funguje, přestože jde o sémanticky chybnou volbu.

value = None

if value is None:
    print("missing")
Zkusit odpovědět na tuto otázku s trenérem AI

4Co je hašovatelný (hashable) objekt a proč je to důležité?

Hašovatelný (hashable) objekt má hodnotu haše, která zůstává po celou dobu jeho životnosti stabilní, a má chování rovnosti, které je s tímto hašem konzistentní. Hašovatelnost je důležitá, protože na ní závisí klíče slovníků (`dict`) a prvky množin (`set`). Většina vestavěných neměnných typů je hašovatelná, ale ne všechny objekty uvnitř neměnného kontejneru se automaticky stávají hašovatelnými: ntice (`tuple`) obsahující seznam (`list`) hašovatelná není. Na pohovorech je dobré toto propojit s návrhem: pokud se objekt používá jako klíč, atributy, které se účastní metod `__eq__` nebo `__hash__`, se po vložení nesmí změnit, jinak se objekt může stát ve slovníku nebo množině nedosažitelným.

Zkusit odpovědět na tuto otázku s trenérem AI

5Jak funguje krájení (slicing), včetně záporných indexů a kroků?

Řez `sequence[start:stop:step]` zahrnuje index `start`, nezahrnuje `stop` a posouvá se o `step`. Chybějící hranice se odvodí ze směru kroku. Záporné indexy se počítají od konce, takže `s[-1]` je poslední prvek. Záporný krok postupuje zprava doleva; `s[::-1]` u mnoha sekvenčních typů vytvoří obrácenou sekvenci. Nevýhodou je, že krájení (slicing) často vytváří nový objekt, takže je sice velmi expresivní, ale pro velká data není vždy bez režie (overhead).

text = "interview"

print(text[1:5])
print(text[::-1])
Zkusit odpovědět na tuto otázku s trenérem AI

Funkce a obor platnosti (scope)

6Jakým způsobem Python předává argumenty funkcím?

Python předává reference na objekty pomocí přiřazení, což se často nazývá volání sdílením (call by sharing). Funkce obdrží lokální názvy parametrů, které jsou svázány se stejnými objekty, jaké poskytl volající. Pokud je objekt měnitelný (mutable), funkce jej může upravit a volající tuto změnu zaznamená. Pokud funkce parametru přiřadí novou hodnotu, změní se pouze toto lokální svázání. Kvalitní odpověď u pohovoru rozlišuje mezi změnou na místě (in-place mutation) a lokálním přepřiřazením: `items.append(...)` nebo `items.clear()` změní seznam volajícího, zatímco `items = []` uvnitř funkce pouze přesměruje lokální název na nový seznam.

def add_item(items):
    items.append("new")

def replace_items(items):
    items = []

values = []
add_item(values)
replace_items(values)

print(values)  # ["new"]
Zkusit odpovědět na tuto otázku s trenérem AI

7Proč jsou měnitelné (mutable) výchozí argumenty nebezpečné?

Výchozí hodnoty argumentů se vyhodnocují pouze jednou při definici funkce, nikoli při každém jejím volání. Pokud je výchozí hodnotou seznam (`list`) nebo slovník (`dict`), všechna volání mohou sdílet stejný objekt a nechtěně propouštět stav. Bezpečným návrhovým vzorem je `def f(items=None):` a následné vytvoření `items = []` uvnitř těla funkce, pokud je `items` `None`. Existují vzácné případy, kdy je sdílený stav záměrný, jako například malá vyrovnávací paměť (cache), ale v takovém případě by to mělo být explicitní a zdokumentované, protože to může čtenáře kódu překvapit.

def add_tag(tag, tags=[]):
    tags.append(tag)
    return tags

print(add_tag("python"))
print(add_tag("backend"))  # Same list reused

def safe_add_tag(tag, tags=None):
    tags = [] if tags is None else tags
    tags.append(tag)
    return tags
Zkusit odpovědět na tuto otázku s trenérem AI

8K čemu se používají *args a **kwargs?

*args shromažďuje dodatečné poziční argumenty do n-tice (tuple), zatímco **kwargs shromažďuje dodatečné pojmenované argumenty (keyword arguments) do slovníku (dict). Jsou užitečné pro obalovací funkce (wrappers), dekorátory (decorators), flexibilní API (Application Programming Interface) a přeposílání volání. Na místě volání se *iterable rozbalí na poziční hodnoty a **mapping se rozbalí na pojmenované hodnoty. Kompromisem je čitelnost: explicitní parametry jsou lepší, když má funkce stabilní veřejný kontrakt, zatímco args a kwargs jsou vhodnější na rozhraních, kde záleží na přeposílání nebo zpětné kompatibilitě.

def call_api(path, *args, **kwargs):
    print(path, args, kwargs)

call_api("/users", 1, 2, timeout=5)

values = [1, 2]
options = {"timeout": 5}
call_api("/users", *values, **options)
Zkusit odpovědět na tuto otázku s trenérem AI

9Jak Python překládá názvy proměnných pomocí pravidla LEGB?

Python hledá názvy v oborech platnosti (scopes): místní (Local), nadřazený/uzavírající (Enclosing), globální (Global) a vestavěný (Builtins). Čtení názvu probíhá v tomto pořadí. Přiřazení názvu uvnitř funkce standardně vytváří místní vazbu, a proto může kód vyvolat `UnboundLocalError`, pokud je název čten před místním přiřazením. Klíčové slovo `global` přesměrovává přiřazení do oboru platnosti modulu a `nonlocal` přesměrovává přiřazení do oboru platnosti nadřazené funkce. V kvalitním produkčním kódu by se obě měla používat střídmě, protože kvůli nim je tok stavu (state flow) méně zřejmý.

total = 0

def add(value):
    global total
    total += value

def counter():
    count = 0

    def increment():
        nonlocal count
        count += 1
        return count

    return increment
Zkusit odpovědět na tuto otázku s trenérem AI

10Co je to uzávěr (closure) a co je to pozdní vazba (late binding)?

Uzávěr (closure) je funkce, která si zachovává přístup k proměnným z obklopujícího oboru platnosti (enclosing scope) i poté, co vnější funkce skončila a vrátila výsledek. To je užitečné pro dekorátory, tovární funkce pro zpětná volání (callbacks) a předkonfigurované funkce. Pozdní vazba (late binding) znamená, že volná proměnná se vyhledává až při volání vnitřní funkce, nikoli při jejím vytvoření. Funkce lambda vytvořené ve smyčce tak mohou všechny vidět až konečnou hodnotu ze smyčky. Aktuální hodnotu lze zachytit pomocí výchozího argumentu, například `lambda i=i: i`, nebo pomocí menší tovární funkce.

callbacks = []

for i in range(3):
    callbacks.append(lambda: i)

print([callback() for callback in callbacks])  # [2, 2, 2]

callbacks = []
for i in range(3):
    callbacks.append(lambda i=i: i)

print([callback() for callback in callbacks])  # [0, 1, 2]
Zkusit odpovědět na tuto otázku s trenérem AI

OOP, iterace a správa prostředků

11Jak se liší třídní proměnné (class variables) od proměnných instance?

Třídní proměnná (class variable) je uložena v objektu třídy a sdílena všemi instancemi, pokud ji konkrétní instance nezastíní (shadowing) vlastním atributem. Proměnná instance (instance variable) je uložena v konkrétním objektu, obvykle přiřazená jako `self.name` v metodě `__init__`. Neměnné třídní konstanty jsou obvykle v pořádku, ale měnitelné atributy třídy bývají častým zdrojem chyb, pokud jsou omylem použity jako stav pro jednotlivé instance. Při pohovoru se hodí zmínit pořadí vyhledávání (lookup order): Python nejprve prohledá instanci, poté třídu a nakonec její předky.

class Cart:
    items = []

first = Cart()
second = Cart()

first.items.append("book")
print(second.items)  # ["book"]

class SafeCart:
    def __init__(self):
        self.items = []
Zkusit odpovědět na tuto otázku s trenérem AI

12Jak funguje dědičnost, MRO (Method Resolution Order) a `super()`?

Python vyhledává atributy (resolves attributes) pomocí pořadí překladu metod (MRO - Method Resolution Order), které je viditelné přes `ClassName.__mro__`. U jednoduché dědičnosti je to přímočaré, ale u vícenásobné dědičnosti Python používá C3 linearizaci k vytvoření konzistentního pořadí. `super()` neznamená přesně "zavolej mého rodiče"; znamená "zavolej další implementaci v MRO". To umožňuje kooperativní vícenásobnou dědičnost, pokud metody přijímají kompatibilní argumenty a každá třída volá `super()` správně.

class A:
    def save(self):
        print("A")

class B(A):
    def save(self):
        print("B")
        super().save()

class C(A):
    def save(self):
        print("C")
        super().save()

class D(B, C):
    pass

D().save()  # B, C, A
Zkusit odpovědět na tuto otázku s trenérem AI

13Jaký je rozdíl mezi iterovatelným objektem (iterable), iterátorem a generátorem?

Iterovatelný objekt (iterable) je jakýkoli objekt, který může vrátit iterátor z funkce `iter()`, jako je například seznam (list) nebo objekt souboru. Iterátor je stavový objekt, který implementuje metodu `__next__` a při vyčerpání vyvolá výjimku `StopIteration`. Generátor je pohodlný způsob, jak vytvořit iterátor pomocí klíčového slova `yield` nebo výrazu generátoru (generator expression). Generátory jsou paměťově efektivní, protože produkují hodnoty líně (lazily), ale obvykle jsou určeny pouze k jednomu průchodu (one-shot) a nelze je indexovat jako seznam.

def read_lines(path):
    with open(path, encoding="utf-8") as file:
        for line in file:
            yield line.strip()
Zkusit odpovědět na tuto otázku s trenérem AI

14Co je to správce kontextu (context manager) a proč je užitečný příkaz with?

Správce kontextu definuje chování při vstupu a výstupu pomocí __enter__ a __exit__, nebo jej lze vytvořit pomocí contextlib.contextmanager. Příkaz with činí správu prostředků explicitní a spolehlivou: soubory se uzavřou, zámky se uvolní a transakce se mohou potvrdit (commit) nebo vrátit zpět (roll back), a to i v případě výskytu výjimek. Metoda __exit__ přijímá informace o výjimce a může výjimku potlačit vrácením hodnoty True, ačkoliv potlačování chyb by se mělo používat opatrně.

from contextlib import contextmanager

with open("data.txt", encoding="utf-8") as file:
    data = file.read()

@contextmanager
def temporary_setting():
    print("enter")
    try:
        yield
    finally:
        print("exit")
Zkusit odpovědět na tuto otázku s trenérem AI

15Jak se liší mělká kopie (shallow copy) a hluboká kopie (deep copy)?

Mělká kopie (shallow copy) vytvoří nový vnější kontejner, ale zachová reference na stejné vnořené objekty. Hluboká kopie (deep copy) rekurzivně zkopíruje vnořené objekty tam, kde je to možné. Pro jednoduché ploché seznamy často postačí mělká kopie. U vnořených měnitelných (mutable) struktur však mělká kopie může stále sdílet stav a způsobit neočekávané chování. Hluboká kopie je výpočetně náročnější a může selhat s chybou TypeError nebo PicklingError u prostředků, jako jsou otevřené soubory, sokety, databázové relace nebo objekty s chováním citlivým na vlastní identitu.

from copy import copy, deepcopy

original = [[1], [2]]
shallow = copy(original)
deep = deepcopy(original)

original[0].append(99)

print(shallow)  # [[1, 99], [2]]
print(deep)     # [[1], [2]]
Zkusit odpovědět na tuto otázku s trenérem AI

Běhové prostředí, asynchronní programování a produkční kód

16Co je to GIL (Global Interpreter Lock) a jak ovlivňuje backendový kód?

Ve standardním CPythonu umožňuje GIL vykonávat bytecode (bajtový kód) Pythonu pouze jednomu vláknu současně. To znamená, že vlákna obvykle nezrychlují kód, který je náročný na procesor (CPU-bound). Vlákna jsou stále užitečná pro práci náročnou na I/O, protože GIL se během čekání na mnoho blokujících operací uvolní. Pro úlohy náročné na procesor použijte modul `multiprocessing`, nativní rozšíření, vektorizované nativní knihovny nebo oddělené služby. Pro pohovory v roce 2026 také zmiňte sestavení CPythonu verze 3.13+ s volnými vlákny (free-threaded): činí GIL volitelným pro kompatibilní kód, ale stále vyžadují testování závislostí a výkonnostní testy.

Zkusit odpovědět na tuto otázku s trenérem AI

17Kdy byste měli zvolit vlákna (threads), procesy (processes) nebo asyncio?

Vlákna použijte, pokud máte blokující I/O nebo knihovny, které vystavují pouze synchronní API. Procesy použijte pro práci náročnou na procesor (CPU-bound), izolaci nebo omezení dopadu pádu (crash containment), přičemž musíte počítat s režií na serializaci a paměť. Modul `asyncio` použijte, když máte velké množství souběžných I/O operací a asynchronně kompatibilní knihovny, jako jsou asynchronní databázové ovladače nebo HTTP (Hypertext Transfer Protocol) klienti. Praktická volba závisí také na vašem frameworku a závislostech: míchání asynchronního kódu s blokujícími voláními může zhoršit výkon.

Zkusit odpovědět na tuto otázku s trenérem AI

18K čemu je užitečné `async`/`await` ve webových službách v Pythonu?

`async` a `await` umožňují kooperativní souběžnost. Korutina vrátí řízení zpět do smyčky událostí (event loop), když čeká na I/O operaci, což umožní spuštění dalších úloh. To je užitečné pro API, která čekají na databáze, HTTP volání, fronty nebo sokety. Zázračně to ale nezrychlí kód, který je náročný na CPU; práce náročná na procesor zablokuje smyčku událostí, pokud není přesunuta do workeru, jiného procesu nebo nativního kódu. Ve FastAPI se koncový bod definovaný jako normální `def` spouští ve fondu vláken, zatímco blokující volání uvnitř `async def` mohou zablokovat smyčku událostí; `async` pomáhá pouze tehdy, když volaná cesta používá neblokující asynchronní I/O operace.

import asyncio

async def load_user():
    await asyncio.sleep(0.1)
    return {"id": 1}

async def main():
    user, settings = await asyncio.gather(
        load_user(),
        load_user(),
    )
    return user, settings
Zkusit odpovědět na tuto otázku s trenérem AI

19Jak by se měly zpracovávat výjimky v produkčním kódu v Pythonu?

Zachyťte nejkonkrétnější výjimku, kterou dokážete obsloužit, a zachyťte ji na vrstvě, která se dokáže zotavit, přidat kontext nebo ji přeložit na doménovou chybu. Vyhněte se holému `except` a skrytému pohlcování chyb, protože to skrývá ztrátu dat a provozní problémy. Použijte `raise` pro opětovné vyvolání stejné výjimky a `raise NewError(...) from exc` k zachování kauzálního kontextu. Dobrý produkční kód také loguje na hranicích rozhraní, vrací odpovídající HTTP chyby a udržuje zprávy výjimek užitečné, aniž by prozrazoval utajované informace (secrets).

try:
    result = parse_payload(payload)
except ValueError as exc:
    raise InvalidRequest("Invalid payload") from exc
Zkusit odpovědět na tuto otázku s trenérem AI

20Jak byste strukturovali a testovali produkční službu v Pythonu?

Oddělte doménovou logiku od kódu frameworku a infrastruktury, aby bylo možné testovat základní chování bez serveru nebo databáze. Používejte konfiguraci z prostředí nebo typovaná nastavení (typed settings), logování namísto příkazu `print`, injektáž závislostí (dependency injection) na hranicích a explicitní časové limity (timeouts) pro síťová volání. Pro testy kombinujte jednotkové testy (unit tests) pro čistou logiku, integrační testy pro databázi nebo externí hranice a několik end-to-end kontrol pro kritické toky. Mockujte externí systémy na zřetelných hranicích, ale testujte spíše pozorovatelné chování než privátní detaily implementace.

Zkusit odpovědět na tuto otázku s trenérem AI