Pytania rekrutacyjne Python

102 pytań — 20 odpowiedzi dostępnych od razu

Ten darmowy podgląd pytań rekrutacyjnych Python pozwala sprawdzić przygotowanie do rozmowy o języku używanym w backendzie, automatyzacji i analizie danych. Zawiera przykładowe pytania z pełnej puli 102 zagadnień.

Materiał obejmuje typy i struktury danych, funkcje, zakresy, programowanie obiektowe, iteratory i generatory. Fiszki pomagają powtórzyć idiomy Pythona oraz wyjaśniać ich praktyczne zastosowanie.

Darmowy start

Pierwsze 20 odpowiedzi są dostępne od razu

Przeglądaj pełną listę 102 pytań. Pełne odpowiedzi, przykłady kodu i materiały do nauki odblokujesz w panelu.

Odblokuj 82 odpowiedzi

Podstawy Pythona

Czym jest Python i jakie są jego najważniejsze cechy jako języka programowania?

Odpowiedź w 30 sekund: Python to wysokopoziomowy, ogólnego przeznaczenia język programowania, który stawia na czytelność kodu i zwięzłość składni. Jest interpretowany, dynamicznie typowany i wspiera wiele paradygmatów (obiektowy, proceduralny, funkcyjny). Jego siłą jest ogromny ekosystem bibliotek i niska bariera wejścia.

Odpowiedź w 2 minuty: Python został stworzony przez Guido van Rossuma na przełomie lat 80. i 90. z naciskiem na czytelność — kod ma być łatwy do napisania, ale przede wszystkim łatwy do późniejszego przeczytania przez człowieka. Stąd charakterystyczne wcięcia (ang. significant whitespace), które zamiast nawiasów klamrowych wyznaczają bloki kodu. Język jest wysokopoziomowy, co oznacza, że abstrahuje od zarządzania pamięcią i niskopoziomowych szczegółów sprzętu.

Najważniejsze cechy to: dynamiczne typowanie (typy ustalane są w trakcie wykonania), automatyczne zarządzanie pamięcią (zliczanie referencji plus garbage collector), oraz wieloparadygmatowość — można pisać proceduralnie, obiektowo lub funkcyjnie. Python jest też interpretowany (a dokładniej kompilowany do bytecode'u uruchamianego przez maszynę wirtualną), co przyspiesza cykl pisz-uruchom-popraw.

Cechą decydującą o popularności jest „baterie w zestawie" (batteries included) — bogata biblioteka standardowa pokrywająca zadania od obsługi sieci po przetwarzanie danych. Do tego dochodzi ogromny ekosystem zewnętrzny (PyPI): NumPy, pandas, Django, FastAPI, PyTorch. Dzięki temu Python dominuje w analizie danych, uczeniu maszynowym, automatyzacji, skryptowaniu i backendzie webowym.

Warto wspomnieć o powszechnym nieporozumieniu: Python bywa nazywany „wolnym". To prawda dla czystych obliczeń CPU-bound w pętlach, ale w praktyce wąskie gardła przenosi się do bibliotek napisanych w C (NumPy), a o produktywności programisty często decyduje czas pisania kodu, nie czas jego wykonania.

Materiały

Czym różni się język interpretowany od kompilowanego i do której grupy należy Python?

Odpowiedź w 30 sekund: Język kompilowany tłumaczy cały kod źródłowy na kod maszynowy przed uruchomieniem (np. C, Rust), a język interpretowany wykonuje kod instrukcja po instrukcji w trakcie działania. Python należy do grupy interpretowanych, choć w praktyce najpierw kompiluje kod do bytecode'u, który następnie wykonuje maszyna wirtualna.

Odpowiedź w 2 minuty: W klasycznym ujęciu kompilacja to jednorazowe przetłumaczenie całego programu na kod maszynowy zrozumiały dla procesora — efektem jest plik wykonywalny (np. .exe), który działa bez dalszego udziału kompilatora. Daje to wysoką wydajność, ale dłuższy cykl budowania i kod zależny od platformy. Interpretacja polega na analizowaniu i wykonywaniu kodu źródłowego „na bieżąco" przez program zwany interpreterem — kosztem szybkości zyskujemy przenośność i szybki feedback.

Podział ten jest jednak uproszczeniem, bo dotyczy implementacji, nie samego języka. CPython (referencyjna implementacja Pythona) działa dwuetapowo: najpierw kompiluje kod źródłowy do pośredniego bytecode'u, a następnie interpretuje ten bytecode w pętli ewaluacyjnej maszyny wirtualnej (PVM). Dlatego Python klasyfikujemy jako interpretowany — z perspektywy użytkownika nie ma osobnego kroku kompilacji, uruchamia się go bezpośrednio.

W praktyce ten model ma konsekwencje. Błędy składniowe wykrywane są podczas kompilacji do bytecode'u (przed wykonaniem), ale błędy typów czy logiczne ujawniają się dopiero w trakcie działania, gdy interpreter dotrze do problematycznej linii. Granica się zaciera również w innych implementacjach — np. PyPy używa kompilacji JIT (Just-In-Time), tłumacząc gorące fragmenty kodu na kod maszynowy w trakcie działania, co zbliża go wydajnościowo do języków kompilowanych.

Cecha Kompilowany (C) Interpretowany (Python)
Moment tłumaczenia Przed uruchomieniem W trakcie działania
Artefakt Plik wykonywalny Brak (lub .pyc bytecode)
Szybkość wykonania Wysoka Niższa
Cykl pisz–uruchom Wolniejszy (build) Natychmiastowy
Przenośność Zależna od platformy Wysoka

Materiały

Czym jest CPython i czym różni się od innych implementacji, takich jak PyPy, Jython czy IronPython?

Odpowiedź w 30 sekund: CPython to referencyjna i najpopularniejsza implementacja Pythona, napisana w języku C — to ją pobiera się z python.org. Alternatywy różnią się platformą uruchomieniową lub strategią wykonania: PyPy używa kompilacji JIT dla szybkości, Jython działa na JVM, a IronPython na platformie .NET.

Odpowiedź w 2 minuty: Należy odróżnić język Python (specyfikację) od jego implementacji (programu, który ten język wykonuje). CPython jest implementacją odniesienia: kompiluje kod do bytecode'u i interpretuje go w maszynie wirtualnej napisanej w C. Gdy mówimy „Python" bez kontekstu, niemal zawsze chodzi o CPython. Jego dużą zaletą jest doskonała integracja z bibliotekami w C (rozszerzenia CPython API), dlatego dominuje w ekosystemie naukowym (NumPy, SciPy). Jego ograniczeniem jest GIL (Global Interpreter Lock).

PyPy to implementacja napisana w RPython, której kluczową cechą jest kompilator JIT — analizuje kod w trakcie działania i tłumaczy często wykonywane fragmenty bezpośrednio na kod maszynowy. Dla długo działającego kodu CPU-bound potrafi być wielokrotnie szybszy od CPython. Cena to gorsza kompatybilność z rozszerzeniami C i wyższe zużycie pamięci na starcie.

Jython kompiluje Pythona do bytecode'u JVM, dzięki czemu kod Pythona może bezpośrednio korzystać z bibliotek Javy i działać w środowisku JVM. IronPython robi analogiczną rzecz dla platformy .NET (CLR), umożliwiając integrację z bibliotekami C#/.NET. Obie te implementacje zwykle pozostają w tyle za najnowszą wersją języka i nie obsługują rozszerzeń napisanych w C. Warto też wspomnieć o MicroPython — wersji dla mikrokontrolerów i systemów wbudowanych.

Implementacja Platforma / technologia Główna zaleta Główne ograniczenie
CPython C (referencyjna) Kompatybilność, ekosystem C GIL, szybkość CPU-bound
PyPy RPython + JIT Szybkość wykonania Słabsza zgodność z C-API
Jython JVM Integracja z Javą Opóźnione wersje, brak C-ext
IronPython .NET (CLR) Integracja z .NET Opóźnione wersje, brak C-ext

Materiały

Czym są pliki .pyc i jaką rolę pełni bytecode w działaniu Pythona?

Odpowiedź w 30 sekund: Pliki .pyc to skompilowany bytecode — pośrednia, zoptymalizowana reprezentacja kodu źródłowego, którą wykonuje maszyna wirtualna Pythona (PVM). CPython buforuje je w katalogu __pycache__, aby przy kolejnych uruchomieniach pominąć etap kompilacji i przyspieszyć start importowanych modułów.

Odpowiedź w 2 minuty: Gdy uruchamiasz program w Pythonie, CPython nie wykonuje kodu źródłowego bezpośrednio. Najpierw kompiluje go do bytecode'u — niskopoziomowego zestawu instrukcji niezależnych od platformy, lecz zależnych od wersji interpretera. Ten bytecode jest następnie wykonywany w pętli ewaluacyjnej maszyny wirtualnej Pythona (PVM). Bytecode to warstwa pośrednia między czytelnym dla człowieka kodem a operacjami, które rozumie interpreter.

Pliki .pyc to zapisany na dysku, zbuforowany bytecode. Co ważne, są one tworzone tylko dla importowanych modułów, a nie dla skryptu uruchamianego bezpośrednio (python main.py). Trafiają do katalogu __pycache__ z nazwą zawierającą wersję interpretera, np. module.cpython-312.pyc. Dzięki temu przy ponownym imporcie CPython sprawdza, czy źródło się nie zmieniło (porównując znacznik czasu lub hash), i jeśli nie — wczytuje gotowy bytecode, oszczędzając czas kompilacji. To optymalizacja czasu startu, a nie czasu wykonania — sam działający kod nie staje się szybszy.

Bytecode można podejrzeć modułem dis z biblioteki standardowej, co bywa przydatne przy zrozumieniu, jak Python interpretuje dany fragment kodu i przy mikrooptymalizacjach. Warto pamiętać o dwóch pułapkach: .pyc nie jest formą zabezpieczenia kodu (łatwo go zdekompilować) ani nie jest przenośny między wersjami Pythona — bytecode zmienia się między wydaniami.

Przykład kodu:

import dis

def dodaj(a, b):
    # Prosta funkcja, której bytecode podejrzymy
    return a + b

# dis pokazuje instrukcje bytecode'u tej funkcji
dis.dis(dodaj)

# Przykładowy wynik:
#   LOAD_FAST   a       # załaduj argument a na stos
#   LOAD_FAST   b       # załaduj argument b na stos
#   BINARY_OP   + (add) # wykonaj dodawanie
#   RETURN_VALUE        # zwróć wynik ze szczytu stosu

Diagram:

flowchart LR
    A["kod źródłowy (.py)"] --> B["kompilator CPython"]
    B --> C["bytecode (.pyc w __pycache__)"]
    C --> D["maszyna wirtualna (PVM)"]
    D --> E["wykonanie programu"]

Materiały

Czym jest PEP i dlaczego PEP 8 ma tak duże znaczenie?

Odpowiedź w 30 sekund: PEP (Python Enhancement Proposal) to ustandaryzowany dokument projektowy opisujący nową funkcję języka, proces lub wytyczne dla społeczności Pythona. PEP 8 to przewodnik stylu kodowania, który ma kluczowe znaczenie, bo ujednolica wygląd kodu, dzięki czemu jest on spójny i czytelny niezależnie od autora.

Odpowiedź w 2 minuty: PEP (Python Enhancement Proposal) to formalny mechanizm proponowania zmian i dokumentowania decyzji w ekosystemie Pythona — odpowiednik specyfikacji RFC. Każdy PEP ma numer i kategorię. Wyróżniamy Standards Track (nowe funkcje języka lub biblioteki, np. PEP 484 o adnotacjach typów), Informational (wytyczne i informacje, np. PEP 8) oraz Process (zmiany w procesach społeczności). To właśnie poprzez PEP-y język ewoluuje w sposób przejrzysty i udokumentowany — można prześledzić, dlaczego dana decyzja została podjęta.

PEP 8 to oficjalny przewodnik stylu dla kodu w Pythonie, napisany m.in. przez samego Guido van Rossuma. Określa konwencje: 4 spacje na wcięcie (nie taby), limit długości linii (zwyczajowo 79 znaków), nazewnictwo (snake_case dla funkcji i zmiennych, PascalCase dla klas, UPPER_CASE dla stałych), zasady rozmieszczania spacji wokół operatorów i kolejność importów.

Jego znaczenie wynika z filozofii Pythona: kod czyta się znacznie częściej, niż się go pisze. Spójny styl w całym projekcie redukuje obciążenie poznawcze — czytelnik nie traci uwagi na różnice formatowania i może skupić się na logice. W zespole eliminuje to jałowe dyskusje o styl podczas code review. PEP 8 zawiera jednak ważne zastrzeżenie: „spójność jest ważna, ale wiedz, kiedy złamać regułę" — czytelność konkretnego fragmentu jest nadrzędna wobec dosłownego trzymania się litery przewodnika.

W praktyce zgodności z PEP 8 pilnują automatyczne narzędzia: Black (formater), flake8 oraz nowoczesny, bardzo szybki Ruff (linter), które integruje się z edytorem i CI/CD, dzięki czemu styl utrzymuje się bez ręcznej pracy.

Materiały

Na czym polega dynamiczne typowanie i jak różni się od typowania statycznego?

Odpowiedź w 30 sekund: W typowaniu dynamicznym typ jest związany z wartością (obiektem), a nie ze zmienną, i jest sprawdzany dopiero w trakcie wykonania — ta sama zmienna może raz wskazywać na liczbę, a chwilę później na tekst. W typowaniu statycznym typy zmiennych są znane i weryfikowane przez kompilator przed uruchomieniem programu. Python jest dynamicznie typowany.

Odpowiedź w 2 minuty: W Pythonie zmienna to tak naprawdę nazwa (etykieta) wskazująca na obiekt w pamięci, a typ należy do obiektu, nie do nazwy. Dlatego można bez problemu napisać x = 10, a potem x = "tekst" — nazwa x po prostu zaczyna wskazywać na inny obiekt. Interpreter sprawdza poprawność operacji (np. czy można dodać dwa obiekty) dopiero w momencie ich wykonania. To właśnie typowanie dynamiczne: błędy typów (TypeError) ujawniają się w czasie działania programu.

W językach statycznie typowanych (Java, C++, Go) typ zmiennej jest ustalony i sprawdzany przez kompilator zanim program się uruchomi. Wymaga to często deklarowania typów, ale pozwala wychwycić całą klasę błędów wcześnie i daje lepsze podpowiedzi w narzędziach (IDE).

Trzeba odróżnić dwie niezależne osie. Pierwsza to dynamiczne kontra statyczne (kiedy sprawdzany jest typ). Druga to silne kontra słabe typowanie (jak chętnie język wykonuje niejawne konwersje). Python jest dynamicznie, ale silnie typowany — nie pozwoli np. dodać liczby do napisu (1 + "2" rzuca TypeError), w przeciwieństwie do JavaScriptu, który dokonałby niejawnej konwersji. To częste źródło nieporozumień na rozmowach.

Zaletą dynamicznego typowania jest szybkość prototypowania, zwięzłość kodu i elastyczność (np. duck typing — „jeśli chodzi jak kaczka i kwacze jak kaczka, traktuj to jak kaczkę"). Wadą jest przesunięcie wykrywania błędów na czas wykonania. Dlatego nowoczesny Python łączy oba światy: pozostaje dynamiczny w runtime, ale wprowadza adnotacje typów (PEP 484), które narzędzia jak mypy czy pyright weryfikują statycznie — bez wpływu na samo działanie programu.

Przykład kodu:

# Typowanie dynamiczne: ta sama nazwa wskazuje na różne typy
x = 10          # x wskazuje na obiekt int
print(type(x))  # <class 'int'>

x = "tekst"     # teraz ta sama nazwa wskazuje na str
print(type(x))  # <class 'str'>

# Silne typowanie: brak niejawnej konwersji
try:
    wynik = 1 + "2"   # Python NIE skonwertuje napisu na liczbę
except TypeError as e:
    print("Błąd:", e)  # unsupported operand type(s) for +: 'int' and 'str'

# Adnotacje typów są opcjonalne i nie zmieniają działania w runtime
def powitanie(imie: str) -> str:
    return f"Cześć, {imie}!"

Materiały

Jakie są najważniejsze różnice między Python 2 a Python 3 i dlaczego dziś używa się wyłącznie Python 3?

Odpowiedź w 30 sekund: Python 3 wprowadził niekompatybilne wstecz, lecz porządkujące zmiany: print stał się funkcją, dzielenie / zwraca liczbę zmiennoprzecinkową, a obsługa tekstu opiera się na Unicode (str) i bajtach (bytes). Używa się dziś wyłącznie Pythona 3, ponieważ Python 2 osiągnął koniec wsparcia 1 stycznia 2020 i nie otrzymuje już poprawek, w tym bezpieczeństwa.

Odpowiedź w 2 minuty: Python 3 (wydany w 2008 r.) był świadomą decyzją o zerwaniu kompatybilności wstecz, aby naprawić nagromadzone niedoskonałości języka. Najbardziej widoczną zmianą jest print jako funkcja — print("x") zamiast instrukcji print "x". Dzięki temu zyskał spójność z resztą języka i konfigurowalne argumenty (sep, end, file).

Najistotniejszą i najbardziej kłopotliwą przy migracji zmianą jest obsługa tekstu. W Pythonie 2 str był ciągiem bajtów, a unicode osobnym typem. W Pythonie 3 str to zawsze tekst Unicode, a dane binarne reprezentuje osobny typ bytes. To rozdzielenie eliminuje całą klasę błędów kodowania znaków, ale wymaga jawnego kodowania/dekodowania na styku z plikami i siecią. Kolejna częsta pułapka: dzielenie — 3 / 2 daje w Pythonie 3 wynik 1.5 (true division), podczas gdy w Pythonie 2 dawało 1. Dzielenie całkowite uzyskujemy operatorem //.

Inne zmiany: range() zwraca leniwy obiekt zamiast budować całą listę w pamięci (podobnie dict.keys(), map(), zip() zwracają widoki/iteratory), poprawiono składnię obsługi wyjątków (except E as e), a od Pythona 3.7 słowniki gwarantują zachowanie kolejności wstawiania. Python 3 stał się też domem dla nowoczesnych funkcji, których nigdy nie przeniesiono do „dwójki": f-stringi, adnotacje typów, async/await, operator morsa czy strukturalne dopasowanie wzorców.

Powód, dla którego używa się wyłącznie Pythona 3, jest praktyczny: 1 stycznia 2020 r. Python 2 osiągnął oficjalny koniec życia (EOL). Nie powstają już żadne poprawki — w tym łatki bezpieczeństwa. Cały współczesny ekosystem (Django, NumPy, pandas, requests) dawno porzucił wsparcie dla „dwójki". Utrzymywanie kodu w Pythonie 2 to dziś dług technologiczny i zagrożenie bezpieczeństwa.

Cecha Python 2 Python 3
print Instrukcja: print "x" Funkcja: print("x")
Tekst domyślnie str = bajty str = Unicode, bytes osobno
Dzielenie / 3 / 2 == 1 3 / 2 == 1.5
range() Buduje listę Leniwy obiekt
Kolejność dict Niegwarantowana Gwarantowana (od 3.7)
Wsparcie EOL 1.01.2020 Aktywnie rozwijany

Materiały

Czym jest „Zen of Pythona" (PEP 20) i jak wpływa na sposób pisania kodu?

Odpowiedź w 30 sekund: „Zen of Pythona" (PEP 20) to zbiór 19 aforyzmów ujmujących filozofię projektową języka, takich jak „Czytelność się liczy" czy „Proste jest lepsze niż złożone". Można go wyświetlić poleceniem import this. Stanowi nieformalny kompas, który zachęca do pisania kodu prostego, jawnego i czytelnego.

Odpowiedź w 2 minuty: „Zen of Pythona", autorstwa Tima Petersa, to żartobliwy, ale traktowany poważnie zbiór wytycznych opisujących ducha języka. Zawiera 19 maksym (mimo że tekst mówi o 20). Najczęściej cytowane to: „Beautiful is better than ugly", „Explicit is better than implicit" (jawne lepsze niż domniemane), „Simple is better than complex", „Readability counts" (czytelność się liczy) oraz „There should be one — and preferably only one — obvious way to do it" (powinien istnieć jeden oczywisty sposób zrobienia czegoś). Ten ostatni punkt to świadomy kontrast wobec filozofii Perla („jest wiele sposobów") i tłumaczy, dlaczego Python preferuje konwencje i idiomy.

Wpływ na codzienne pisanie kodu jest realny, nawet jeśli „Zen" nie jest sztywnym standardem jak PEP 8. Zasada „explicit is better than implicit" skłania do unikania ukrytej magii — np. jawnego importowania nazw zamiast from module import *. „Flat is better than nested" zachęca do spłaszczania głębokich zagnieżdżeń (np. przez wczesne return lub strażników zamiast piramidy if). „Errors should never pass silently" przestrzega przed pustym except:, który połyka błędy. „If the implementation is hard to explain, it's a bad idea" to praktyczny test prostoty projektu.

W kontekście rozmowy rekrutacyjnej warto pokazać, że „Zen" to nie ozdobnik, lecz kompas decyzyjny: gdy istnieje kilka możliwych rozwiązań, wybiera się to bardziej czytelne, jawne i proste — bo to one przekładają się na łatwiejszą długoterminową konserwację kodu, co jest sednem „pythonowości" (pythonic code). Ciekawostką jest, że tekst można wywołać w dowolnym interpreterze poleceniem import this.

Przykład kodu:

# Wyświetlenie pełnego tekstu Zen of Python
import this

# "Explicit is better than implicit" w praktyce:
# źle — ukryty import wszystkiego, niejasne pochodzenie nazw
from math import *
print(sqrt(16))

# dobrze — jawnie wiadomo, skąd pochodzi nazwa
import math
print(math.sqrt(16))

# "Flat is better than nested" — strażnik zamiast głębokiego zagnieżdżenia
def przetworz(dane):
    if not dane:
        return None          # wczesny powrót spłaszcza strukturę
    return [x * 2 for x in dane]

Materiały

Typy danych i struktury danych

Jakie są wbudowane typy danych w Pythonie?

Odpowiedź w 30 sekund: Python ma kilka kategorii typów wbudowanych: liczbowe (int, float, complex, bool), tekstowe (str), sekwencje (list, tuple, range), mapowanie (dict), zbiory (set, frozenset), binarne (bytes, bytearray, memoryview) oraz typ specjalny NoneType. Wszystkie są obiektami, a każdy obiekt ma swój typ, który sprawdzisz funkcją type().

Odpowiedź w 2 minuty: W Pythonie wszystko jest obiektem, a każdy obiekt ma typ. Wbudowane typy dzielimy na kilka grup. Typy liczbowe to int (liczby całkowite o dowolnej precyzji), float (liczby zmiennoprzecinkowe podwójnej precyzji, IEEE 754), complex (liczby zespolone) oraz bool (wartości logiczne True/False, będące podtypem int).

Sekwencje to uporządkowane kolekcje: str (niemutowalny ciąg znaków Unicode), list (mutowalna lista), tuple (niemutowalna krotka) oraz range (niemutowalny ciąg liczb). Typy mapowania reprezentuje dict (słownik klucz-wartość). Typy zbiorów to set (mutowalny zbiór) i frozenset (niemutowalny zbiór).

Typy binarne obejmują bytes (niemutowalna sekwencja bajtów), bytearray (mutowalna sekwencja bajtów) oraz memoryview (widok na bufor pamięci bez kopiowania danych). Wreszcie istnieje typ specjalny NoneType, którego jedyną instancją jest None — używany do reprezentowania braku wartości.

Warto pamiętać, że Python jest językiem o typowaniu dynamicznym (typ jest związany z obiektem, nie ze zmienną) i silnym (nie wykonuje niejawnych, ryzykownych konwersji, np. "1" + 1 zgłasza błąd).

Przykład kodu:

# Typy liczbowe
print(type(42))          # <class 'int'>
print(type(3.14))        # <class 'float'>
print(type(2 + 3j))      # <class 'complex'>
print(type(True))        # <class 'bool'>

# Sekwencje
print(type("tekst"))     # <class 'str'>
print(type([1, 2, 3]))   # <class 'list'>
print(type((1, 2, 3)))   # <class 'tuple'>
print(type(range(5)))    # <class 'range'>

# Mapowanie, zbiory, typy binarne, None
print(type({"a": 1}))    # <class 'dict'>
print(type({1, 2, 3}))   # <class 'set'>
print(type(frozenset())) # <class 'frozenset'>
print(type(b"bajty"))    # <class 'bytes'>
print(type(None))        # <class 'NoneType'>

# bool jest podtypem int
print(isinstance(True, int))  # True
print(True + True)            # 2

Materiały

Jaka jest różnica między typami mutowalnymi a niemutowalnymi? Podaj przykłady.

Odpowiedź w 30 sekund: Obiekt mutowalny można zmienić w miejscu po utworzeniu (zmienia się jego zawartość, ale tożsamość id() pozostaje ta sama), a niemutowalny nie — każda „zmiana" tworzy nowy obiekt. Mutowalne to m.in. list, dict, set, bytearray; niemutowalne to int, float, str, tuple, frozenset, bytes.

Odpowiedź w 2 minuty: Mutowalność określa, czy stan obiektu może się zmienić po jego utworzeniu. Obiekt mutowalny (np. list) pozwala modyfikować swoją zawartość w miejscu — możesz dodać element, a id() obiektu się nie zmieni. Obiekt niemutowalny (np. str, int, tuple) nie pozwala na zmianę zawartości; operacje, które wyglądają jak modyfikacja (np. x += 1 dla int albo s.upper() dla str), w rzeczywistości tworzą zupełnie nowy obiekt i przypinają do niego zmienną.

To rozróżnienie ma istotne konsekwencje praktyczne. Po pierwsze, tylko obiekty haszowalne (a niemutowalne typy wbudowane są haszowalne) mogą być kluczami słownika i elementami zbioru. Po drugie, niemutowalność wpływa na semantykę przekazywania argumentów: Python przekazuje referencje do obiektów, więc funkcja może zmodyfikować przekazaną listę „u dzwoniącego", ale nie może zmienić wartości przekazanego int.

Klasyczna pułapka to mutowalny argument domyślny — wartość domyślna jest tworzona raz, przy definicji funkcji, i współdzielona między wywołaniami. Drugą pułapką jest płytkie kopiowanie: skopiowanie listy zawierającej zagnieżdżone listy kopiuje tylko referencje do wewnętrznych obiektów. Warto też wiedzieć, że krotka jest niemutowalna, ale jeśli zawiera element mutowalny (np. listę), zawartość tej listy nadal można zmienić.

Przykład kodu:

# Lista jest mutowalna — id się nie zmienia po modyfikacji
lista = [1, 2, 3]
print(id(lista))
lista.append(4)
print(id(lista))   # ten sam id — zmiana w miejscu

# str jest niemutowalny — powstaje nowy obiekt
s = "abc"
print(id(s))
s = s + "d"
print(id(s))       # inny id — nowy obiekt

# Pułapka: mutowalny argument domyślny
def dodaj(element, kosz=[]):   # ZŁY wzorzec
    kosz.append(element)
    return kosz

print(dodaj(1))  # [1]
print(dodaj(2))  # [1, 2] — niespodzianka! lista współdzielona

# Poprawny wzorzec ze strażnikiem None
def dodaj_ok(element, kosz=None):
    if kosz is None:
        kosz = []
    kosz.append(element)
    return kosz

# Krotka niemutowalna, ale jej mutowalny element już nie
krotka = (1, [2, 3])
krotka[1].append(4)
print(krotka)      # (1, [2, 3, 4])

Diagram:

flowchart TD
    A["Typy wbudowane"] --> B["Mutowalne"]
    A --> C["Niemutowalne"]
    B --> B1["list"]
    B --> B2["dict"]
    B --> B3["set"]
    B --> B4["bytearray"]
    C --> C1["int / float / bool"]
    C --> C2["str"]
    C --> C3["tuple"]
    C --> C4["frozenset"]
    C --> C5["bytes"]
    C -. "haszowalne -> klucze dict / elementy set" .-> D["Haszowalność"]

Materiały

Czym różni się lista (list) od krotki (tuple) i kiedy użyłbyś której?

Odpowiedź w 30 sekund: Lista jest mutowalna (można ją modyfikować w miejscu), krotka niemutowalna (po utworzeniu nie zmienisz jej zawartości). Listy używasz dla jednorodnych, zmieniających się kolekcji, a krotki dla stałych, często heterogenicznych rekordów — krotka może też być kluczem słownika, bo jest haszowalna.

Odpowiedź w 2 minuty: Najważniejsza różnica to mutowalność. Lista pozwala dodawać, usuwać i zmieniać elementy w miejscu (append, pop, przypisanie do indeksu), natomiast krotka jest „zamrożona" po utworzeniu. Z tego wynika kolejna różnica: krotka jest haszowalna (jeśli wszystkie jej elementy są haszowalne), więc może być kluczem słownika lub elementem zbioru; lista nie może.

Konwencjonalnie listy stosuje się do jednorodnych sekwencji o zmiennej długości, po których iterujemy (np. lista użytkowników, lista wyników). Krotki zwykle reprezentują heterogeniczne rekordy o stałej strukturze, gdzie pozycja ma znaczenie (np. współrzędne (x, y), krotka (imię, wiek)). Krotki są też zwracane przez funkcje przy wielu wartościach i używane w rozpakowywaniu.

Krotki mają drobne przewagi wydajnościowe: zajmują mniej pamięci i ich tworzenie z literałów jest minimalnie szybsze, ponieważ CPython może je zbudować jednym bajtkodem (BUILD_TUPLE) i cache'ować małe stałe krotki. Niemutowalność daje też bezpieczeństwo — przekazując krotkę masz pewność, że odbiorca jej nie zmodyfikuje.

W praktyce, jeśli potrzebujesz nazwanych pól rekordu, zamiast zwykłej krotki rozważ collections.namedtuple albo typing.NamedTuple — zachowują niemutowalność, a dają czytelny dostęp przez nazwy.

Przykład kodu:

# Lista — mutowalna kolekcja
lista = [1, 2, 3]
lista.append(4)
lista[0] = 99
print(lista)            # [99, 2, 3, 4]

# Krotka — niemutowalna
krotka = (1, 2, 3)
# krotka[0] = 99        # TypeError: 'tuple' object does not support item assignment

# Krotka jako klucz słownika (lista by się nie nadała)
ceny = {("Warszawa", "Kraków"): 299}
print(ceny[("Warszawa", "Kraków")])  # 299

# Rozpakowywanie i zwracanie wielu wartości
def minmax(dane):
    return min(dane), max(dane)   # zwraca krotkę

najm, najw = minmax([3, 1, 4, 1, 5])
print(najm, najw)       # 1 5

# Porównanie pamięci
import sys
print(sys.getsizeof([1, 2, 3]))   # więcej bajtów
print(sys.getsizeof((1, 2, 3)))   # mniej bajtów

Diagram: (tabela porównawcza poniżej)

Cecha list tuple
Mutowalność mutowalna niemutowalna
Składnia [1, 2, 3] (1, 2, 3)
Haszowalna (klucz dict / element set) nie tak (gdy elementy haszowalne)
Zużycie pamięci większe mniejsze
Typowe zastosowanie jednorodna, zmienna kolekcja stały, heterogeniczny rekord
Metody modyfikujące append, pop, sort... brak (tylko count, index)

Materiały

Czym jest słownik (dict) i jak działa wewnętrznie (haszowanie, kolejność kluczy)?

Odpowiedź w 30 sekund: dict to mapowanie kluczy na wartości oparte na tablicy haszującej, dające średnio stały czas O(1) dla wyszukiwania, wstawiania i usuwania. Klucze muszą być haszowalne (niemutowalne). Od Pythona 3.7 słownik gwarantuje zachowanie kolejności wstawiania kluczy.

Odpowiedź w 2 minuty: Słownik to kolekcja par klucz-wartość zaimplementowana jako tablica haszująca (hash table). Gdy dodajesz parę, Python oblicza hash(klucz), na tej podstawie wyznacza pozycję w wewnętrznej tablicy i tam zapisuje wpis. Przy odczycie ponownie liczy hash i trafia bezpośrednio do właściwego miejsca, dzięki czemu operacje mają średnią złożoność O(1) (w pesymistycznym przypadku, przy wielu kolizjach, O(n)).

Kluczem może być tylko obiekt haszowalny — taki, który implementuje __hash__ i __eq__, a jego hash nie zmienia się w czasie życia. Z definicji niemutowalne typy wbudowane (int, str, tuple z haszowalnymi elementami) są haszowalne, a mutowalne (list, dict, set) nie. Dwa klucze równe wg == muszą mieć ten sam hash; kolizje (różne klucze z tym samym hashem lub tą samą pozycją) Python rozwiązuje przez sondowanie (open addressing).

Od Pythona 3.7 zachowanie kolejności wstawiania jest gwarancją językową (w CPythonie 3.6 było szczegółem implementacji). Wynika to z tzw. „compact dict" — wpisy trzymane są w zwartej, uporządkowanej tablicy, a osobna rzadka tablica haszująca przechowuje jedynie indeksy do nich. To rozwiązanie zmniejsza zużycie pamięci i naturalnie utrwala kolejność. Uwaga: kolejność to kolejność wstawiania, a nie posortowanie — do sortowania użyj sorted().

Praktyczne wnioski: nie używaj jako kluczy obiektów, których stan (i hash) może się zmienić; pamiętaj o metodach takich jak .get() (bezpieczny odczyt z wartością domyślną), .setdefault(), słownikach defaultdict i Counter z modułu collections, a od 3.9 o operatorze łączenia |.

Przykład kodu:

# Podstawy
osoba = {"imię": "Anna", "wiek": 30}
print(osoba["imię"])              # Anna
print(osoba.get("email", "brak")) # brak — bezpieczny odczyt

# Kolejność wstawiania jest zachowana (Python 3.7+)
d = {}
d["c"] = 1
d["a"] = 2
d["b"] = 3
print(list(d))                    # ['c', 'a', 'b']

# Klucz musi być haszowalny
poprawny = {(1, 2): "krotka ok"}
# bledny = {[1, 2]: "x"}          # TypeError: unhashable type: 'list'

# Równość kluczy a hash: 1 == 1.0 == True -> ten sam wpis
mieszany = {1: "a"}
mieszany[1.0] = "b"
mieszany[True] = "c"
print(mieszany)                   # {1: 'c'} — jeden klucz!

# Łączenie słowników (Python 3.9+)
a = {"x": 1}
b = {"y": 2}
print(a | b)                      # {'x': 1, 'y': 2}

Diagram:

flowchart LR
    K["klucz 'imię'"] --> H["hash(klucz)"]
    H --> I["indeks w tablicy<br/>haszujacej"]
    I --> S["rzadka tablica<br/>indeksow"]
    S --> E["zwarta tablica wpisow<br/>(kolejnosc wstawiania)"]
    E --> V["wartosc 'Anna'"]
    H -. "kolizja" .-> P["sondowanie<br/>kolejnej pozycji"]

Materiały

Czym różni się set od frozenset i do czego służą zbiory?

Odpowiedź w 30 sekund: set to mutowalny zbiór unikalnych, haszowalnych elementów, a frozenset to jego niemutowalna i haszowalna wersja. Zbiory służą do usuwania duplikatów, szybkiego testu przynależności O(1) oraz operacji teoriomnogościowych (suma, część wspólna, różnica). Bo frozenset jest haszowalny, można go użyć jako klucza słownika lub elementu innego zbioru.

Odpowiedź w 2 minuty: Zbiór (set) to nieuporządkowana kolekcja unikalnych elementów oparta — podobnie jak słownik — na tablicy haszującej. Daje to średnio stały czas O(1) dla testu przynależności (x in s), dodawania i usuwania, co czyni zbiory dużo szybszymi od list przy sprawdzaniu „czy element istnieje". Elementy muszą być haszowalne, więc nie wrzucisz do zbioru listy ani innego słownika.

Różnica między set a frozenset jest analogiczna do różnicy między list a tuple: set jest mutowalny (add, discard, update), natomiast frozenset jest niemutowalny i przez to haszowalny. Dlatego frozenset może być elementem innego zbioru lub kluczem słownika, a zwykły set nie. Jeśli potrzebujesz „zbioru zbiorów", elementy muszą być frozenset.

Zbiory są idealne do: usuwania duplikatów (list(set(dane))), szybkiego członkostwa oraz operacji teoriomnogościowych. Wspierają sumę (| / union), część wspólną (& / intersection), różnicę (- / difference) i różnicę symetryczną (^ / symmetric_difference), a także testy podzbioru i nadzbioru (<=, >=). Operatory wymagają, by oba operandy były zbiorami; metody akceptują dowolny iterowalny.

Ważne ograniczenia: zbiory są nieuporządkowane (kolejność iteracji nie jest gwarantowana, choć w danym uruchomieniu bywa stabilna), więc nie indeksujesz ich s[0]. Literał {} tworzy pusty słownik, nie zbiór — pusty zbiór tworzysz przez set().

Przykład kodu:

# Usuwanie duplikatów i szybki test przynależności
liczby = [1, 2, 2, 3, 3, 3]
unikalne = set(liczby)
print(unikalne)              # {1, 2, 3}
print(3 in unikalne)         # True — średnio O(1)

# Operacje teoriomnogościowe
a = {1, 2, 3}
b = {2, 3, 4}
print(a | b)                 # {1, 2, 3, 4}  suma
print(a & b)                 # {2, 3}        część wspólna
print(a - b)                 # {1}           różnica
print(a ^ b)                 # {1, 4}        różnica symetryczna

# set jest mutowalny
a.add(5)
print(a)                     # {1, 2, 3, 5}

# frozenset jest niemutowalny i haszowalny
fs = frozenset({1, 2, 3})
# fs.add(4)                  # AttributeError: 'frozenset' has no attribute 'add'
zbior_zbiorow = {frozenset({1, 2}), frozenset({3, 4})}
print(zbior_zbiorow)         # działa, bo frozenset jest haszowalny

# Pusty zbiór: set(), nie {}
print(type({}))              # <class 'dict'>
print(type(set()))          # <class 'set'>

Diagram: (tabela porównawcza poniżej)

Cecha set frozenset
Mutowalność mutowalny niemutowalny
Haszowalny (klucz dict / element set) nie tak
Metody modyfikujące add, discard, update... brak
Operacje teoriomnogościowe tak tak
Tworzenie {1, 2} lub set(...) frozenset(...)

Materiały

Jak działa slicing (wycinanie) sekwencji i czym jest składnia [start:stop:step]?

Odpowiedź w 30 sekund: Slicing tworzy nowy fragment sekwencji za pomocą seq[start:stop:step], gdzie start jest włączany, stop wyłączany, a step to krok (może być ujemny, by iść od końca). Wszystkie trzy parametry są opcjonalne i mają sensowne wartości domyślne, więc seq[:] zwraca kopię całości, a seq[::-1] odwraca sekwencję.

Odpowiedź w 2 minuty: Slicing pozwala wyciąć podsekwencję z dowolnej sekwencji (list, str, tuple, range, bytes). Składnia to seq[start:stop:step]. start to indeks pierwszego elementu (włącznie, domyślnie 0 lub koniec przy ujemnym kroku), stop to indeks, przy którym wycinanie się zatrzymuje (wyłącznie, domyślnie długość sekwencji), a step to krok między kolejnymi elementami (domyślnie 1).

Indeksy mogą być ujemne — liczą się od końca, gdzie -1 to ostatni element. Ujemny step odwraca kierunek iteracji, dlatego seq[::-1] to idiomatyczne odwrócenie sekwencji. Slicing jest bezpieczny względem zakresu: indeksy wykraczające poza sekwencję są obcinane bez zgłaszania IndexError, więc [1, 2, 3][10:20] zwraca po prostu pustą listę.

Dla list i innych sekwencji slicing zwraca nowy obiekt (płytka kopia wybranego fragmentu), nie referencję — to wygodny sposób na kopiowanie listy przez lista[:]. Co istotne, dla list slice można też przypisać (lista[1:3] = [...]) lub usunąć (del lista[1:3]), co modyfikuje listę w miejscu i może nawet zmienić jej długość.

Pod spodem seq[start:stop:step] jest cukrem składniowym dla obiektu slice(start, stop, step). We własnych klasach możesz obsłużyć wycinanie, implementując __getitem__, gdzie argument będzie obiektem typu slice — pomocna jest wtedy metoda slice.indices(len), normalizująca granice do konkretnej długości.

Przykład kodu:

dane = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

print(dane[2:5])      # [2, 3, 4]  — start włącznie, stop wyłącznie
print(dane[:3])       # [0, 1, 2]  — od początku
print(dane[7:])       # [7, 8, 9]  — do końca
print(dane[:])        # cała kopia listy

print(dane[::2])      # [0, 2, 4, 6, 8]  — co drugi
print(dane[::-1])     # [9, 8, ..., 0]   — odwrócenie
print(dane[-3:])      # [7, 8, 9]        — trzy ostatnie

# Bezpieczne względem zakresu — brak IndexError
print(dane[5:100])    # [5, 6, 7, 8, 9]

# Przypisanie i usuwanie przez slice (modyfikacja w miejscu)
kopia = dane[:]
kopia[1:4] = [99]
print(kopia)          # [0, 99, 4, 5, 6, 7, 8, 9]
del kopia[:2]
print(kopia)          # [4, 5, 6, 7, 8, 9]

# Slicing działa też na łańcuchach znaków
print("Python"[::-1]) # nohtyP

# Pod spodem to obiekt slice
print(dane[slice(2, 5)])  # [2, 3, 4]

Materiały

Czym są list, dict i set comprehensions oraz jakie dają korzyści?

Odpowiedź w 30 sekund: Comprehensions to zwięzła składnia tworzenia kolekcji z iterowalnego źródła w jednym wyrażeniu, np. [x*x for x in zakres]. Istnieją wersje dla listy ([...]), słownika ({k: v ...}), zbioru ({...}) oraz wyrażenie generatorowe ((...)). Są czytelniejsze i zwykle szybsze od ręcznej pętli z append.

Odpowiedź w 2 minuty: Comprehension to deklaratywny sposób budowania kolekcji: opisujesz, co ma się znaleźć w wyniku, zamiast krok po kroku budować ją pętlą. List comprehension [wyrażenie for element in iterowalne if warunek] tworzy listę, set comprehension {...} zbiór, a dict comprehension {klucz: wartość for ...} słownik. Otoczenie nawiasami okrągłymi (...) daje wyrażenie generatorowe, które nie buduje całej kolekcji w pamięci, lecz produkuje elementy leniwie — idealne dla dużych zbiorów danych.

Korzyści to przede wszystkim czytelność i zwięzłość: jedna linia zamiast czterech (inicjalizacja, pętla, warunek, append). Comprehensions są też zwykle szybsze od równoważnej pętli, bo dodawanie elementów odbywa się na poziomie zoptymalizowanego bajtkodu, bez powtarzalnego wyszukiwania metody append. Pozwalają na filtrowanie (if na końcu), transformację oraz wyrażenia warunkowe (x if c else y przed for) i zagnieżdżanie wielu klauzul for.

Trzeba jednak zachować umiar. Zbyt złożone, wielopoziomowe comprehensions stają się nieczytelne — w takich przypadkach lepsza jest zwykła pętla. Od Pythona 3 zmienna pętli w comprehension ma własny zakres i nie „wycieka" do otaczającego kodu (inaczej niż w Pythonie 2).

Praktyczna wskazówka: jeśli i tak będziesz iterować po wyniku tylko raz (np. w sum() czy any()), użyj wyrażenia generatorowego zamiast list comprehension — oszczędzisz pamięć, bo nie zmaterializujesz pośredniej listy.

Przykład kodu:

# List comprehension — kwadraty liczb parzystych
kwadraty = [x * x for x in range(10) if x % 2 == 0]
print(kwadraty)            # [0, 4, 16, 36, 64]

# Z wyrażeniem warunkowym (przed for)
etykiety = ["parzysta" if x % 2 == 0 else "nieparzysta" for x in range(4)]
print(etykiety)            # ['parzysta', 'nieparzysta', 'parzysta', 'nieparzysta']

# Dict comprehension — odwrócenie mapowania
ceny = {"chleb": 5, "mleko": 3}
odwrocone = {wartość: klucz for klucz, wartość in ceny.items()}
print(odwrocone)           # {5: 'chleb', 3: 'mleko'}

# Set comprehension — unikalne długości słów
słowa = ["kot", "pies", "ryba", "lew"]
długości = {len(s) for s in słowa}
print(długości)            # {3, 4}

# Wyrażenie generatorowe — leniwe, oszczędne pamięciowo
suma_kwadratów = sum(x * x for x in range(1_000_000))
print(suma_kwadratów)

# Zagnieżdżenie: spłaszczenie macierzy
macierz = [[1, 2], [3, 4], [5, 6]]
płaska = [el for wiersz in macierz for el in wiersz]
print(płaska)              # [1, 2, 3, 4, 5, 6]

Materiały

Jak Python obsługuje liczby całkowite o dowolnej precyzji (arbitrary precision integers)?

Odpowiedź w 30 sekund: Typ int w Pythonie 3 nie ma stałego rozmiaru — automatycznie rośnie, by pomieścić dowolnie dużą liczbę, ograniczoną jedynie dostępną pamięcią. Nie ma więc przepełnienia (overflow) jak w językach z 32/64-bitowymi liczbami; obliczenia takie jak 2 ** 1000 po prostu działają.

Odpowiedź w 2 minuty: W przeciwieństwie do C czy Javy, gdzie int ma ustalony rozmiar (np. 64 bity) i może się przepełnić, Python 3 implementuje liczby całkowite jako liczby o dowolnej precyzji (bignum). CPython przechowuje int jako tablicę „cyfr" o dużej podstawie (zwykle 30-bitowych na platformach 64-bitowych) wraz ze znakiem. Gdy wynik operacji przekracza pojemność bieżącej reprezentacji, Python automatycznie alokuje więcej pamięci — programista nie musi nic robić, a jedynym praktycznym limitem jest ilość dostępnej pamięci.

Dzięki temu nie istnieje pojęcie przepełnienia liczb całkowitych. Operacje takie jak silnia dużej liczby, potęgowanie kryptograficzne czy liczby Fibonacciego o setkach cyfr działają poprawnie bez żadnych specjalnych bibliotek. Ceną jest to, że arytmetyka na bardzo dużych liczbach jest wolniejsza niż na natywnych liczbach maszynowych i zużywa więcej pamięci.

Dla małych liczb CPython stosuje optymalizację cache'owania: obiekty int z zakresu od -5 do 256 są tworzone raz przy starcie interpretera i współdzielone, dlatego a is b dla a = 256; b = 256 zwraca True, ale dla 257 już niekoniecznie. Tej cechy nie należy traktować jako gwarancji języka — do porównywania wartości zawsze używaj ==, a is rezerwuj dla None.

Warto wiedzieć o dwóch niuansach. Po pierwsze, dzielenie / zawsze zwraca float, więc na bardzo dużych liczbach może utracić precyzję — dla dokładnego wyniku całkowitego użyj //. Po drugie, od Pythona 3.11 istnieje limit konwersji dużych int na/z str (domyślnie 4300 cyfr) jako zabezpieczenie przed atakami DoS; można go zmienić przez sys.set_int_max_str_digits().

Przykład kodu:

# Brak przepełnienia — dowolnie duże liczby
duza = 2 ** 1000
print(duza)               # liczba o ponad 300 cyfrach
print(len(str(duza)))     # 302

import math
print(math.factorial(50)) # ogromna liczba, działa bez problemu

# Liczba liczona iteracyjnie nadal jest dokładna
def fib(n):
    a, b = 0, 1
    for _ in range(n):
        a, b = b, a + b
    return a

print(fib(200))           # dokładny wynik, setki cyfr

# Cache małych intów (szczegół implementacji CPythona, NIE gwarancja!)
a = 256
b = 256
print(a is b)             # True (zazwyczaj)
x = 257
y = 257
print(x is y)             # może być False
print(x == y)             # True — do wartości zawsze ==

# Dzielenie: / daje float (możliwa utrata precyzji), // daje int
print(10 ** 20 // 3)      # dokładny int
print(10 ** 20 / 3)       # float — przybliżenie

Materiały

Jaka jest różnica między operatorami is a ==?

Odpowiedź w 30 sekund: == porównuje wartości (czy obiekty są równe, wywołując __eq__), a is porównuje tożsamość (czy to dokładnie ten sam obiekt w pamięci, czyli ta sama wartość id()). Operatora is używaj przede wszystkim do porównań z None, True, False; do porównywania wartości zawsze używaj ==.

Odpowiedź w 2 minuty: == to operator równości — sprawdza, czy dwa obiekty mają równą wartość, delegując do metody __eq__. is to operator tożsamości — sprawdza, czy dwie nazwy wskazują na ten sam obiekt w pamięci, co jest równoważne porównaniu id(a) == id(b). Dwa różne obiekty mogą być równe (== zwraca True), ale nie być tym samym obiektem (is zwraca False).

Najczęstsza pułapka wynika z cache'owania małych obiektów przez CPython. Małe liczby całkowite (-5 do 256) i niektóre krótkie/„internowane" stringi są współdzielone, więc a is b bywa True dla małych wartości, a False dla większych — mimo że == zawsze zwraca True. To szczegół implementacji, nie cecha języka, dlatego porównywanie wartości operatorem is jest błędem, który potrafi działać „przypadkiem" i zawieść w innym miejscu. Nowsze wersje Pythona ostrzegają o tym przez SyntaxWarning: "is" with a literal.

Operatora is używaj świadomie tam, gdzie naprawdę chodzi o tożsamość: porównania z singletonami None, True, False (if x is None:), sprawdzanie czy zmienna to ten sam obiekt po przypisaniu, albo strażniki typu _SENTINEL = object(). Do wszystkiego innego — liczby, łańcuchy, listy, własne obiekty — używaj ==.

Warto pamiętać, że zachowanie == możesz dostosować, nadpisując __eq__ w klasie (a w parze z nim __hash__, jeśli obiekt ma być haszowalny). Natomiast is jest nieprzeciążalne — zawsze porównuje czystą tożsamość obiektu i nie da się tego zmienić.

Przykład kodu:

# Listy o tej samej zawartości: równe, ale to różne obiekty
a = [1, 2, 3]
b = [1, 2, 3]
print(a == b)   # True  — taka sama wartość
print(a is b)   # False — różne obiekty
print(a is a)   # True  — ten sam obiekt

# Pułapka cache'owania małych intów
x = 256
y = 256
print(x is y)   # True  (cache)  — NIE polegaj na tym
m = 1000
n = 1000
print(m is n)   # zwykle False
print(m == n)   # True — poprawny sposób

# Idiomatyczne użycie is dla None
def funkcja(arg=None):
    if arg is None:        # poprawnie
        arg = []
    return arg

# Nadpisanie __eq__ wpływa na ==, ale nie na is
class Punkt:
    def __init__(self, x, y):
        self.x, self.y = x, y
    def __eq__(self, other):
        return (self.x, self.y) == (other.x, other.y)

p1 = Punkt(1, 2)
p2 = Punkt(1, 2)
print(p1 == p2)  # True  — wg __eq__
print(p1 is p2)  # False — różne obiekty

Diagram: (tabela porównawcza poniżej)

Aspekt == is
Co porównuje wartość (równość) tożsamość (ten sam obiekt)
Mechanizm wywołuje __eq__ porównuje id()
Przeciążalny tak (__eq__) nie
Typowe użycie porównywanie wartości x is None, singletony, sentinel
Pułapka — cache małych int / internowane str

Materiały

Funkcje, zakresy i domknięcia

Jak definiuje się funkcję w Pythonie i co oznacza, że funkcje są „pierwszej klasy" (first-class)?

Odpowiedź w 30 sekund: Funkcję definiuje się słowem kluczowym def, po którym następuje nazwa, lista parametrów w nawiasach i wcięty blok ciała. To, że funkcje są „pierwszej klasy" oznacza, że Python traktuje je jak każdą inną wartość (obiekt): można je przypisać do zmiennej, przekazać jako argument, zwrócić z innej funkcji i przechowywać w strukturach danych.

Odpowiedź w 2 minuty: Definicja funkcji w Pythonie ma postać def nazwa(parametry): zakończoną dwukropkiem, a jej ciało to wcięty blok kodu. Funkcja może zwracać wartość przez return (gdy go brakuje, zwraca None). Sama instrukcja def jest wykonywana w czasie działania programu — tworzy obiekt funkcji i wiąże go z nazwą w bieżącym zakresie. Dlatego funkcja istnieje dopiero po wykonaniu jej definicji.

Określenie „funkcje pierwszej klasy" (ang. first-class functions) oznacza, że funkcja jest pełnoprawnym obiektem (instancją typu function). Wynika z tego kilka praktycznych konsekwencji: funkcję można przypisać do zmiennej (f = moja_funkcja), umieścić na liście lub w słowniku, przekazać do innej funkcji jako argument oraz zwrócić jako wynik. Funkcje przyjmujące lub zwracające inne funkcje nazywamy funkcjami wyższego rzędu (np. map, filter, sorted z argumentem key).

Ponieważ funkcja jest obiektem, posiada też atrybuty: __name__ (nazwa), __doc__ (docstring) czy __defaults__ (wartości domyślne). Warto rozróżnić odwołanie do funkcji (f) od jej wywołania (f()) — to częsta pomyłka początkujących, którzy zapominają o nawiasach albo dodają je tam, gdzie chcą przekazać samą funkcję.

Przykład kodu:

def powitaj(imie: str) -> str:
    """Zwraca powitanie dla podanego imienia."""
    return f"Cześć, {imie}!"

# Przypisanie funkcji do zmiennej (bez nawiasów = sama funkcja)
f = powitaj
print(f("Ania"))            # Cześć, Ania!

# Przekazanie funkcji jako argumentu (funkcja wyższego rzędu)
def zastosuj(funkcja, wartosc):
    return funkcja(wartosc)

print(zastosuj(powitaj, "Bartek"))   # Cześć, Bartek!

# Przechowywanie funkcji w strukturach danych
operacje = {"powitanie": powitaj, "wielkie": str.upper}
print(operacje["wielkie"]("python"))  # PYTHON

# Funkcja jest obiektem z atrybutami
print(powitaj.__name__)     # powitaj
print(powitaj.__doc__)      # Zwraca powitanie dla podanego imienia.

Materiały

Czym są argumenty *args i **kwargs i kiedy się ich używa?

Odpowiedź w 30 sekund: *args zbiera dodatkowe argumenty pozycyjne do krotki (tuple), a **kwargs zbiera dodatkowe argumenty nazwane do słownika (dict). Używa się ich, gdy funkcja ma przyjmować zmienną liczbę argumentów lub gdy chcemy przekazać argumenty dalej do innej funkcji bez ich wymieniania.

Odpowiedź w 2 minuty: Nazwy args i kwargs to tylko konwencja — istotne są gwiazdki. Pojedyncza gwiazdka * przed parametrem powoduje, że Python pakuje wszystkie nadmiarowe argumenty pozycyjne do krotki. Podwójna gwiazdka ** pakuje nadmiarowe argumenty nazwane (klucz=wartość) do słownika. W definicji funkcji obowiązuje kolejność: najpierw zwykłe parametry, potem *args, potem parametry keyword-only, na końcu **kwargs.

Gwiazdki działają też w drugą stronę — przy wywołaniu funkcji służą do rozpakowania (unpacking) kolekcji. *lista rozbija sekwencję na osobne argumenty pozycyjne, a **slownik rozbija słownik na argumenty nazwane. To bardzo wygodne przy przekazywaniu argumentów „w przelocie", np. w dekoratorach i funkcjach opakowujących (wrapper(*args, **kwargs)), które nie powinny znać konkretnej sygnatury opakowywanej funkcji.

Typowe zastosowania to: funkcje o nieokreślonej liczbie argumentów (jak print czy max), przekazywanie argumentów do funkcji nadrzędnej przez super().__init__(*args, **kwargs), oraz budowa elastycznych API i dekoratorów. Pułapka: nadużywanie *args/**kwargs w publicznym API pogarsza czytelność i podpowiedzi IDE — gdy zestaw parametrów jest znany, lepiej wypisać je jawnie.

Przykład kodu:

def suma(*args):
    # args to krotka wszystkich argumentów pozycyjnych
    return sum(args)

print(suma(1, 2, 3, 4))      # 10

def opis(**kwargs):
    # kwargs to słownik argumentów nazwanych
    for klucz, wartosc in kwargs.items():
        print(f"{klucz} = {wartosc}")

opis(imie="Ola", wiek=30)    # imie = Ola \n wiek = 30

# Łączenie wszystkich rodzajów parametrów
def zarejestruj(nazwa, *tagi, aktywny=True, **metadane):
    print(nazwa, tagi, aktywny, metadane)

zarejestruj("plik", "wazne", "pilne", aktywny=False, autor="Kuba")
# plik ('wazne', 'pilne') False {'autor': 'Kuba'}

# Rozpakowanie przy WYWOŁANIU (operacja odwrotna)
liczby = [1, 2, 3]
dane = {"imie": "Ewa", "wiek": 25}
print(suma(*liczby))         # 6 — rozpakowanie listy
opis(**dane)                 # imie = Ewa \n wiek = 25 — rozpakowanie słownika

Materiały

Jak działają argumenty domyślne i na czym polega pułapka mutowalnego argumentu domyślnego?

Odpowiedź w 30 sekund: Wartości domyślne są obliczane RAZ, w momencie definicji funkcji, a nie przy każdym wywołaniu. Jeśli domyślną wartością jest obiekt mutowalny (lista, słownik, zbiór), to wszystkie wywołania bez tego argumentu współdzielą ten sam obiekt — zmiany kumulują się między wywołaniami. Poprawnym wzorcem jest domyślne None i utworzenie nowego obiektu w ciele funkcji.

Odpowiedź w 2 minuty: Argument domyślny pozwala pominąć wartość przy wywołaniu — wtedy użyta zostaje wartość z definicji. Kluczowy szczegół: wyrażenie definiujące wartość domyślną jest ewaluowane jednokrotnie, kiedy interpreter wykonuje instrukcję def. Wynik zostaje zapisany w atrybucie __defaults__ obiektu funkcji i ten sam obiekt jest używany przy każdym kolejnym wywołaniu, w którym argument pominięto.

Dla wartości niemutowalnych (liczby, łańcuchy, None, krotki) nie ma to znaczenia. Problem pojawia się przy obiektach mutowalnych. Klasyczna pułapka to def dodaj(element, lista=[]): pusta lista powstaje raz i jest współdzielona przez wszystkie wywołania bez podanej listy. Pierwsze wywołanie doda element do listy, ale drugie zobaczy listę z poprzednim elementem — wynik „pamięta" historię, co prawie nigdy nie jest zamierzone i prowadzi do trudnych do wykrycia błędów.

Rozwiązaniem jest idiom sentinel z None: jako wartość domyślną podajemy None, a w ciele funkcji sprawdzamy if lista is None: lista = []. Dzięki temu nowy obiekt powstaje przy każdym wywołaniu. Jeśli None jest poprawną wartością biznesową, można użyć dedykowanego znacznika, np. _SENTINEL = object(). Narzędzia takie jak pylint (W0102 dangerous-default-value) wykrywają tę pułapkę automatycznie.

Przykład kodu:

# ŹLE — mutowalna lista współdzielona między wywołaniami
def dodaj_zle(element, lista=[]):
    lista.append(element)
    return lista

print(dodaj_zle(1))   # [1]
print(dodaj_zle(2))   # [1, 2]  <- niespodzianka! pamięta poprzedni stan
print(dodaj_zle(3))   # [1, 2, 3]

# DOBRZE — idiom z None tworzy nową listę przy każdym wywołaniu
def dodaj_dobrze(element, lista=None):
    if lista is None:
        lista = []
    lista.append(element)
    return lista

print(dodaj_dobrze(1))   # [1]
print(dodaj_dobrze(2))   # [2]  <- za każdym razem świeża lista
print(dodaj_dobrze(3))   # [3]

# Dowód: wartość domyślna powstaje RAZ i jest pamiętana w obiekcie funkcji
print(dodaj_zle.__defaults__)   # ([1, 2, 3],) — ta sama lista

Diagram:

flowchart TD
    A["def f(x, lst=[])"] -->|"ewaluacja RAZ przy definicji"| B["[] zapisane w f.__defaults__"]
    B --> C["wywołanie 1: f(1)"]
    B --> D["wywołanie 2: f(2)"]
    B --> E["wywołanie 3: f(3)"]
    C --> F["ten sam obiekt listy"]
    D --> F
    E --> F
    F --> G["stan kumuluje się: [1, 2, 3]"]

Materiały

21. Czym jest funkcja lambda i kiedy warto jej używać, a kiedy nie?

Odpowiedź dostępna w pełnej wersji. Odblokuj 82 pozostałych odpowiedzi i ucz się z pełnego zestawu.

Odblokuj odpowiedzi
22. Wyjaśnij regułę LEGB dotyczącą rozstrzygania zakresu zmiennych.

Odpowiedź dostępna w pełnej wersji. Odblokuj 82 pozostałych odpowiedzi i ucz się z pełnego zestawu.

Odblokuj odpowiedzi
23. Do czego służą słowa kluczowe global i nonlocal?

Odpowiedź dostępna w pełnej wersji. Odblokuj 82 pozostałych odpowiedzi i ucz się z pełnego zestawu.

Odblokuj odpowiedzi
24. Czym jest domknięcie (closure) i jak działa w praktyce?

Odpowiedź dostępna w pełnej wersji. Odblokuj 82 pozostałych odpowiedzi i ucz się z pełnego zestawu.

Odblokuj odpowiedzi
25. Czym różnią się argumenty pozycyjne, nazwane, keyword-only oraz positional-only?

Odpowiedź dostępna w pełnej wersji. Odblokuj 82 pozostałych odpowiedzi i ucz się z pełnego zestawu.

Odblokuj odpowiedzi
26. Czy Python przekazuje argumenty przez wartość czy przez referencję? Wyjaśnij mechanizm.

Odpowiedź dostępna w pełnej wersji. Odblokuj 82 pozostałych odpowiedzi i ucz się z pełnego zestawu.

Odblokuj odpowiedzi

Programowanie obiektowe

27. Jak tworzy się klasę i instancję obiektu w Pythonie?

Odpowiedź dostępna w pełnej wersji. Odblokuj 82 pozostałych odpowiedzi i ucz się z pełnego zestawu.

Odblokuj odpowiedzi
28. Czym jest self i dlaczego musi być przekazywane jawnie?

Odpowiedź dostępna w pełnej wersji. Odblokuj 82 pozostałych odpowiedzi i ucz się z pełnego zestawu.

Odblokuj odpowiedzi
29. Czym jest metoda __init__ i czym różni się od __new__?

Odpowiedź dostępna w pełnej wersji. Odblokuj 82 pozostałych odpowiedzi i ucz się z pełnego zestawu.

Odblokuj odpowiedzi
30. Jakie są filary programowania obiektowego i jak Python je realizuje?

Odpowiedź dostępna w pełnej wersji. Odblokuj 82 pozostałych odpowiedzi i ucz się z pełnego zestawu.

Odblokuj odpowiedzi
31. Czym są metody specjalne (dunder / magic methods)? Podaj przykłady i ich zastosowanie.

Odpowiedź dostępna w pełnej wersji. Odblokuj 82 pozostałych odpowiedzi i ucz się z pełnego zestawu.

Odblokuj odpowiedzi
32. Jaka jest różnica między @classmethod, @staticmethod a zwykłą metodą instancji?

Odpowiedź dostępna w pełnej wersji. Odblokuj 82 pozostałych odpowiedzi i ucz się z pełnego zestawu.

Odblokuj odpowiedzi
33. Czym jest dekorator @property i do czego służy?

Odpowiedź dostępna w pełnej wersji. Odblokuj 82 pozostałych odpowiedzi i ucz się z pełnego zestawu.

Odblokuj odpowiedzi
34. Jak działa dziedziczenie wielokrotne i czym jest MRO (Method Resolution Order)?

Odpowiedź dostępna w pełnej wersji. Odblokuj 82 pozostałych odpowiedzi i ucz się z pełnego zestawu.

Odblokuj odpowiedzi
35. Czym są dataclasses i jakie problemy rozwiązują?

Odpowiedź dostępna w pełnej wersji. Odblokuj 82 pozostałych odpowiedzi i ucz się z pełnego zestawu.

Odblokuj odpowiedzi
36. Jak Python realizuje enkapsulację — co oznaczają konwencje _nazwa i __nazwa (name mangling)?

Odpowiedź dostępna w pełnej wersji. Odblokuj 82 pozostałych odpowiedzi i ucz się z pełnego zestawu.

Odblokuj odpowiedzi
37. Czym są klasy abstrakcyjne (ABC) i moduł abc?

Odpowiedź dostępna w pełnej wersji. Odblokuj 82 pozostałych odpowiedzi i ucz się z pełnego zestawu.

Odblokuj odpowiedzi

Iteratory i generatory

38. Jaka jest różnica między obiektem iterowalnym (iterable) a iteratorem (iterator)?

Odpowiedź dostępna w pełnej wersji. Odblokuj 82 pozostałych odpowiedzi i ucz się z pełnego zestawu.

Odblokuj odpowiedzi
39. Jak działa protokół iteracji (__iter__ i __next__)?

Odpowiedź dostępna w pełnej wersji. Odblokuj 82 pozostałych odpowiedzi i ucz się z pełnego zestawu.

Odblokuj odpowiedzi
40. Czym jest generator i jak słowo kluczowe yield zmienia działanie funkcji?

Odpowiedź dostępna w pełnej wersji. Odblokuj 82 pozostałych odpowiedzi i ucz się z pełnego zestawu.

Odblokuj odpowiedzi
41. Czym różnią się generator expressions od list comprehensions pod względem pamięci i wydajności?

Odpowiedź dostępna w pełnej wersji. Odblokuj 82 pozostałych odpowiedzi i ucz się z pełnego zestawu.

Odblokuj odpowiedzi
42. Do czego służy konstrukcja yield from?

Odpowiedź dostępna w pełnej wersji. Odblokuj 82 pozostałych odpowiedzi i ucz się z pełnego zestawu.

Odblokuj odpowiedzi
43. Czym jest leniwa ewaluacja (lazy evaluation) i jakie daje korzyści?

Odpowiedź dostępna w pełnej wersji. Odblokuj 82 pozostałych odpowiedzi i ucz się z pełnego zestawu.

Odblokuj odpowiedzi
44. Jak wykorzystać moduł itertools w praktyce? Podaj przykłady.

Odpowiedź dostępna w pełnej wersji. Odblokuj 82 pozostałych odpowiedzi i ucz się z pełnego zestawu.

Odblokuj odpowiedzi
45. Dlaczego range jest pamięciowo wydajny mimo reprezentowania dużych zakresów?

Odpowiedź dostępna w pełnej wersji. Odblokuj 82 pozostałych odpowiedzi i ucz się z pełnego zestawu.

Odblokuj odpowiedzi

Dekoratory i programowanie funkcyjne

46. Czym jest dekorator i jak działa „pod maską"?

Odpowiedź dostępna w pełnej wersji. Odblokuj 82 pozostałych odpowiedzi i ucz się z pełnego zestawu.

Odblokuj odpowiedzi
47. Jak napisać własny dekorator przyjmujący argumenty?

Odpowiedź dostępna w pełnej wersji. Odblokuj 82 pozostałych odpowiedzi i ucz się z pełnego zestawu.

Odblokuj odpowiedzi
48. Do czego służy functools.wraps i dlaczego warto go stosować?

Odpowiedź dostępna w pełnej wersji. Odblokuj 82 pozostałych odpowiedzi i ucz się z pełnego zestawu.

Odblokuj odpowiedzi
49. Czym jest functools.lru_cache i kiedy warto go używać?

Odpowiedź dostępna w pełnej wersji. Odblokuj 82 pozostałych odpowiedzi i ucz się z pełnego zestawu.

Odblokuj odpowiedzi
50. Jak działają funkcje map, filter i reduce?

Odpowiedź dostępna w pełnej wersji. Odblokuj 82 pozostałych odpowiedzi i ucz się z pełnego zestawu.

Odblokuj odpowiedzi
51. Czym jest functools.partial i w jakich sytuacjach się przydaje?

Odpowiedź dostępna w pełnej wersji. Odblokuj 82 pozostałych odpowiedzi i ucz się z pełnego zestawu.

Odblokuj odpowiedzi
52. Czym są dekoratory klas (class decorators) i czym różnią się od dekoratorów funkcji?

Odpowiedź dostępna w pełnej wersji. Odblokuj 82 pozostałych odpowiedzi i ucz się z pełnego zestawu.

Odblokuj odpowiedzi
53. Jakie są typowe zastosowania dekoratorów w praktyce (logowanie, autoryzacja, cache, pomiar czasu)?

Odpowiedź dostępna w pełnej wersji. Odblokuj 82 pozostałych odpowiedzi i ucz się z pełnego zestawu.

Odblokuj odpowiedzi

Obsługa wyjątków i context managery

54. Jak działa blok try / except / else / finally?

Odpowiedź dostępna w pełnej wersji. Odblokuj 82 pozostałych odpowiedzi i ucz się z pełnego zestawu.

Odblokuj odpowiedzi
55. Czym różni się podejście EAFP od LBYL i które jest bardziej „pythoniczne"?

Odpowiedź dostępna w pełnej wersji. Odblokuj 82 pozostałych odpowiedzi i ucz się z pełnego zestawu.

Odblokuj odpowiedzi
56. Jak utworzyć własny wyjątek (custom exception) i kiedy to robić?

Odpowiedź dostępna w pełnej wersji. Odblokuj 82 pozostałych odpowiedzi i ucz się z pełnego zestawu.

Odblokuj odpowiedzi
57. Jak wygląda hierarchia wyjątków w Pythonie (BaseException, Exception)?

Odpowiedź dostępna w pełnej wersji. Odblokuj 82 pozostałych odpowiedzi i ucz się z pełnego zestawu.

Odblokuj odpowiedzi
58. Do czego służy instrukcja with i czym jest context manager?

Odpowiedź dostępna w pełnej wersji. Odblokuj 82 pozostałych odpowiedzi i ucz się z pełnego zestawu.

Odblokuj odpowiedzi
59. Jak napisać własny context manager (protokół __enter__/__exit__ oraz contextlib)?

Odpowiedź dostępna w pełnej wersji. Odblokuj 82 pozostałych odpowiedzi i ucz się z pełnego zestawu.

Odblokuj odpowiedzi
60. Czym jest raise ... from ... i jak działa łańcuchowanie wyjątków (exception chaining)?

Odpowiedź dostępna w pełnej wersji. Odblokuj 82 pozostałych odpowiedzi i ucz się z pełnego zestawu.

Odblokuj odpowiedzi
61. Jakie są dobre praktyki obsługi wyjątków i dlaczego należy unikać „gołego" except:?

Odpowiedź dostępna w pełnej wersji. Odblokuj 82 pozostałych odpowiedzi i ucz się z pełnego zestawu.

Odblokuj odpowiedzi

Moduły, pakiety i zarządzanie zależnościami

62. Czym jest moduł, a czym pakiet w Pythonie?

Odpowiedź dostępna w pełnej wersji. Odblokuj 82 pozostałych odpowiedzi i ucz się z pełnego zestawu.

Odblokuj odpowiedzi
63. Jak działa system importów i jaką rolę pełni plik __init__.py?

Odpowiedź dostępna w pełnej wersji. Odblokuj 82 pozostałych odpowiedzi i ucz się z pełnego zestawu.

Odblokuj odpowiedzi
64. Jaka jest różnica między importem absolutnym a względnym?

Odpowiedź dostępna w pełnej wersji. Odblokuj 82 pozostałych odpowiedzi i ucz się z pełnego zestawu.

Odblokuj odpowiedzi
65. Czym jest konstrukcja if __name__ == "__main__": i kiedy się jej używa?

Odpowiedź dostępna w pełnej wersji. Odblokuj 82 pozostałych odpowiedzi i ucz się z pełnego zestawu.

Odblokuj odpowiedzi
66. Czym jest wirtualne środowisko (venv) i dlaczego warto go używać?

Odpowiedź dostępna w pełnej wersji. Odblokuj 82 pozostałych odpowiedzi i ucz się z pełnego zestawu.

Odblokuj odpowiedzi
67. Jak zarządzać zależnościami za pomocą pip i pliku requirements.txt?

Odpowiedź dostępna w pełnej wersji. Odblokuj 82 pozostałych odpowiedzi i ucz się z pełnego zestawu.

Odblokuj odpowiedzi
68. Czym różnią się pip, pipenv, poetry oraz conda?

Odpowiedź dostępna w pełnej wersji. Odblokuj 82 pozostałych odpowiedzi i ucz się z pełnego zestawu.

Odblokuj odpowiedzi
69. Jak Python wyszukuje moduły (sys.path) i czym jest zmienna PYTHONPATH?

Odpowiedź dostępna w pełnej wersji. Odblokuj 82 pozostałych odpowiedzi i ucz się z pełnego zestawu.

Odblokuj odpowiedzi

Zarządzanie pamięcią i GIL

70. Jak Python zarządza pamięcią (zliczanie referencji i garbage collector)?

Odpowiedź dostępna w pełnej wersji. Odblokuj 82 pozostałych odpowiedzi i ucz się z pełnego zestawu.

Odblokuj odpowiedzi
71. Czym jest cykliczna referencja i jak garbage collector sobie z nią radzi?

Odpowiedź dostępna w pełnej wersji. Odblokuj 82 pozostałych odpowiedzi i ucz się z pełnego zestawu.

Odblokuj odpowiedzi
72. Czym jest GIL (Global Interpreter Lock) i jakie ma praktyczne konsekwencje?

Odpowiedź dostępna w pełnej wersji. Odblokuj 82 pozostałych odpowiedzi i ucz się z pełnego zestawu.

Odblokuj odpowiedzi
73. Dlaczego GIL ogranicza równoległość wątków dla zadań CPU-bound?

Odpowiedź dostępna w pełnej wersji. Odblokuj 82 pozostałych odpowiedzi i ucz się z pełnego zestawu.

Odblokuj odpowiedzi
74. Czym są __slots__ i jak wpływają na zużycie pamięci oraz wydajność?

Odpowiedź dostępna w pełnej wersji. Odblokuj 82 pozostałych odpowiedzi i ucz się z pełnego zestawu.

Odblokuj odpowiedzi
75. Czym jest interning małych liczb całkowitych i napisów w Pythonie?

Odpowiedź dostępna w pełnej wersji. Odblokuj 82 pozostałych odpowiedzi i ucz się z pełnego zestawu.

Odblokuj odpowiedzi
76. Jak profilować wydajność i zużycie pamięci kodu (cProfile, timeit, memory_profiler)?

Odpowiedź dostępna w pełnej wersji. Odblokuj 82 pozostałych odpowiedzi i ucz się z pełnego zestawu.

Odblokuj odpowiedzi
77. Jakie techniki pozwalają zoptymalizować wydajność kodu w Pythonie?

Odpowiedź dostępna w pełnej wersji. Odblokuj 82 pozostałych odpowiedzi i ucz się z pełnego zestawu.

Odblokuj odpowiedzi

Współbieżność i programowanie asynchroniczne

78. Jakie modele współbieżności oferuje Python (threading, multiprocessing, asyncio)?

Odpowiedź dostępna w pełnej wersji. Odblokuj 82 pozostałych odpowiedzi i ucz się z pełnego zestawu.

Odblokuj odpowiedzi
79. Kiedy użyłbyś threading, a kiedy multiprocessing?

Odpowiedź dostępna w pełnej wersji. Odblokuj 82 pozostałych odpowiedzi i ucz się z pełnego zestawu.

Odblokuj odpowiedzi
80. Czym różnią się zadania CPU-bound od I/O-bound w kontekście wyboru modelu współbieżności?

Odpowiedź dostępna w pełnej wersji. Odblokuj 82 pozostałych odpowiedzi i ucz się z pełnego zestawu.

Odblokuj odpowiedzi
81. Czym jest asyncio i jak działa pętla zdarzeń (event loop)?

Odpowiedź dostępna w pełnej wersji. Odblokuj 82 pozostałych odpowiedzi i ucz się z pełnego zestawu.

Odblokuj odpowiedzi
82. Jak działają słowa kluczowe async i await?

Odpowiedź dostępna w pełnej wersji. Odblokuj 82 pozostałych odpowiedzi i ucz się z pełnego zestawu.

Odblokuj odpowiedzi
83. Czym jest korutyna (coroutine) i czym różni się od generatora?

Odpowiedź dostępna w pełnej wersji. Odblokuj 82 pozostałych odpowiedzi i ucz się z pełnego zestawu.

Odblokuj odpowiedzi
84. Do czego służy moduł concurrent.futures (ThreadPoolExecutor, ProcessPoolExecutor)?

Odpowiedź dostępna w pełnej wersji. Odblokuj 82 pozostałych odpowiedzi i ucz się z pełnego zestawu.

Odblokuj odpowiedzi
85. Jak bezpiecznie współdzielić dane między wątkami (blokady, kolejki)?

Odpowiedź dostępna w pełnej wersji. Odblokuj 82 pozostałych odpowiedzi i ucz się z pełnego zestawu.

Odblokuj odpowiedzi
86. Czym jest wyścig (race condition) oraz zakleszczenie (deadlock) i jak ich unikać?

Odpowiedź dostępna w pełnej wersji. Odblokuj 82 pozostałych odpowiedzi i ucz się z pełnego zestawu.

Odblokuj odpowiedzi

Nowoczesny Python: typowanie i nowe funkcje

87. Czym są adnotacje typów (type hints) i jak się je stosuje (PEP 484)?

Odpowiedź dostępna w pełnej wersji. Odblokuj 82 pozostałych odpowiedzi i ucz się z pełnego zestawu.

Odblokuj odpowiedzi
88. Czy adnotacje typów wpływają na działanie programu w czasie wykonania?

Odpowiedź dostępna w pełnej wersji. Odblokuj 82 pozostałych odpowiedzi i ucz się z pełnego zestawu.

Odblokuj odpowiedzi
89. Czym jest mypy i jak pomaga w statycznej analizie typów?

Odpowiedź dostępna w pełnej wersji. Odblokuj 82 pozostałych odpowiedzi i ucz się z pełnego zestawu.

Odblokuj odpowiedzi
90. Czym są f-stringi i jakie mają przewagi nad %, str.format() i konkatenacją?

Odpowiedź dostępna w pełnej wersji. Odblokuj 82 pozostałych odpowiedzi i ucz się z pełnego zestawu.

Odblokuj odpowiedzi
91. Czym jest operator morsa (walrus operator :=) i kiedy go używać?

Odpowiedź dostępna w pełnej wersji. Odblokuj 82 pozostałych odpowiedzi i ucz się z pełnego zestawu.

Odblokuj odpowiedzi
92. Czym jest dopasowywanie strukturalne (match / case) wprowadzone w Pythonie 3.10?

Odpowiedź dostępna w pełnej wersji. Odblokuj 82 pozostałych odpowiedzi i ucz się z pełnego zestawu.

Odblokuj odpowiedzi
93. Jak działa moduł typing (np. Optional, Union, list[int], Callable, generyki)?

Odpowiedź dostępna w pełnej wersji. Odblokuj 82 pozostałych odpowiedzi i ucz się z pełnego zestawu.

Odblokuj odpowiedzi
94. Czym jest Protocol i typowanie strukturalne (statyczny duck typing)?

Odpowiedź dostępna w pełnej wersji. Odblokuj 82 pozostałych odpowiedzi i ucz się z pełnego zestawu.

Odblokuj odpowiedzi

Testowanie i dobre praktyki

95. Jakie są różnice między unittest a pytest?

Odpowiedź dostępna w pełnej wersji. Odblokuj 82 pozostałych odpowiedzi i ucz się z pełnego zestawu.

Odblokuj odpowiedzi
96. Jak pisać testy jednostkowe w pytest i jak je uruchamiać?

Odpowiedź dostępna w pełnej wersji. Odblokuj 82 pozostałych odpowiedzi i ucz się z pełnego zestawu.

Odblokuj odpowiedzi
97. Czym jest mockowanie i do czego służą unittest.mock oraz monkeypatch?

Odpowiedź dostępna w pełnej wersji. Odblokuj 82 pozostałych odpowiedzi i ucz się z pełnego zestawu.

Odblokuj odpowiedzi
98. Czym są fixtures w pytest i jak zarządzają cyklem życia danych testowych?

Odpowiedź dostępna w pełnej wersji. Odblokuj 82 pozostałych odpowiedzi i ucz się z pełnego zestawu.

Odblokuj odpowiedzi
99. Czym jest PEP 8 i jakie narzędzia pomagają utrzymać jakość kodu (black, flake8, ruff)?

Odpowiedź dostępna w pełnej wersji. Odblokuj 82 pozostałych odpowiedzi i ucz się z pełnego zestawu.

Odblokuj odpowiedzi
100. Jak debugować kod Pythona (pdb, breakpoint(), logowanie)?

Odpowiedź dostępna w pełnej wersji. Odblokuj 82 pozostałych odpowiedzi i ucz się z pełnego zestawu.

Odblokuj odpowiedzi
101. Czym jest pokrycie kodu testami (code coverage) i jak je mierzyć?

Odpowiedź dostępna w pełnej wersji. Odblokuj 82 pozostałych odpowiedzi i ucz się z pełnego zestawu.

Odblokuj odpowiedzi
102. Jakie są dobre praktyki strukturyzowania i organizacji projektu w Pythonie?

Odpowiedź dostępna w pełnej wersji. Odblokuj 82 pozostałych odpowiedzi i ucz się z pełnego zestawu.

Odblokuj odpowiedzi

Chcesz poznać wszystkie odpowiedzi?

Uzyskaj pełny dostęp do 102 pytań rekrutacyjnych z Python oraz pozostałych technologii.

Zobacz plany cenowe