Zaawansowane zarządzanie pamięcią i garbage collector w CPythonie

Kacper Sieradziński
Kacper Sieradziński
4 sierpnia 2025Edukacja3 min czytania

Python automatycznie zarządza pamięcią za pomocą garbage collectorareference counting. W większości przypadków nie musisz o tym myśleć, ale zrozumienie, jak to działa, pomaga w optymalizacji i debugowaniu problemów z pamięcią. W tym artykule poznasz mechanizmy zarządzania pamięcią w CPythonie, jak działa garbage collector, kiedy używać weakref, jak monitorować użycie pamięci i optymalizować aplikacje Python.

Obraz główny Zaawansowane zarządzanie pamięcią i garbage collector w CPythonie

Jak Python zarządza pamięcią?

Python używa dwóch mechanizmów:

  1. Reference Counting – podstawowy mechanizm, działa automatycznie
  2. Garbage Collector – usuwa obiekty w cyklicznych referencjach

Jeśli chcesz poznać narzędzia do profilowania pamięci, sprawdź Profilowanie wydajności w Pythonie.

Reference Counting

Każdy obiekt w Pythonie ma licznik referencji. Gdy licznik spada do zera, obiekt jest natychmiast usuwany.

Jak to działa

Python
1 2 3 4 5 6 7 8 9 10 11 12 import sys a = [1, 2, 3] # Obiekt lista ma 1 referencję (zmienna a) print(sys.getrefcount(a)) # 2 (a + tymczasowa referencja w getrefcount) b = a # Druga referencja print(sys.getrefcount(a)) # 3 del b # Usunięcie referencji print(sys.getrefcount(a)) # 2 del a # Ostatnia referencja usunięta - obiekt może być zwolniony

Kiedy reference counting nie wystarcza?

Reference counting nie radzi sobie z cyklicznymi referencjami:

Python
1 2 3 4 5 6 7 8 9 10 11 12 13 class Node: def __init__(self, value): self.value = value self.next = None # Cykliczna referencja a = Node(1) b = Node(2) a.next = b b.next = a # Cykl! del a del b # Obiekty nadal istnieją w pamięci - cykl!

Tutaj wchodzi garbage collector.

Garbage Collector (GC)

GC wykrywa i usuwa obiekty w cyklicznych referencencjach.

Podstawowe informacje o GC

Python
1 2 3 4 5 import gc # Statystyki GC print(gc.get_count()) # (generation0, generation1, generation2) print(gc.get_stats()) # Szczegółowe statystyki

Generacje GC

GC używa 3 generacji:

  • Generation 0 – nowe obiekty
  • Generation 1 – obiekty, które przeżyły jedną kolekcję
  • Generation 2 – obiekty, które przeżyły wiele kolekcji
Python
1 2 3 4 5 6 7 8 import gc # Ręczne uruchomienie GC collected = gc.collect() print(f"Zwolniono {collected} obiektów") # GC tylko dla generation 0 gc.collect(0)

Wyłączanie/załączanie GC

Czasami warto tymczasowo wyłączyć GC dla wydajności:

Python
1 2 3 4 5 6 7 8 9 10 11 import gc # Wyłącz GC gc.disable() # Kod krytyczny wydajnościowo # ... # Włącz z powrotem gc.enable() gc.collect() # Uruchom kolekcję

weakref – słabe referencje

weakref pozwala na referencję do obiektu bez zwiększania jego licznika referencji. Przydatne do cache'owania i obserwatorów.

Podstawowe użycie

Python
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 import weakref import sys class Data: def __init__(self, value): self.value = value def __repr__(self): return f"Data({self.value})" # Normalna referencja obj = Data(42) print(sys.getrefcount(obj)) # 2 # Słaba referencja weak_ref = weakref.ref(obj) print(sys.getrefcount(obj)) # Nadal 2! # Dostęp przez słabą referencję print(weak_ref()) # Data(42) del obj # Obiekt zwolniony print(weak_ref()) # None - obiekt już nie istnieje

WeakValueDictionary – cache ze słabymi referencjami

Python
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 import weakref class ExpensiveObject: def __init__(self, key): self.key = key # Symulacja kosztownej operacji self.data = list(range(1000000)) def __repr__(self): return f"ExpensiveObject({self.key})" # Cache z automatycznym usuwaniem cache = weakref.WeakValueDictionary() def get_object(key): if key not in cache: print(f"Tworzenie nowego obiektu dla {key}") cache[key] = ExpensiveObject(key) return cache[key] obj1 = get_object("a") # Tworzenie nowego obiektu obj2 = get_object("a") # Użycie z cache del obj1, obj2 # Obiekty usunięte print(len(cache)) # 0 - automatycznie zwolnione z cache

Monitorowanie użycia pamięci

sys.getsizeof()

Python
1 2 3 4 import sys data = [1, 2, 3, 4, 5] print(sys.getsizeof(data)) # Rozmiar obiektu w bajtach

memory_profiler

Python
1 2 3 4 5 6 7 8 from memory_profiler import profile @profile def moja_funkcja(): data = list(range(1000000)) return sum(data) moja_funkcja()

tracemalloc

Python
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 import tracemalloc tracemalloc.start() # Twój kod data = [x**2 for x in range(1000000)] # Statystyki current, peak = tracemalloc.get_traced_memory() print(f"Current: {current / 1024 / 1024:.2f} MB") print(f"Peak: {peak / 1024 / 1024:.2f} MB") # Top 10 alokacji snapshot = tracemalloc.take_snapshot() top_stats = snapshot.statistics('lineno') for stat in top_stats[:10]: print(stat) tracemalloc.stop()

Optymalizacja pamięci

1. Unikaj niepotrzebnych kopii

Python
1 2 3 4 5 6 7 # ❌ Niepotrzebna kopia data = list(range(1000000)) copy = data[:] # Kopia całej listy # ✅ Użyj referencji lub slice tylko gdy potrzeba data = list(range(1000000)) reference = data # Tylko referencja

2. Używaj generatorów dla dużych zbiorów

Generatory to doskonały sposób na oszczędzanie pamięci. Więcej o generatorach znajdziesz w Generatory i przetwarzanie danych:

Python
1 2 3 4 5 6 7 8 9 10 # ❌ Ładuje wszystko do pamięci def read_file_lines(filename): with open(filename) as f: return f.readlines() # Cały plik w pamięci # ✅ Generator - jedna linia na raz def read_file_lines(filename): with open(filename) as f: for line in f: yield line

3. Usuwaj duże obiekty, gdy nie są potrzebne

Python
1 2 3 4 5 6 7 8 9 # Duże dane large_data = list(range(10_000_000)) # Przetwarzanie result = process(large_data) # Usuń duże dane, jeśli nie są już potrzebne del large_data gc.collect() # Wymuś zwolnienie pamięci

4. Używaj slots dla klas z wieloma instancjami

Python
1 2 3 4 5 6 7 8 9 10 11 12 13 # Bez __slots__ class Point: def __init__(self, x, y): self.x = x self.y = y # Z __slots__ - mniej pamięci class PointOptimized: __slots__ = ['x', 'y'] def __init__(self, x, y): self.x = x self.y = y

Najlepsze praktyki

1. Pozwól Python zarządzać pamięcią

W większości przypadków Python robi to dobrze automatycznie.

2. Profiluj przed optymalizacją

Używaj memory_profiler lub tracemalloc do znajdowania problemów.

3. Używaj weakref dla cache'ów i obserwatorów

Unikaj cyklicznych referencji w cache'ach.

4. Usuwaj duże obiekty explicite

Jeśli wiesz, że obiekt nie jest już potrzebny, użyj del.

5. Monitoruj w produkcji

Używaj narzędzi do monitorowania pamięci w aplikacjach produkcyjnych.

Podsumowanie

Zarządzanie pamięcią w Pythonie jest automatyczne, ale zrozumienie mechanizmów pomaga w optymalizacji:

  • Reference Counting – podstawowy mechanizm
  • Garbage Collector – usuwa cykliczne referencje
  • weakref – słabe referencje bez zwiększania licznika
  • Profilowanie – znajdowanie problemów z pamięcią
  • Optymalizacja – generatory, slots, usuwanie dużych obiektów

Co dalej?

Rozwijaj umiejętności optymalizacji:

Pamiętaj: Python zarządza pamięcią automatycznie, ale zrozumienie mechanizmów pozwala na lepszą optymalizację!

Tagi

#Python#Pamięć#Garbage Collector#Optymalizacja#Zaawansowany Python