Last modified: June 06, 2026

This article is written in: 🇵🇱

Kod bajtowy

Kod bajtowy (ang. bytecode) w Pythonie to pośrednia, niskopoziomowa reprezentacja kodu źródłowego, która jest zrozumiała dla wirtualnej maszyny Pythona (Python Virtual Machine, PVM). Kiedy uruchamiamy skrypt Pythona, interpreter nie wykonuje bezpośrednio kodu źródłowego; zamiast tego, najpierw kompiluje go do kodu bajtowego. Ten proces kompilacji jest automatyczny i zazwyczaj niewidoczny dla programisty.

Kod bajtowy jest zoptymalizowaną wersją kodu, która może być szybciej interpretowana przez PVM. Dzięki temu wykonanie programu jest bardziej efektywne, ponieważ PVM operuje na kodzie bajtowym zamiast na kodzie źródłowym wysokiego poziomu. Kod bajtowy jest przenośny między różnymi platformami, co oznacza, że ten sam kod bajtowy może być uruchamiany na różnych systemach operacyjnych z zainstalowanym interpreterem Pythona.

Proces kompilacji w Pythonie

  1. Kiedy uruchamiasz program w Pythonie, interpreter najpierw kompiluje kod źródłowy do kodu bajtowego. Jest to reprezentacja wewnętrzna programu, która jest bardziej zwięzła i szybciej przetwarzana przez PVM.
  2. Skompilowany kod bajtowy może być zapisywany w plikach z rozszerzeniem .pyc w katalogu __pycache__. Dzięki temu przy ponownym uruchomieniu programu Python może użyć już skompilowanego kodu bajtowego, co przyspiesza uruchamianie aplikacji.
  3. Kod bajtowy jest następnie interpretowany przez wirtualną maszynę Pythona, która wykonuje instrukcje zawarte w kodzie bajtowym.

Zalety używania kodu bajtowego

Struktura kodu bajtowego

Kod bajtowy składa się z sekwencji instrukcji, z których każda reprezentuje podstawową operację, taką jak:

Przykład kodu bajtowego

Rozważmy prosty kod:

def powitaj(imie):
    print(f"Cześć, {imie}!")

Kompilując tę funkcję do kodu bajtowego i używając modułu dis, otrzymamy:

2           0 LOAD_GLOBAL              0 (print)
              2 LOAD_CONST               1 ('Cześć, {}!')
              4 LOAD_FAST                0 (imie)
              6 FORMAT_VALUE             0
              8 BUILD_STRING             2
             10 CALL_FUNCTION            1
             12 POP_TOP
             14 LOAD_CONST               0 (None)
             16 RETURN_VALUE

Każda z tych instrukcji odpowiada konkretnemu działaniu w funkcji, takim jak ładowanie wartości, wywołanie funkcji czy zwrócenie wartości.

Analiza funkcji z użyciem modułu dis

Moduł dis (disassembler) jest narzędziem wbudowanym w Pythona, które pozwala na dezasemblację kodu bajtowego. Umożliwia on analizę, w jaki sposób interpreter Pythona przekształca kod źródłowy na instrukcje kodu bajtowego.

Użycie modułu dis

Aby użyć modułu dis, należy go zaimportować:

from dis import dis

Następnie można przekazać funkcję lub inny obiekt do funkcji dis(), aby wyświetlić jego kod bajtowy.

Przykład analizy funkcji

Rozważmy funkcję sumującą dwie liczby:

def suma(a, b):
    return a + b

dis(suma)

Wynik dezasemblacji:

2           0 LOAD_FAST                0 (a)
              2 LOAD_FAST                1 (b)
              4 BINARY_ADD
              6 RETURN_VALUE

Analiza złożonej funkcji

Weźmy bardziej złożony przykład z użyciem pętli i warunków:

def licz_parzyste(n):
    suma = 0
    for i in range(n):
        if i % 2 == 0:
            suma += i
    return suma

dis(licz_parzyste)

Wynik dezasemblacji będzie dłuższy i zawierać będzie instrukcje obsługujące pętle (SETUP_LOOP, FOR_ITER), warunki (POP_JUMP_IF_FALSE), operacje arytmetyczne (INPLACE_ADD), itp.

Analizując ten kod bajtowy, możemy zrozumieć, jak Python implementuje pętle i warunki na niskim poziomie.

Korzyści z analizy kodu bajtowego

Analiza bloków kodu

Moduł dis pozwala również na analizę dowolnych bloków kodu, nie tylko funkcji. Możemy skompilować fragment kodu za pomocą funkcji compile(), a następnie zdezasemblować go.

Użycie funkcji compile()

Funkcja compile() przekształca ciąg znaków zawierający kod źródłowy Pythona w obiekt kodu bajtowego.

code = compile(source, filename, mode)

Przykład analizy bloku kodu

from dis import dis

code = compile("""
x = 10
y = 20
z = x + y
print(z)
""", "<string>", "exec")

dis(code)

Wynik dezasemblacji pokaże, jakie instrukcje są wykonywane podczas inicjalizacji zmiennych, wykonywania operacji arytmetycznych i wywoływania funkcji print.

Wyjaśnienie instrukcji

Analiza kodu z zewnętrznych bibliotek za pomocą modułu inspect

Moduł inspect w Pythonie dostarcza potężnych narzędzi do introspekcji, czyli badania obiektów w czasie wykonywania programu. Pozwala na analizę modułów, klas, funkcji, metod, ramek stosu i kodu źródłowego. Jest to szczególnie przydatne przy pracy z zewnętrznymi bibliotekami, gdy chcemy zrozumieć ich wewnętrzne działanie lub znaleźć odpowiedzi na pytania, których nie ma w dokumentacji.

Kluczowe funkcje dostępne w module inspect

Oto poprawiona tabela w formacie Markdown, gdzie ostatni wiersz został odpowiednio dostosowany:

Funkcja Opis
inspect.getsource(object) Zwraca kod źródłowy obiektu jako string. Umożliwia zobaczenie implementacji funkcji, klasy czy metody.
inspect.getdoc(object) Zwraca dokumentację obiektu (docstring). Pozwala na szybki dostęp do informacji o tym, co robi dany obiekt.
inspect.getcomments(object) Zwraca komentarze poprzedzające definicję obiektu.
inspect.signature(object) Zwraca obiekt Signature, który reprezentuje podpis funkcji lub metody, w tym informacje o argumentach i wartościach domyślnych.
inspect.getmodule(object) Zwraca moduł, w którym obiekt został zdefiniowany.
inspect.getmembers(object, predicate=None) Zwraca listę krotek (name, value) dla wszystkich atrybutów obiektu. Opcjonalnie można podać predykat filtrujący wyniki.
inspect.isclass(object), inspect.isfunction(object), inspect.ismethod(object) Funkcje kontrolne sprawdzające, czy obiekt jest klasą, funkcją lub metodą.

Przykładowe użycie

Przeanalizujmy klasę Tk z modułu tkinter:

import inspect
import tkinter

# Pobranie kodu źródłowego klasy Tk
source = inspect.getsource(tkinter.Tk)
print("Kod źródłowy klasy Tk:")
print(source)

# Pobranie dokumentacji klasy Tk
doc = inspect.getdoc(tkinter.Tk)
print("\nDokumentacja klasy Tk:")
print(doc)

# Pobranie podpisu konstruktora klasy Tk
signature = inspect.signature(tkinter.Tk)
print("\nPodpis konstruktora klasy Tk:")
print(signature)

# Sprawdzenie, czy Tk jest klasą
is_class = inspect.isclass(tkinter.Tk)
print(f"\nCzy tkinter.Tk jest klasą? {is_class}")

# Pobranie członków klasy Tk
members = inspect.getmembers(tkinter.Tk)
print("\nCzłonkowie klasy Tk:")
for name, value in members:
    print(f"{name}: {value}")

Praktyczne zastosowania

Ograniczenia i uwagi

Przykład problemu z brakiem kodu źródłowego

Jeśli spróbujemy użyć inspect.getsource() na wbudowanej funkcji, np.:

import inspect

print(inspect.getsource(len))

Otrzymamy błąd OSError, ponieważ funkcja len jest zaimplementowana w C i nie ma dostępnego kodu źródłowego w Pythonie.

Tabela najczęstszych instrukcji kodu bajtowego

Instrukcja Kategoria Opis
LOAD_CONST Ładowanie Ładuje stałą na stos
LOAD_FAST Ładowanie Ładuje zmienną lokalną na stos
LOAD_GLOBAL Ładowanie Ładuje zmienną globalną na stos
LOAD_NAME Ładowanie Ładuje nazwę ze scope'u
STORE_FAST Przechowywanie Zapisuje wartość ze stosu do zmiennej lokalnej
STORE_NAME Przechowywanie Zapisuje wartość ze stosu pod nazwą
STORE_GLOBAL Przechowywanie Zapisuje wartość ze stosu do zmiennej globalnej
BINARY_ADD Arytmetyka Dodaje dwie wartości ze szczytu stosu
BINARY_MULTIPLY Arytmetyka Mnoży dwie wartości ze szczytu stosu
BINARY_SUBSCR Operacje Dostęp do elementu: a[b]
COMPARE_OP Porównanie Porównuje dwie wartości (==, <, >, itd.)
POP_JUMP_IF_FALSE Skok Skacze jeśli wartość na stosie jest False
POP_JUMP_IF_TRUE Skok Skacze jeśli wartość na stosie jest True
JUMP_ABSOLUTE Skok Bezwarunkowy skok do danego offsetu
FOR_ITER Pętla Pobiera następny element z iteratora
CALL_FUNCTION Wywołanie Wywołuje funkcję z argumentami ze stosu
RETURN_VALUE Powrót Zwraca wartość ze szczytu stosu
BUILD_LIST Budowanie Tworzy listę z n elementów ze stosu
BUILD_TUPLE Budowanie Tworzy krotkę z n elementów ze stosu
BUILD_MAP Budowanie Tworzy słownik z par klucz-wartość
UNPACK_SEQUENCE Rozpakowywanie Rozpakowuje sekwencję na poszczególne elementy

Optymalizacja na podstawie analizy kodu bajtowego

Analiza kodu bajtowego może pomóc zrozumieć, dlaczego pewne konstrukcje są szybsze od innych.

Przykład: dostęp do atrybutów vs zmienne lokalne

from dis import dis

def wolniejsza(lista):
    wynik = []
    for x in lista:
        wynik.append(x * 2)  # LOAD_ATTR (append) w każdej iteracji
    return wynik

def szybsza(lista):
    wynik = []
    dodaj = wynik.append  # Jedna operacja LOAD_ATTR
    for x in lista:
        dodaj(x * 2)       # LOAD_FAST w każdej iteracji
    return wynik

def najszybsza(lista):
    return [x * 2 for x in lista]  # Zoptymalizowane wewnętrznie

# Porównanie kodu bajtowego
print("=== wolniejsza ===")
dis(wolniejsza)
print("\n=== szybsza ===")
dis(szybsza)

Porównanie wydajności konstrukcji

Konstrukcja Względna szybkość Uwagi
List comprehension Najszybsza Zoptymalizowana wewnętrznie
Pętla z lokalnym append Szybka Unika powtórnego LOAD_ATTR
Pętla z lista.append() Wolniejsza LOAD_ATTR w każdej iteracji
map() z lambdą Średnia Koszt wywołania lambdy
map() z wbudowaną funkcją Szybka Bez kosztu Pythonowego wywołania

Obiekt kodu — __code__

Każda funkcja ma atrybut __code__, który przechowuje skompilowany kod bajtowy oraz metadane:

def przyklad(a, b, c=10):
    x = a + b
    y = x * c
    return y

code = przyklad.__code__

print(f"Nazwa:            {code.co_name}")
print(f"Plik:             {code.co_filename}")
print(f"Liczba argumentów: {code.co_argcount}")
print(f"Zmienne lokalne:  {code.co_varnames}")
print(f"Stałe:            {code.co_consts}")
print(f"Nazwy globalne:   {code.co_names}")
print(f"Rozmiar stosu:    {code.co_stacksize}")
print(f"Bajty kodu:       {code.co_code.hex()}")

Atrybut Opis
co_name Nazwa funkcji
co_argcount Liczba argumentów pozycyjnych
co_varnames Krotka nazw zmiennych lokalnych
co_consts Krotka stałych używanych w funkcji
co_names Krotka nazw globalnych/atrybutów
co_code Surowy kod bajtowy (bytes)
co_stacksize Maksymalny rozmiar stosu operandów
co_flags Flagi (np. czy używa args, *kwargs, generatory)

Pliki .pyc i katalog __pycache__

Python automatycznie kompiluje moduły do kodu bajtowego i zapisuje je w katalogu __pycache__:

__pycache__/
├── modul.cpython-312.pyc    # Python 3.12
├── modul.cpython-311.pyc    # Python 3.11
└── utils.cpython-312.pyc

Kluczowe fakty: