← Kursa Dön
📄 Text · 15 min

Debug, Logging ve Profiling

Yazılımda "bug" (böcek) terimi 1947'den gelir. Harvard'daki bilgisayarın içinde gerçek bir böcek bulunmuş ve günlüğe "First actual case of bug being found" diye yazılmış. O günden beri yazılım hatalarına "bug", hata bulmaya da "debugging" diyoruz.

Debug yapmak bir dedektiflik işidir. Olay yeri var (hatalı çıktı), ipuçları var (log mesajları, hata mesajları), ve sen suçluyu (bug) bulmaya çalışıyorsun. Bazen suçlu apaçık ortada olur, bazen saatlerce iz sürersin.

Bu derste print'ten başlayıp, logging modülüne, pdb debugger'a ve profiling araçlarına kadar tüm silahları öğreneceğiz.


Evet, print() ile debug yapmak "amatörce" görünebilir. Ama gerçek şu ki: herkes yapıyor. Senior geliştiriciler de, Google mühendisleri de. Hızlı ve etkili.

def calculate_discount(price, discount_percent, is_member):
    print(f"DEBUG: price={price}, discount={discount_percent}, member={is_member}")
    
    if is_member:
        discount_percent += 10
        print(f"DEBUG: member discount applied, new percent={discount_percent}")
    
    discount = price * discount_percent / 100
    print(f"DEBUG: discount amount={discount}")
    
    final_price = price - discount
    print(f"DEBUG: final_price={final_price}")
    
    return final_price

# Nerede hata olduğunu görmek çok kolay
result = calculate_discount(100, 20, True)

Çıktı:

DEBUG: price=100, discount=20, member=True
DEBUG: member discount applied, new percent=30
DEBUG: discount amount=30.0
DEBUG: final_price=70.0

f-string ile Hızlı Debug (Python 3.8+)

Python 3.8'den itibaren = operatörü var. Bu harika bir kısayol:

x = 42
name = "Python"
items = [1, 2, 3]

print(f"{x=}")          # x=42
print(f"{name=}")       # name='Python'
print(f"{len(items)=}") # len(items)=3
print(f"{items[0]=}")   # items[0]=1

Değişken adını tekrar yazmana gerek yok!

  • Temizlemeyi unutursun: Üretime print'ler kaçar

  • Performans: Çok fazla print yavaşlatır

  • Kontrol yok: Bazı print'leri açıp kapatmak zor

  • Kalıcı değil: Kapattığında kaybolur

İşte bu sorunları çözen araç: logging.


logging Modülü — Profesyonel Kayıt Tutma

logging modülü print'in profesyonel versiyonudur. Mesajları seviyelerine göre ayırır, dosyaya yazabilir, formatlar ve kolayca açılıp kapatılabilir.

Log Seviyeleri

DEBUG    → En detaylı, geliştirme sırasında (10)
INFO     → Normal akış bilgileri (20)
WARNING  → Dikkat edilmesi gereken durumlar (30)
ERROR    → Bir şey yanlış gitti ama program devam ediyor (40)
CRITICAL → Ciddi hata, program çalışamaz (50)

Düşün ki bir evin alarm sistemisin:

  • DEBUG: "Salon ışığı açıldı" (çok detaylı)

  • INFO: "Ev sahibi eve geldi" (normal bilgi)

  • WARNING: "Arka kapı 5 dakikadır açık" (dikkat)

  • ERROR: "Duman dedektörü arızalı" (sorun var)

  • CRITICAL: "Yangın tespit edildi!" (acil)

Basit Kullanım

import logging

# Basit config
logging.basicConfig(level=logging.DEBUG)

logging.debug("Bu çok detaylı bir bilgi")
logging.info("Program başladı")
logging.warning("Disk %90 dolu")
logging.error("Dosya bulunamadı: config.json")
logging.critical("Veritabanı bağlantısı koptu!")

Çıktı:

DEBUG:root:Bu çok detaylı bir bilgi
INFO:root:Program başladı
WARNING:root:Disk %90 dolu
ERROR:root:Dosya bulunamadı: config.json
CRITICAL:root:Veritabanı bağlantısı koptu!

Level Filtreleme

import logging

# Sadece WARNING ve üstünü göster
logging.basicConfig(level=logging.WARNING)

logging.debug("Bu görünmez")     # ❌ Gizli
logging.info("Bu da görünmez")   # ❌ Gizli
logging.warning("Bu görünür!")   # ✅
logging.error("Bu da görünür!")  # ✅

Geliştirme ortamında DEBUG, production'da WARNING ayarlarsan, tek satır değişiklikle detay seviyesini kontrol edersin.

Logger ile Format Ayarlama

import logging

# Detaylı format
logging.basicConfig(
    level=logging.DEBUG,
    format="%(asctime)s - %(name)s - %(levelname)s - %(message)s",
    datefmt="%Y-%m-%d %H:%M:%S"
)

logger = logging.getLogger("my_app")

logger.debug("Uygulama başlatılıyor...")
logger.info("Kullanıcı giriş yaptı: ahmet@example.com")
logger.warning("API yanıt süresi yüksek: 2.5s")

Çıktı:

2024-01-15 10:30:45 - my_app - DEBUG - Uygulama başlatılıyor...
2024-01-15 10:30:45 - my_app - INFO - Kullanıcı giriş yaptı: ahmet@example.com
2024-01-15 10:30:45 - my_app - WARNING - API yanıt süresi yüksek: 2.5s

Artık her mesajda tarih, modül adı ve seviye var. Print'le bunu yapabilir misin?

Farklı Modüllerde Logger

# database.py
import logging
logger = logging.getLogger("my_app.database")

def connect():
    logger.info("Veritabanına bağlanılıyor...")
    logger.debug("Host: localhost, Port: 5432")
    # ...
    logger.info("Bağlantı başarılı")

# api.py
import logging
logger = logging.getLogger("my_app.api")

def handle_request(path):
    logger.info(f"İstek alındı: {path}")
    # ...
    logger.debug(f"Response status: 200")

Her modülün kendi logger'ı var. Hangi modülden geldiğini hemen anlarsın.


File Handler — Dosyaya Log Yazma

Log'ları terminale değil dosyaya yazmak isteyebilirsin. Özellikle production'da bu şart:

import logging

# File handler oluştur
logger = logging.getLogger("my_app")
logger.setLevel(logging.DEBUG)

# Konsol handler
console_handler = logging.StreamHandler()
console_handler.setLevel(logging.WARNING)  # Konsola sadece WARNING+

# Dosya handler
file_handler = logging.FileHandler("app.log")
file_handler.setLevel(logging.DEBUG)  # Dosyaya her şeyi yaz

# Format
formatter = logging.Formatter(
    "%(asctime)s - %(name)s - %(levelname)s - %(message)s"
)
console_handler.setFormatter(formatter)
file_handler.setFormatter(formatter)

# Handler'ları logger'a ekle
logger.addHandler(console_handler)
logger.addHandler(file_handler)

# Kullanım
logger.debug("Bu sadece dosyada görünür")
logger.warning("Bu hem dosyada hem konsolda")
logger.error("Bu da hem dosyada hem konsolda")

Bu yapıda:

  • Geliştirici konsolda sadece önemli mesajları görür

  • Dosyada tüm detaylar kayıtlı olur

  • Problem olursa dosyayı inceleyebilirsin

RotatingFileHandler — Dosya Boyutu Kontrolü

Log dosyaları çok büyüyebilir. RotatingFileHandler dosyayı belirli boyuta ulaşınca döndürür:

import logging
from logging.handlers import RotatingFileHandler

logger = logging.getLogger("my_app")

handler = RotatingFileHandler(
    "app.log",
    maxBytes=5_000_000,   # 5 MB
    backupCount=3          # En fazla 3 yedek dosya
)
handler.setFormatter(logging.Formatter(
    "%(asctime)s - %(levelname)s - %(message)s"
))

logger.addHandler(handler)

Dosya 5 MB olunca app.logapp.log.1 olur, yeni app.log oluşturulur. En fazla 3 yedek tutulur, en eski silinir.

💡 İpucu: Production'da mutlaka RotatingFileHandler veya TimedRotatingFileHandler kullan. Yoksa log dosyan gigabyte'larca büyüyüp diski doldurabilir.


pdb — Python Debugger

print ve logging iyi ama bazen kodun içine girip adım adım çalıştırmak istersin. İşte pdb bunun için var.

breakpoint() ile Başla

Python 3.7+'dan itibaren breakpoint() fonksiyonu var. Koda bu satırı koyduğunda, program o noktada durur ve interaktif bir debug ortamına düşersin:

def find_bug(items):
    total = 0
    for i, item in enumerate(items):
        if item > 10:
            total += item
        breakpoint()  # Buraya gelince duracak
    return total

result = find_bug([5, 15, 3, 20, 8])

Program breakpoint()'e gelince:

> /home/user/script.py(6)find_bug()
-> return total
(Pdb) 

Artık interaktif debug modundasın!

Temel pdb Komutları

n (next)     → Sonraki satıra git (fonksiyon içine GİRME)
s (step)     → Sonraki satıra git (fonksiyon içine GİR)
c (continue) → Sonraki breakpoint'e kadar devam et
p expr       → İfadeyi yazdır (print)
pp expr      → Pretty print
l (list)     → Çevredeki kaynak kodu göster
ll           → Tüm fonksiyon kodunu göster
w (where)    → Call stack'i göster
q (quit)     → Debugger'dan çık
b N          → N. satıra breakpoint koy
cl N         → N. satırdaki breakpoint'i kaldır

pdb Pratik Kullanım

def process_orders(orders):
    results = []
    for order in orders:
        subtotal = order["price"] * order["quantity"]
        tax = subtotal * 0.18
        total = subtotal + tax
        
        # Hmm, total neden yanlış?
        breakpoint()
        
        results.append({
            "order_id": order["id"],
            "total": total
        })
    return results

orders = [
    {"id": 1, "price": 100, "quantity": 2},
    {"id": 2, "price": 50, "quantity": "3"},  # Bug! String quantity
    {"id": 3, "price": 75, "quantity": 1},
]

process_orders(orders)

Debug session:

(Pdb) p order
{'id': 1, 'price': 100, 'quantity': 2}
(Pdb) p subtotal
200
(Pdb) p total
236.0
(Pdb) c
(Pdb) p order
{'id': 2, 'price': 50, 'quantity': '3'}
(Pdb) p subtotal
'505050'          # AHA! String çarpımı!
(Pdb) p type(order["quantity"])
<class 'str'>     # Buldum seni!

Gördün mü? quantity string olarak gelmiş. 50 * "3" yapınca Python string tekrarı yapıyor: "505050". Bug bulundu!

breakpoint() Ortam Değişkeni ile Kontrol

# breakpoint()'leri devre dışı bırak (production)
PYTHONBREAKPOINT=0 python script.py

# Farklı debugger kullan
PYTHONBREAKPOINT=ipdb.set_trace python script.py
PYTHONBREAKPOINT=pudb.set_trace python script.py

⚠️ Dikkat: breakpoint() satırlarını commit etme! Code review'da bu satırı görürsen, muhtemelen unutulmuştur. Bazı takımlar pre-commit hook ile breakpoint kelimesini arayan kontrol koyar.


VS Code Debugger

VS Code'un built-in debugger'ı pdb'den çok daha kullanıcı dostu. Grafik arayüzle:

  • Breakpoint: Satır numarasının yanına tıklayarak koyarsın

  • Variables panel: Tüm değişkenleri gösterir

  • Watch: Belirli ifadeleri izleyebilirsin

  • Call Stack: Fonksiyon çağrı zincirini gösterir

  • Step Over / Step Into / Step Out: Butonlarla gezinirsin

launch.json Ayarı

{
    "version": "0.2.0",
    "configurations": [
        {
            "name": "Python: Current File",
            "type": "debugpy",
            "request": "launch",
            "program": "${file}",
            "console": "integratedTerminal",
            "args": ["--verbose"]
        },
        {
            "name": "Python: pytest",
            "type": "debugpy",
            "request": "launch",
            "module": "pytest",
            "args": ["tests/", "-v"]
        }
    ]
}

VS Code debugger'ın en güzel yanı: conditional breakpoint koyabilmen. Sağ tıklayıp "Add Conditional Breakpoint" → order["id"] == 42 yazarsan, sadece o koşul sağlandığında durur. Binlerce iterasyonluk bir döngüde çok işe yarar.


Profiling — Performans Dedektifliği

Kodun doğru çalışıyor ama yavaş mı? O zaman profiling zamanı. Profiling, kodun hangi kısmının ne kadar zaman harcadığını ölçer.

Analoji: Doktorun yaptığı kan testi gibi. Bir sorun var ama nerede? Kan testi sonuçlarına bakıp sorunu tespit ediyorsun.

cProfile — Built-in Profiler

import cProfile

def slow_function():
    total = 0
    for i in range(1000000):
        total += i ** 2
    return total

def fast_function():
    return sum(i ** 2 for i in range(1000000))

def main():
    slow_function()
    fast_function()

cProfile.run("main()")

Çıktı:

         5 function calls in 0.542 seconds

   Ordered by: standard name

   ncalls  tottime  percall  cumtime  percall filename:lineno(function)
        1    0.000    0.000    0.542    0.542 <string>:1(<module>)
        1    0.321    0.321    0.321    0.321 script.py:3(slow_function)
        1    0.220    0.220    0.221    0.221 script.py:8(fast_function)
        1    0.000    0.000    0.542    0.542 script.py:11(main)
  • ncalls: Kaç kez çağrıldı

  • tottime: Bu fonksiyonda harcanan toplam süre (alt fonksiyonlar hariç)

  • cumtime: Bu fonksiyonda harcanan toplam süre (alt fonksiyonlar dahil)

cProfile'ı Komut Satırından Kullanma

python -m cProfile -s cumtime my_script.py

-s cumtime sonuçları cumulative time'a göre sıralar. En çok zaman harcayan fonksiyon en üstte.

Sonuçları Dosyaya Kaydetme ve Analiz

import cProfile
import pstats

# Profil çıktısını dosyaya kaydet
cProfile.run("main()", "profile_output.prof")

# Sonuçları analiz et
stats = pstats.Stats("profile_output.prof")
stats.sort_stats("cumulative")
stats.print_stats(10)  # En yavaş 10 fonksiyon

snakeviz ile Görsel Analiz

pip install snakeviz
python -m cProfile -o output.prof my_script.py
snakeviz output.prof

Bu tarayıcıda interaktif bir görselleştirme açar. Hangi fonksiyon ne kadar zaman harcamış, sunburst chart ile görürsün.


line_profiler — Satır Satır Profiling

cProfile fonksiyon bazında ölçüm yapar. Ama bazen fonksiyonun hangi satırının yavaş olduğunu bilmek istersin:

pip install line_profiler
# slow_code.py
from line_profiler import profile

@profile
def process_data(data):
    # Satır satır ne kadar sürdüğünü göreceksin
    cleaned = [item.strip().lower() for item in data]
    unique = list(set(cleaned))
    sorted_data = sorted(unique)
    result = {}
    for item in sorted_data:
        result[item] = cleaned.count(item)  # Bu satır yavaş!
    return result
kernprof -l -v slow_code.py

Çıktı:

Line #   Hits   Time   Per Hit   % Time  Line Contents
=======================================================
     4      1     50      50.0      0.1   cleaned = [item.strip()...
     5      1     30      30.0      0.1   unique = list(set(cleaned))
     6      1     20      20.0      0.0   sorted_data = sorted(unique)
     7      1      1       1.0      0.0   result = {}
     8    500   5000      10.0     10.0   for item in sorted_data:
     9    500  45000      90.0     89.8   result[item] = cleaned.count(item)

%89.8 zamanı 9. satırda harcıyor! list.count() O(n) olduğu için, döngü içinde kullanmak O(n²) yapıyor. Counter kullanmak çözüm:

from collections import Counter

@profile
def process_data_fast(data):
    cleaned = [item.strip().lower() for item in data]
    result = dict(Counter(cleaned))  # O(n) — çok daha hızlı!
    return dict(sorted(result.items()))

timeit — Mikro Benchmark

İki yaklaşımdan hangisi daha hızlı? timeit bunu ölçer:

import timeit

# String birleştirme: + vs join
setup = "words = ['hello'] * 1000"

time_plus = timeit.timeit(
    "result = ''\nfor w in words:\n    result += w + ' '",
    setup=setup,
    number=10000
)

time_join = timeit.timeit(
    "result = ' '.join(words)",
    setup=setup,
    number=10000
)

print(f"+ operatörü: {time_plus:.4f}s")
print(f"join():      {time_join:.4f}s")
print(f"join {time_plus/time_join:.1f}x daha hızlı")
+ operatörü: 1.2340s
join():      0.0890s
join 13.9x daha hızlı

Komut Satırından timeit

python -m timeit "''.join(str(i) for i in range(100))"
# 10000 loops, best of 5: 20.5 usec per loop

python -m timeit "''.join([str(i) for i in range(100)])"
# 20000 loops, best of 5: 17.8 usec per loop

timeit ile Fonksiyon Karşılaştırma

import timeit

def approach_1():
    """List append"""
    result = []
    for i in range(1000):
        result.append(i ** 2)
    return result

def approach_2():
    """List comprehension"""
    return [i ** 2 for i in range(1000)]

def approach_3():
    """Map"""
    return list(map(lambda i: i ** 2, range(1000)))

# Her birini ölç
for func in [approach_1, approach_2, approach_3]:
    time = timeit.timeit(func, number=10000)
    print(f"{func.__doc__.strip():25s}: {time:.4f}s")
List append              : 1.0234s
List comprehension       : 0.7891s
Map                      : 0.8567s

List comprehension kazandı! Ama fark çok büyük değilse, okunabilirliği tercih et.

💡 İpucu: timeit mikro benchmark içindir. Gerçek uygulamalarda darboğazı cProfile ile bul, sonra o noktayı timeit ile optimize et. Tüm kodu timeit ile ölçmeye çalışma — zaman kaybı.


Pratik: Yavaş Fonksiyonu Tespit ve İyileştirme

Bir dosya işleme script'imiz var ve çok yavaş çalışıyor. Adım adım sorunu bulup düzeltelim:

Sorunlu Kod

import time

def read_data(filename):
    """Dosyadan veri oku (simülasyon)"""
    time.sleep(0.01)  # I/O simülasyonu
    return [f"item_{i}" for i in range(10000)]

def process_items(items):
    """Her öğeyi işle"""
    results = []
    for item in items:
        processed = item.upper().replace("_", "-")
        # Her seferinde tüm listeyi kontrol etme (O(n²)!)
        if processed not in results:
            results.append(processed)
    return results

def generate_report(items):
    """Rapor oluştur"""
    report = ""
    for item in items:
        report += f"- {item}\n"  # String concatenation anti-pattern
    return report

def find_duplicates(items):
    """Tekrarları bul"""
    duplicates = []
    for i in range(len(items)):
        for j in range(i + 1, len(items)):
            if items[i] == items[j] and items[i] not in duplicates:
                duplicates.append(items[i])
    return duplicates

def main():
    data = read_data("data.txt")
    processed = process_items(data)
    report = generate_report(processed)
    dupes = find_duplicates(data[:1000])  # İlk 1000
    return len(report), len(dupes)

Adım 1: cProfile ile Darboğazı Bul

import cProfile
cProfile.run("main()", sort="cumulative")
   ncalls   tottime   cumtime  filename:lineno(function)
        1     0.000     8.542  script.py:main
        1     5.234     5.234  script.py:find_duplicates  ← !!!
        1     2.890     2.890  script.py:process_items    ← !!
        1     0.398     0.398  script.py:generate_report  ← !
        1     0.020     0.020  script.py:read_data

En yavaş: find_duplicates (5.2s), sonra process_items (2.9s).

Adım 2: Optimize Et

def process_items_fast(items):
    """Set kullanarak O(n) karmaşıklık"""
    seen = set()
    results = []
    for item in items:
        processed = item.upper().replace("_", "-")
        if processed not in seen:  # Set lookup O(1)!
            seen.add(processed)
            results.append(processed)
    return results

def generate_report_fast(items):
    """join kullanarak string birleştirme"""
    lines = [f"- {item}" for item in items]
    return "\n".join(lines)

def find_duplicates_fast(items):
    """Counter kullanarak O(n) karmaşıklık"""
    from collections import Counter
    counts = Counter(items)
    return [item for item, count in counts.items() if count > 1]

Adım 3: Benchmark Karşılaştırma

import timeit

# process_items karşılaştırma
items = [f"item_{i}" for i in range(5000)]

old_time = timeit.timeit(lambda: process_items(items), number=5)
new_time = timeit.timeit(lambda: process_items_fast(items), number=5)
print(f"process_items:      {old_time:.3f}s → {new_time:.3f}s ({old_time/new_time:.0f}x hızlı)")

# generate_report karşılaştırma
old_time = timeit.timeit(lambda: generate_report(items), number=100)
new_time = timeit.timeit(lambda: generate_report_fast(items), number=100)
print(f"generate_report:    {old_time:.3f}s → {new_time:.3f}s ({old_time/new_time:.0f}x hızlı)")

# find_duplicates karşılaştırma
small = [f"item_{i % 500}" for i in range(1000)]
old_time = timeit.timeit(lambda: find_duplicates(small), number=5)
new_time = timeit.timeit(lambda: find_duplicates_fast(small), number=5)
print(f"find_duplicates:    {old_time:.3f}s → {new_time:.3f}s ({old_time/new_time:.0f}x hızlı)")
process_items:      2.890s → 0.012s (240x hızlı)
generate_report:    0.398s → 0.008s (50x hızlı)
find_duplicates:    5.234s → 0.001s (5234x hızlı)

Toplam: 8.5 saniyeden 0.02 saniyeye! 425x hızlanma. Ve tek yaptığımız doğru veri yapılarını kullanmak.


Debug Stratejileri — Sistem Yaklaşımı

Rastgele print koyarak debug yapma. Sistematik ol:

1. Hatayı Yeniden Üret

Hatayı güvenilir şekilde tekrarlayamıyorsan, düzeltmen çok zor. Önce minimal bir örnek oluştur.

# Kötü: "Bazen çalışmıyor"
# İyi: "input=[1, 2, None, 4] olduğunda line 15'te TypeError veriyor"

2. İkiye Bölme (Binary Search Debug)

Kodun ortasına bir print koy. Sorun öncesinde mi sonrasında mı? Bulduğun yarıyı tekrar ikiye böl. Logaritmik hızda daralt.

def complex_pipeline(data):
    step1 = transform(data)
    print(f"After step1: {step1[:3]}")  # Burada doğru mu?
    
    step2 = filter_data(step1)
    print(f"After step2: {step2[:3]}")  # Burada doğru mu?
    
    step3 = aggregate(step2)
    print(f"After step3: {step3}")      # Burada doğru mu?
    
    return format_output(step3)

3. Rubber Duck Debugging

Ciddiyim. Masana bir lastik ördek koy (veya herhangi bir nesne). Sorunu ona sesli olarak anlat. Problemi açıklarken çoğu zaman çözümü de bulursun. Bu gerçekten işe yarıyor çünkü sorunu formüle etmek, düşünce sürecini düzenler.

4. Versiyona Dön (Git Bisect)

# Sorunsuz çalışan son commit'i bul
git bisect start
git bisect bad          # Şu anki commit bozuk
git bisect good abc123  # Bu commit çalışıyordu

# Git otomatik olarak ortadaki commit'i checkout eder
# Test et, bad veya good de
git bisect bad   # veya good
# Tekrarla...

Git logaritmik arama ile bug'ı tanıtan commit'i bulur.


Debug Araçları Karşılaştırma

AraçNe Zaman?AvantajDezavantaj
print()Hızlı kontrolKolay, hızlıTemizlemek lazım
loggingProduction, uzun süreliKalıcı, seviyeliSetup gerekir
breakpoint()/pdbKarmaşık bugİnteraktif, adım adımTerminal-based
VS Code DebuggerGünlük geliştirmeGörsel, kolayIDE gerekli
cProfilePerformans sorunuBuilt-in, genel bakışSatır detayı yok
line_profilerDetaylı performansSatır bazındaKurulum gerekli
timeitKarşılaştırmaDoğru ölçümMikro seviye

⚠️ Dikkat: "Premature optimization is the root of all evil" — Donald Knuth. Önce kodun doğru çalışsın, sonra profiling yap, sonra sadece darboğazları optimize et. Her satırı optimize etmeye çalışmak hem zaman kaybı hem de kodu okunmaz yapar.


Özet

  • Print debugging basit ama etkilidir, f-string = operatörü (Python 3.8+) ile daha da pratik

  • logging modülü print'in profesyonel versiyonudur: seviyeler (DEBUG→CRITICAL), dosyaya yazma, format kontrolü

  • pdb (breakpoint()) ile kodun içine girip adım adım çalıştırabilir, değişkenleri inceleyebilirsin

  • cProfile ile hangi fonksiyonun yavaş olduğunu, line_profiler ile hangi satırın yavaş olduğunu bulursun

  • timeit ile iki yaklaşımı mikro benchmark ile karşılaştırabilirsin

  • Debug yaparken sistematik ol: hatayı yeniden üret, ikiye böl, rubber duck dene