Python En İyi Pratikler, İpuçları ve Tasarım Desenleri — Kapsamlı Rehber
Deyimsel (Pythonic), sürdürülebilir, performanslı ve güvenli Python kodu yazmak için principal-engineer seviyesinde bir referans.
İçindekiler
- Felsefe & Python’un Zen’i
- Kod Stili & PEP 8
- İsimlendirme Kuralları
- Type Hints & Statik Tipleme
- Veri Yapıları — Deyimsel Kullanım
- Fonksiyonlar: Tasarım & En İyi Pratikler
- Comprehension’lar & Generator İfadeleri
- Iterator’lar & Generator’lar
- Context Manager’lar
- Decorator’lar
- Python’da OOP: Sınıfları Doğru Yazmak
- Dataclass, NamedTuple & Attrs
- Hata Yönetimi & Exception’lar
- Modüller, Paketler & Proje Yapısı
- Eşzamanlılık: Threading, Multiprocessing, Asyncio
- Performans Optimizasyonu
- Test Yazma En İyi Pratikleri
- Loglama
- Güvenlik En İyi Pratikleri
- Python’da Tasarım Desenleri
- Yaygın Tuzaklar & Anti-Pattern’ler
- Araç Ekosistemi
- Son Kontrol Listesi
1. Felsefe & Python’un Zen’i
import this komutunu çalıştırın ve içselleştirin. Bazı ilkeler daha derin bir yorumu hak eder:
- “Explicit is better than implicit” (Açık olan, örtük olandan iyidir). Sihirden kaçının. Import sırasının yan etkilerine güvenmeyin, kesinlikle gerekmedikçe monkeypatch yapmayın ve düz bir fonksiyon yeterliyken kontrol akışını akıllıca metaprogramlama arkasına gizlemeyin.
- “Flat is better than nested” (Düz olan, iç içe geçmişten iyidir). Derin iç içe geçmiş
if/elsebloklarına karşı erken dönüşleri (guard clause) tercih edin. - “Errors should never pass silently” (Hatalar asla sessizce geçmemeli). Hataları yutmak için asla çıplak
except:kullanmayın. - “There should be one — and preferably only one — obvious way to do it” (Bunu yapmanın bir — ve tercihen tek bir — açık yolu olmalı). Takım üyeleri aynı problemi beş farklı şekilde çözdüğünde, bu ifade gücünün bir göstergesi değil, kural (linter ve kod incelemesi yoluyla) oluşturma zamanı geldiğinin sinyalidir.
# Kötü: iç içe geçmiş koşullar
def process(order):
if order is not None:
if order.is_valid():
if order.total > 0:
return charge(order)
else:
return None
else:
return None
else:
return None
# İyi: guard clause'lar (düz, açık, okunabilir)
def process(order):
if order is None:
return None
if not order.is_valid():
return None
if order.total <= 0:
return None
return charge(order)
2. Kod Stili & PEP 8
- Girinti başına 4 boşluk, asla tab kullanmayın.
- Maksimum satır uzunluğu: 79–99 karakter (Black varsayılan olarak 88 kullanır).
- Üst düzey fonksiyon/sınıflar arasında iki boş satır, metotlar arasında bir boş satır.
- Fonksiyon/değişkenler için
snake_case, sınıflar içinPascalCase, sabitler içinUPPER_SNAKE_CASEkullanın. - Import sırası: standart kütüphane → üçüncü parti → yerel; her grup bir boş satırla ayrılır ve grup içinde alfabetik sıralanır (isort bunu otomatikleştirir).
- Stil, kod incelemesinde asla tartışma konusu olmasın diye otomatik biçimlendirme için
blackkullanın.
# Import sırası örneği
import os
import sys
from collections import defaultdict
import numpy as np
import requests
from myapp.core import settings
from myapp.utils import helpers
.format()veya%biçimlendirmesi yerine f-string’leri tercih edin:
name = "Ada"
# İyi
greeting = f"Hello, {name}!"
# Kaçının
greeting = "Hello, {}!".format(name)
greeting = "Hello, %s!" % name
3. İsimlendirme Kuralları
| Öğe | Kural | Örnek |
|---|---|---|
| Modül | snake_case, kısa | data_loader.py |
| Paket | snake_case, tercihen alt çizgisiz | mypackage |
| Sınıf | PascalCase | HttpClient |
| Exception sınıfı | PascalCase + Error eki | ValidationError |
| Fonksiyon/metot | snake_case, fiil öbeği | calculate_total() |
| Değişken | snake_case, isim | user_count |
| Sabit | UPPER_SNAKE_CASE | MAX_RETRIES = 3 |
| “Private” öznitelik | başta tek alt çizgi | self._cache |
| Name-mangled öznitelik | başta çift alt çizgi | self.__internal |
| Kullanılmayan/atılabilir değişken | tek alt çizgi | for _ in range(5): |
Bariz ve dar kapsamlar (döngüde i, j; matematikte x, y) dışında tek harfli isimlerden kaçının. Belirsiz kısaltmalardan kaçının — cfg uygun, cfgr değil.
4. Type Hints & Statik Tipleme
Type hint’ler artık profesyonel kod tabanlarında opsiyonel değildir. Dokümantasyon görevi görürler, IDE otomatik tamamlamayı etkinleştirirler ve mypy/pyright ile hataları çalışma zamanından önce yakalarlar.
from __future__ import annotations
from typing import Optional, Union, Callable, Iterable
def find_user(user_id: int, *, cache: dict[int, "User"] | None = None) -> "User" | None:
...
def apply(fn: Callable[[int, int], int], values: Iterable[tuple[int, int]]) -> list[int]:
return [fn(a, b) for a, b in values]
Ana pratikler:
- Python 3.9+ itibarıyla
typing.List/typing.Dictyerine yerleşik generic’leri (list[int],dict[str, int]) kullanın. - Python 3.10+ itibarıyla
Optional[X]yerineX | Nonekullanın (3.x’in daha eski sürümleri içinfrom __future__ import annotationsile). - Kalıtımı zorlamak yerine yapısal tipleme (“duck typing"i açık hale getirir) için
Protocolkullanın:
from typing import Protocol
class SupportsClose(Protocol):
def close(self) -> None: ...
def cleanup(resource: SupportsClose) -> None:
resource.close()
- Yapılandırılmış dict payload’ları (örn. JSON API yanıtları) için
TypedDictkullanın:
from typing import TypedDict
class UserPayload(TypedDict):
id: int
name: str
email: str
- Değerleri kısıtlamak için
Literalkullanın:
from typing import Literal
def set_mode(mode: Literal["r", "w", "a"]) -> None: ...
- Yeniden kullanılabilir generic konteynerler için
TypeVarveGenerickullanın:
from typing import TypeVar, Generic
T = TypeVar("T")
class Stack(Generic[T]):
def __init__(self) -> None:
self._items: list[T] = []
def push(self, item: T) -> None:
self._items.append(item)
def pop(self) -> T:
return self._items.pop()
- CI ortamında
mypy --strict(veyapyright) çalıştırın. Denetlenmeyen type hint’ler hızla değersizleşir (rot).
5. Veri Yapıları — Deyimsel Kullanım
Doğru konteyneri seçmek
| İhtiyaç | Kullan |
|---|---|
| Sıralı, değiştirilebilir dizi | list |
| Sıralı, değiştirilemez dizi | tuple |
| Benzersiz sırasız öğeler, hızlı üyelik testi | set |
| Anahtar-değer eşlemesi | dict |
| O(1) uçlu FIFO/LIFO kuyruk | collections.deque |
| Tekrar sayımı | collections.Counter |
| Eksik anahtarlarda varsayılan değerler | collections.defaultdict |
| Sona-taşıma özellikli sıralı eşleme | collections.OrderedDict (3.7+ itibarıyla dict’ler zaten sıralı olduğu için çoğunlukla gereksiz, ama move_to_end için hâlâ kullanışlı) |
| Hafif, değiştirilemez kayıt | NamedTuple veya dataclass(frozen=True) |
| Birden fazla dict benzeri nesneyi tembelce (lazy) birleştirme | collections.ChainMap |
from collections import Counter, defaultdict, deque
# Sayma
word_counts = Counter("the quick brown fox jumps over the lazy dog".split())
print(word_counts.most_common(2))
# Gruplama
groups: defaultdict[str, list[int]] = defaultdict(list)
for n in range(10):
groups["even" if n % 2 == 0 else "odd"].append(n)
# Kayan pencere / kuyruk
window = deque(maxlen=3)
for x in range(10):
window.append(x)
Üyelik testleri için set
# Kötü: O(n) üyelik kontrolü
valid_ids = [1, 2, 3, 4, 5]
if user_id in valid_ids:
...
# İyi: ortalama O(1)
valid_ids = {1, 2, 3, 4, 5}
if user_id in valid_ids:
...
Unpacking (paketten çıkarma)
first, *rest = [1, 2, 3, 4]
*init, last = [1, 2, 3, 4]
a, (b, c) = 1, (2, 3)
# Geçici değişken olmadan takas
a, b = b, a
dict.get, setdefault ve walrus operatörü
value = data.get("key", "default")
data.setdefault("key", []).append(item)
# Walrus operatörü (3.8+) çift hesaplamayı önler
if (n := len(data)) > 10:
print(f"Too many items: {n}")
6. Fonksiyonlar: Tasarım & En İyi Pratikler
- Fonksiyonları küçük ve tek amaçlı tutun (Tek Sorumluluk İlkesi fonksiyonlar için de geçerlidir).
- Mümkün olduğunda saf fonksiyonları (yan etkisiz) tercih edin — test etmesi ve mantık yürütmesi daha kolaydır.
- Asla değiştirilebilir varsayılan argümanlar kullanmayın:
# Kötü — liste çağrılar arasında paylaşılır!
def append_item(item, items=[]):
items.append(item)
return items
# İyi
def append_item(item, items: list | None = None):
if items is None:
items = []
items.append(item)
return items
- Bir fonksiyonun çok fazla parametresi olduğunda netlik için yalnızca-anahtar-kelime (keyword-only) argümanlar kullanın:
def create_user(*, name: str, email: str, is_admin: bool = False) -> "User":
...
create_user(name="Ada", email="[email protected]", is_admin=True)
- Argüman isimleri implementasyon detayı olduğunda yalnızca-konumsal (positional-only) parametreler (
/) kullanın:
def add(a: int, b: int, /) -> int:
return a + b
- İç içe dallarda durum biriktirmek yerine erken dönüşü tercih edin.
- ~4’ten fazla parametreli fonksiyonlardan kaçının; onun yerine bir dataclass veya config nesnesi kullanın.
- Docstring’lerle (Google veya NumPy stili) belgeleyin — sadece insanlar için değil, Sphinx gibi araçlar için de.
def calculate_discount(price: float, percentage: float) -> float:
"""Calculate the discounted price.
Args:
price: The original price.
percentage: Discount percentage (0-100).
Returns:
The price after discount is applied.
Raises:
ValueError: If percentage is not between 0 and 100.
"""
if not 0 <= percentage <= 100:
raise ValueError("percentage must be between 0 and 100")
return price * (1 - percentage / 100)
7. Comprehension’lar & Generator İfadeleri
Comprehension’lar, .append() ile elle yazılan döngülerden daha Pythonic ve genellikle daha hızlıdır.
# List comprehension
squares = [x**2 for x in range(10)]
# Set comprehension
unique_lengths = {len(word) for word in ["a", "bb", "ccc", "dd"]}
# Dict comprehension
name_to_len = {name: len(name) for name in ["Ada", "Grace", "Alan"]}
# İç içe comprehension (matrisi düzleştirme)
matrix = [[1, 2], [3, 4]]
flat = [x for row in matrix for x in row]
# Koşullu comprehension
evens = [x for x in range(20) if x % 2 == 0]
# Generator ifadesi — tembel (lazy), bellek dostu
total = sum(x**2 for x in range(1_000_000))
Genel kural: comprehension 2’den fazla iç içe geçme seviyesi veya birden fazla if gerektiriyorsa, okunabilirlik için normal bir döngüye veya adlandırılmış bir yardımcı fonksiyona dönüştürün.
# Fazla akıllıca — okunması zor
result = [y for x in data if x > 0 for y in transform(x) if y is not None]
# Daha iyi
def transform_positive(data):
for x in data:
if x <= 0:
continue
for y in transform(x):
if y is not None:
yield y
8. Iterator’lar & Generator’lar
Generator’lar tembel değerlendirmeyi (lazy evaluation) mümkün kılar ve büyük veya sonsuz diziler için bellek kullanımını ciddi şekilde azaltır.
def fibonacci():
a, b = 0, 1
while True:
yield a
a, b = b, a + b
fib = fibonacci()
first_ten = [next(fib) for _ in range(10)]
Devretme (delegation) için yield from
def flatten(nested):
for item in nested:
if isinstance(item, list):
yield from flatten(item)
else:
yield item
list(flatten([1, [2, 3, [4, 5]], 6])) # [1, 2, 3, 4, 5, 6]
Özel iterator protokolü
class Range:
def __init__(self, start: int, stop: int) -> None:
self.current = start
self.stop = stop
def __iter__(self) -> "Range":
return self
def __next__(self) -> int:
if self.current >= self.stop:
raise StopIteration
value = self.current
self.current += 1
return value
Yalnızca bir kez üzerinden geçmeniz gerektiğinde — özellikle I/O pipeline’ları ve akış (streaming) verisinde — tam bir liste oluşturmak yerine generator’ları tercih edin.
9. Context Manager’lar
Kaynak yönetimi (dosyalar, kilitler, bağlantılar, işlemler) için her zaman with kullanın.
with open("data.txt") as f:
content = f.read()
# Birden fazla context manager
with open("in.txt") as fin, open("out.txt", "w") as fout:
fout.write(fin.read())
Kendi context manager’ınızı yazmak (sınıf tabanlı)
class Timer:
def __enter__(self):
import time
self.start = time.perf_counter()
return self
def __exit__(self, exc_type, exc_val, exc_tb):
import time
self.elapsed = time.perf_counter() - self.start
return False # exception'ları yaymaya devam et
Kendi context manager’ınızı yazmak (fonksiyon tabanlı, contextlib)
from contextlib import contextmanager
@contextmanager
def timer():
import time
start = time.perf_counter()
try:
yield
finally:
print(f"Elapsed: {time.perf_counter() - start:.4f}s")
with timer():
do_expensive_work()
try/except/pass yerine contextlib.suppress
from contextlib import suppress
with suppress(FileNotFoundError):
os.remove("maybe_missing.txt")
Dinamik sayıda context manager için ExitStack
from contextlib import ExitStack
with ExitStack() as stack:
files = [stack.enter_context(open(fname)) for fname in filenames]
# tüm dosyalar exception olsa bile çıkışta otomatik kapanır
10. Decorator’lar
Decorator’lar, kaynak kodunu değiştirmeden fonksiyonlara davranış eklemek için onları sarmalar — Açık/Kapalı İlkesi’nin (Open/Closed Principle) klasik uygulamasıdır.
import functools
import time
def timed(func):
@functools.wraps(func) # __name__, __doc__ vb. korur
def wrapper(*args, **kwargs):
start = time.perf_counter()
result = func(*args, **kwargs)
print(f"{func.__name__} took {time.perf_counter() - start:.4f}s")
return result
return wrapper
@timed
def slow_function():
time.sleep(1)
Argümanlı decorator’lar
def retry(times: int = 3, exceptions: tuple = (Exception,)):
def decorator(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
last_exc = None
for attempt in range(times):
try:
return func(*args, **kwargs)
except exceptions as e:
last_exc = e
raise last_exc
return wrapper
return decorator
@retry(times=5, exceptions=(ConnectionError,))
def fetch_data():
...
Sınıf tabanlı decorator’lar & önbellekleme (caching)
from functools import lru_cache, cache
@lru_cache(maxsize=128)
def fib(n: int) -> int:
return n if n < 2 else fib(n - 1) + fib(n - 2)
@cache # sınırsız önbellek, Python 3.9+
def expensive_lookup(key: str) -> str:
...
Her zaman functools.wraps kullanın — atlamak introspection, hata ayıklama araçlarını ve dokümantasyon üreticilerini bozar.
11. Python’da OOP: Sınıfları Doğru Yazmak
- Kalıtım yerine kompozisyonu tercih edin.
- Çok sayıda örneğe ve sabit özniteliklere sahip sınıflarda bellek verimliliği için
__slots__kullanın:
class Point:
__slots__ = ("x", "y")
def __init__(self, x: float, y: float) -> None:
self.x = x
self.y = y
- Yazdığınız her sınıfta
__repr__uygulayın (hata ayıklama için); değere göre kimlik önemli olduğunda__eq__,__hash__uygulayın.
class Point:
def __init__(self, x: float, y: float) -> None:
self.x = x
self.y = y
def __repr__(self) -> str:
return f"Point(x={self.x!r}, y={self.y!r})"
def __eq__(self, other: object) -> bool:
if not isinstance(other, Point):
return NotImplemented
return (self.x, self.y) == (other.x, other.y)
def __hash__(self) -> int:
return hash((self.x, self.y))
- Elle getter/setter metotları yerine property kullanın:
class Temperature:
def __init__(self, celsius: float) -> None:
self._celsius = celsius
@property
def celsius(self) -> float:
return self._celsius
@celsius.setter
def celsius(self, value: float) -> None:
if value < -273.15:
raise ValueError("Below absolute zero")
self._celsius = value
@property
def fahrenheit(self) -> float:
return self._celsius * 9 / 5 + 32
- Alternatif constructor’lar için
@classmethod, örnek durumuyla ilgisi olmayan yardımcı fonksiyonlar için@staticmethodkullanın:
class User:
def __init__(self, name: str, email: str) -> None:
self.name = name
self.email = email
@classmethod
def from_dict(cls, data: dict) -> "User":
return cls(name=data["name"], email=data["email"])
@staticmethod
def is_valid_email(email: str) -> bool:
return "@" in email
- Arayüzleri tanımlamak için Soyut Temel Sınıflar (
abc) kullanın:
from abc import ABC, abstractmethod
class PaymentProcessor(ABC):
@abstractmethod
def charge(self, amount: float) -> bool: ...
class StripeProcessor(PaymentProcessor):
def charge(self, amount: float) -> bool:
...
- Mixin’leri dikkatli kullanın — küçük, odaklı ve yan etkisiz tutun; MRO (Method Resolution Order) etkilerini belgeleyin.
12. Dataclass, NamedTuple & Attrs
__init__, __repr__, __eq__‘i elle yazmak yerine @dataclass kullanmayı tercih edin.
from dataclasses import dataclass, field
@dataclass(frozen=True, slots=True)
class Point:
x: float
y: float
@dataclass
class Order:
items: list[str] = field(default_factory=list)
total: float = 0.0
def add_item(self, item: str, price: float) -> None:
self.items.append(item)
self.total += price
- Değiştirilemezlik (ve tüm alanlar hashlenebilirse hashlenebilirlik) için
frozen=True. - Bellek ayak izini azaltmak ve yanlışlıkla öznitelik oluşumunu önlemek için
slots=True(3.10+). - Değiştirilebilir varsayılanlar için
field(default_factory=...)kullanın — aslafield(default=[])kullanmayın.
Unpacking’i de destekleyen hafif, tuple benzeri değiştirilemez kayıtlar için NamedTuple:
from typing import NamedTuple
class Point(NamedTuple):
x: float
y: float
p = Point(1.0, 2.0)
x, y = p # tuple unpacking çalışır
attrs (üçüncü parti, dataclass’lardan öncesine dayanır) gelişmiş validator/converter’lar için bazı takımlar tarafından hâlâ tercih edilir, ancak dataclasses + pydantic (doğrulama için) modern ihtiyaçların çoğunu karşılar.
13. Hata Yönetimi & Exception’lar
- Belirli exception’ları yakalayın, asla çıplak
except:kullanmayın.
# Kötü
try:
risky()
except:
pass
# İyi
try:
risky()
except (ConnectionError, TimeoutError) as e:
logger.warning("Network issue: %s", e)
raise
- Domain’iniz için özel bir exception hiyerarşisi oluşturun:
class AppError(Exception):
"""Base exception for this application."""
class ValidationError(AppError):
"""Raised when input validation fails."""
class NotFoundError(AppError):
"""Raised when a resource is not found."""
- Exception’ları çevirirken zincirlerini korumak için
raise ... from errkullanın:
try:
parse(data)
except ValueError as e:
raise ValidationError("Invalid input") from e
elsevefinallybloklarını uygun şekilde kullanın:
try:
conn = connect()
except ConnectionError:
logger.error("Could not connect")
else:
# yalnızca exception fırlatılmadıysa çalışır
process(conn)
finally:
# her zaman çalışır — temizlik
conn.close()
- Exception’lar kontrol akışı için değil istisnai durumlar içindir — ancak dict erişimi gibi durumlarda “İzin İstemektense Af Dilemek Daha Kolaydır” (EAFP) yaklaşımı hâlâ “Önce Bakıp Sonra Atla"dan (LBYL) daha Pythonic’tir:
# Pythonic (EAFP)
try:
value = my_dict["key"]
except KeyError:
value = default
# Daha az Pythonic (LBYL) — eşzamanlı kodda race condition'a yatkın
if "key" in my_dict:
value = my_dict["key"]
else:
value = default
- Birden fazla eşzamanlı hatayı (örn.
asyncio.TaskGroup‘tan gelenleri) ele alırkenExceptionGroupveexcept*(3.11+) kullanın.
14. Modüller, Paketler & Proje Yapısı
Modern bir Python paketi için önerilen src-layout:
myproject/
├── pyproject.toml
├── README.md
├── LICENSE
├── src/
│ └── mypackage/
│ ├── __init__.py
│ ├── core.py
│ ├── models/
│ │ ├── __init__.py
│ │ └── user.py
│ └── utils/
│ ├── __init__.py
│ └── helpers.py
├── tests/
│ ├── __init__.py
│ ├── test_core.py
│ └── conftest.py
└── docs/
- Yalnızca
pyproject.toml(PEP 621) kullanın —setup.py/setup.cfgartık eski (legacy) kabul edilir. src/layout, paketinizi kurulu sürüm yerine yanlışlıkla çalışma dizininden import etmenizi önler (klasik bir test ayak tuzağı).__init__.pydosyalarını sade tutun — genel API’yi yeniden dışa aktarın, mantığı orada tutmayın.- Paket içinde göreli (relative), paketler arasında mutlak (absolute) import kullanın:
# mypackage/models/user.py içinde
from ..utils.helpers import normalize_email # göreli, paket içinde
from mypackage.core import Settings # mutlak, yine de sorun değil
- Bağımlılıkları bir DAG olarak yapılandırarak dairesel import’lardan kaçının; iki modül birbirine ihtiyaç duyuyorsa, paylaşılan kodu üçüncü bir modüle çıkarın.
15. Eşzamanlılık: Threading, Multiprocessing, Asyncio
Ne zaman ne kullanılır
| İş yükü | Araç |
|---|---|
| I/O-bound (ağ, disk, DB) — çok sayıda çakışan bekleme | asyncio veya threading |
| CPU-bound (ağır hesaplama) | multiprocessing veya native uzantılar (NumPy, Cython) |
| Tam bir async yeniden yazım olmadan basit paralel I/O görevleri | concurrent.futures.ThreadPoolExecutor |
| Basit paralel CPU görevleri | concurrent.futures.ProcessPoolExecutor |
GIL (Global Interpreter Lock), saf Python kodu için thread’lerin CPU paralelliği sağlamadığı anlamına gelir — free-threaded (GIL’siz) bir build kullanmadığınız sürece yalnızca I/O-bound işler thread kullanımından fayda görür.
import asyncio
import aiohttp
async def fetch(session: aiohttp.ClientSession, url: str) -> str:
async with session.get(url) as resp:
return await resp.text()
async def main(urls: list[str]) -> list[str]:
async with aiohttp.ClientSession() as session:
tasks = [fetch(session, url) for url in urls]
return await asyncio.gather(*tasks)
asyncio.run(main(["https://example.com"] * 10))
Yapılandırılmış eşzamanlılık için asyncio.TaskGroup (3.11+)
async def main():
async with asyncio.TaskGroup() as tg:
task1 = tg.create_task(fetch(url1))
task2 = tg.create_task(fetch(url2))
# burada her iki task de garanti olarak tamamlanmıştır; exception'lar ExceptionGroup olarak yayılır
CPU-bound paralellik için ProcessPoolExecutor
from concurrent.futures import ProcessPoolExecutor
def heavy_computation(n: int) -> int:
return sum(i * i for i in range(n))
with ProcessPoolExecutor() as executor:
results = list(executor.map(heavy_computation, [10_000_000] * 4))
Windows/spawn tabanlı platformlarda multiprocessing giriş noktalarını her zaman if __name__ == "__main__": ile koruyun.
16. Performans Optimizasyonu
- Optimize etmeden önce ölçün —
cProfile,py-spyveyaline_profilerkullanın. - Yerleşik fonksiyonları ve standart kütüphaneyi tercih edin — C’de implemente edilmişlerdir.
- Sıcak (hot) döngülerde tekrarlanan öznitelik aramaları yerine yerel değişken referansları kullanın.
- Büyük sayısal veri kümeleri için liste yerine
arrayveya NumPy kullanın. - Örnek başına bellek yükünü azaltmak için
__slots__kullanın. - Döngüde string birleştirme:
+=yerine"".join(parts)kullanın.
# Yavaş: tekrarlanan string kopyalarından dolayı O(n^2)
result = ""
for s in strings:
result += s
# Hızlı: O(n)
result = "".join(strings)
- Pahalı saf hesaplamaları
functools.lru_cacheile önbelleğe alın. - Büyük ara listeleri somutlaştırmamak için generator’lar kullanın.
- Gerçekten CPU-bound darboğazlar için
Cython,Numbaveya sıcak yolları Rust’ta (PyO3ile) yeniden yazmayı düşünün. - Mikro-benchmark’lar için
timeitkullanın:
import timeit
timeit.timeit("'-'.join(str(n) for n in range(100))", number=10000)
17. Test Yazma En İyi Pratikleri
- Daha basit assertion sözdizimi ve güçlü fixture’ları için
unittestyerinepytestkullanın.
def test_calculate_discount():
assert calculate_discount(100, 10) == 90
def test_calculate_discount_invalid_percentage():
import pytest
with pytest.raises(ValueError):
calculate_discount(100, 150)
- Kurulum/temizlik ve dependency injection için fixture kullanın:
import pytest
@pytest.fixture
def sample_user():
return User(name="Ada", email="[email protected]")
def test_user_email(sample_user):
assert sample_user.email == "[email protected]"
- Testleri kopyalamak yerine parametrize edin:
@pytest.mark.parametrize("price,pct,expected", [
(100, 0, 100),
(100, 50, 50),
(200, 25, 150),
])
def test_discounts(price, pct, expected):
assert calculate_discount(price, pct) == expected
- Dış bağımlılıkları (ağ, DB, dosya sistemi) mock’layın — unit testlerde asla gerçek servislere gitmeyin.
from unittest.mock import patch
@patch("myapp.services.requests.get")
def test_fetch_user(mock_get):
mock_get.return_value.json.return_value = {"id": 1, "name": "Ada"}
result = fetch_user(1)
assert result["name"] == "Ada"
- Test piramidini hedefleyin: çok sayıda hızlı unit test, daha az entegrasyon testi, çok az uçtan uca test.
- Kapsamı takip etmek için
coverage.pykullanın, ancak körü körüne %100’ü kovalamayın — kritik yollara ve kenar durumlara (edge case) odaklanın. - Testleri deterministik tutun: mock’lamadan duvar saati zamanına, rastgele seed’lere veya ağ erişilebilirliğine bağımlı olmayın.
18. Loglama
Atılabilir betikler dışında hiçbir şey için print() kullanmayın. logging modülünü kullanın.
import logging
logger = logging.getLogger(__name__)
logging.basicConfig(
level=logging.INFO,
format="%(asctime)s [%(levelname)s] %(name)s: %(message)s",
)
def process_order(order_id: int) -> None:
logger.info("Processing order %s", order_id)
try:
...
except Exception:
logger.exception("Failed to process order %s", order_id)
raise
- Log çağrılarında f-string yerine
%stembel (lazy) biçimlendirme kullanın — log seviyesi devre dışıyken biçimlendirme maliyetini önler. - Traceback’i otomatik olarak dahil etmek için bir
exceptbloğu içindelogger.exception()kullanın. - Log toplayıcılarıyla entegrasyon için üretim servislerinde yapılandırılmış (JSON) loglama yapılandırın.
- Asla sırları, parolaları, token’ları veya PII’yi loglamayın.
19. Güvenlik En İyi Pratikleri
- Güvenilmeyen girdi üzerinde asla
eval()veyaexec()kullanmayın. - Token’lar, parolalar ve kriptografik amaçlar için
randomyerinesecretsmodülünü kullanın:
import secrets
token = secrets.token_urlsafe(32)
- Kimlik bilgilerini asla sabit kodlamayın (hardcode) — ortam değişkenleri veya bir secrets manager kullanın.
- Tüm dış girdiyi doğrulayın ve temizleyin (yapılandırılmış doğrulama için
pydantickullanın). - Parametreli sorgular kullanın — asla SQL’i string olarak biçimlendirmeyin:
# Kötü — SQL injection riski
cursor.execute(f"SELECT * FROM users WHERE name = '{name}'")
# İyi — parametreli
cursor.execute("SELECT * FROM users WHERE name = ?", (name,))
- Bağımlılık sürümlerini sabitleyin ve bilinen güvenlik açıklarını yakalamak için düzenli olarak
pip-auditveyasafetyçalıştırın. - Güvenilmeyen veri için
picklekullanmaktan kaçının — deserialization sırasında keyfi kod çalıştırabilir;jsonveyamsgpacktercih edin.
20. Python’da Tasarım Desenleri
Python’un dinamik tiplemesi, first-class fonksiyonları ve duck typing özelliği, birçok klasik GoF (Gang of Four) deseninin daha basit veya gereksiz olduğu anlamına gelir — ancak bunları (ve Pythonic karşılıklarını) anlamak yine de önemlidir.
Yaratımsal (Creational) Desenler
Singleton
class Singleton:
_instance = None
def __new__(cls, *args, **kwargs):
if cls._instance is None:
cls._instance = super().__new__(cls)
return cls._instance
Pythonic alternatif: modülün kendisi zaten bir singleton’dur — modül seviyesi durum, çoğu durumda bir Singleton sınıfından daha basit ve daha deyimseldir.
Factory Method
from abc import ABC, abstractmethod
class Notifier(ABC):
@abstractmethod
def send(self, message: str) -> None: ...
class EmailNotifier(Notifier):
def send(self, message: str) -> None:
print(f"Email: {message}")
class SmsNotifier(Notifier):
def send(self, message: str) -> None:
print(f"SMS: {message}")
def notifier_factory(kind: str) -> Notifier:
return {"email": EmailNotifier, "sms": SmsNotifier}[kind]()
Abstract Factory
class GUIFactory(ABC):
@abstractmethod
def create_button(self) -> "Button": ...
@abstractmethod
def create_checkbox(self) -> "Checkbox": ...
class WindowsFactory(GUIFactory):
def create_button(self): return WindowsButton()
def create_checkbox(self): return WindowsCheckbox()
class MacFactory(GUIFactory):
def create_button(self): return MacButton()
def create_checkbox(self): return MacCheckbox()
Builder
class RequestBuilder:
def __init__(self) -> None:
self._url = ""
self._headers: dict[str, str] = {}
self._method = "GET"
def url(self, url: str) -> "RequestBuilder":
self._url = url
return self
def header(self, key: str, value: str) -> "RequestBuilder":
self._headers[key] = value
return self
def method(self, method: str) -> "RequestBuilder":
self._method = method
return self
def build(self) -> dict:
return {"url": self._url, "headers": self._headers, "method": self._method}
request = (
RequestBuilder()
.url("https://api.example.com")
.header("Authorization", "Bearer token")
.method("POST")
.build()
)
Prototype
import copy
class Prototype:
def clone(self):
return copy.deepcopy(self)
Yapısal (Structural) Desenler
Adapter
class OldPrinter:
def print_old(self, text: str) -> None:
print(f"[OLD] {text}")
class NewPrinterInterface(ABC):
@abstractmethod
def print(self, text: str) -> None: ...
class PrinterAdapter(NewPrinterInterface):
def __init__(self, old_printer: OldPrinter) -> None:
self._old_printer = old_printer
def print(self, text: str) -> None:
self._old_printer.print_old(text)
Decorator (yapısal desen, @decorator sözdizimi değil)
class Coffee(ABC):
@abstractmethod
def cost(self) -> float: ...
class SimpleCoffee(Coffee):
def cost(self) -> float:
return 2.0
class MilkDecorator(Coffee):
def __init__(self, coffee: Coffee) -> None:
self._coffee = coffee
def cost(self) -> float:
return self._coffee.cost() + 0.5
coffee = MilkDecorator(SimpleCoffee())
print(coffee.cost()) # 2.5
Facade
class CPU:
def freeze(self): ...
def jump(self, position): ...
def execute(self): ...
class Memory:
def load(self, position, data): ...
class ComputerFacade:
def __init__(self):
self.cpu = CPU()
self.memory = Memory()
def start(self):
self.cpu.freeze()
self.memory.load(0, "boot_data")
self.cpu.jump(0)
self.cpu.execute()
Proxy
class RealImage:
def __init__(self, filename: str) -> None:
self.filename = filename
self._load()
def _load(self) -> None:
print(f"Loading {self.filename}")
def display(self) -> None:
print(f"Displaying {self.filename}")
class ImageProxy:
def __init__(self, filename: str) -> None:
self.filename = filename
self._real_image = None
def display(self) -> None:
if self._real_image is None:
self._real_image = RealImage(self.filename) # tembel (lazy) yükleme
self._real_image.display()
Composite
class Component(ABC):
@abstractmethod
def render(self, indent: int = 0) -> str: ...
class Leaf(Component):
def __init__(self, name: str) -> None:
self.name = name
def render(self, indent: int = 0) -> str:
return " " * indent + self.name
class Composite(Component):
def __init__(self, name: str) -> None:
self.name = name
self.children: list[Component] = []
def add(self, component: Component) -> None:
self.children.append(component)
def render(self, indent: int = 0) -> str:
lines = [" " * indent + self.name]
for child in self.children:
lines.append(child.render(indent + 1))
return "\n".join(lines)
Davranışsal (Behavioral) Desenler
Observer
class Subject:
def __init__(self) -> None:
self._observers: list[Callable] = []
def subscribe(self, observer: Callable) -> None:
self._observers.append(observer)
def notify(self, *args, **kwargs) -> None:
for observer in self._observers:
observer(*args, **kwargs)
subject = Subject()
subject.subscribe(lambda event: print(f"Received: {event}"))
subject.notify("user_created")
Strategy
class SortStrategy(ABC):
@abstractmethod
def sort(self, data: list) -> list: ...
class QuickSort(SortStrategy):
def sort(self, data: list) -> list:
return sorted(data) # basitleştirilmiş
class Sorter:
def __init__(self, strategy: SortStrategy) -> None:
self._strategy = strategy
def sort(self, data: list) -> list:
return self._strategy.sort(data)
Pythonic alternatif: bir sınıfa sarmak yerine doğrudan bir fonksiyon geçin:
def sort_data(data: list, strategy: Callable[[list], list] = sorted) -> list:
return strategy(data)
Command
class Command(ABC):
@abstractmethod
def execute(self) -> None: ...
@abstractmethod
def undo(self) -> None: ...
class AddTextCommand(Command):
def __init__(self, document: list[str], text: str) -> None:
self.document = document
self.text = text
def execute(self) -> None:
self.document.append(self.text)
def undo(self) -> None:
self.document.remove(self.text)
State
class State(ABC):
@abstractmethod
def handle(self, context: "TrafficLight") -> None: ...
class RedState(State):
def handle(self, context):
print("Red -> Green")
context.state = GreenState()
class GreenState(State):
def handle(self, context):
print("Green -> Red")
context.state = RedState()
class TrafficLight:
def __init__(self):
self.state: State = RedState()
def change(self):
self.state.handle(self)
Template Method
class DataProcessor(ABC):
def process(self) -> None:
self.load()
self.transform()
self.save()
@abstractmethod
def load(self) -> None: ...
@abstractmethod
def transform(self) -> None: ...
@abstractmethod
def save(self) -> None: ...
class CsvProcessor(DataProcessor):
def load(self): print("Loading CSV")
def transform(self): print("Transforming CSV")
def save(self): print("Saving CSV")
Chain of Responsibility
class Handler(ABC):
def __init__(self) -> None:
self._next: Handler | None = None
def set_next(self, handler: "Handler") -> "Handler":
self._next = handler
return handler
def handle(self, request) -> None:
if self._next:
self._next.handle(request)
class AuthHandler(Handler):
def handle(self, request):
if not request.get("authenticated"):
print("Auth failed")
return
super().handle(request)
Iterator (dilde __iter__/__next__ ile yerleşik olarak bulunur, bkz. Bölüm 8)
21. Yaygın Tuzaklar & Anti-Pattern’ler
- Değiştirilebilir varsayılan argümanlar (Bölüm 6’da ele alındı) — Python’un #1 ayak tuzağı.
- Döngülerde geç bağlama (late binding) closure’lar:
# Hata: tüm lambda'lar aynı `i`'yi (son değeri) yakalar
funcs = [lambda: i for i in range(5)]
print([f() for f in funcs]) # [4, 4, 4, 4, 4]
# Çözüm: varsayılan argüman tanım anındaki değeri yakalar
funcs = [lambda i=i: i for i in range(5)]
print([f() for f in funcs]) # [0, 1, 2, 3, 4]
- Üzerinde iterasyon yaparken bir listeyi değiştirmek:
# Hata: bazı öğeler atlanır
for item in my_list:
if condition(item):
my_list.remove(item)
# Çözüm: bir kopya üzerinde iterasyon yapın veya yeni bir liste oluşturun
my_list = [item for item in my_list if not condition(item)]
- Değer eşitliği için
==yerineisile karşılaştırma (iskimliği kontrol eder, değeri değil — yalnızcaNone,True,Falseve sentinel nesneler için güvenlidir). Exception‘ı çok geniş kapsamda yakalamak, gerçek hataları gizler.- Kompozisyonun daha basit ve esnek olacağı yerlerde kalıtımı aşırı kullanmak.
- Kötü modül yapısından kaynaklanan dairesel import’lar.
- Tip kontrolleri için
isinstance()yerinetype()kullanmak, bu polimorfizmi bozar:
# Kötü
if type(obj) == list:
...
# İyi
if isinstance(obj, list):
...
- Dosyalar/kilitler/bağlantılar için context manager’ları göz ardı etmek, kaynak sızıntısı riski taşır.
- Global değiştirilebilir durum, eşzamanlı bağlamlarda kodu test etmeyi ve anlamayı zorlaştırır.
- Uygun polimorfizm/enum yerine string tabanlı dispatch:
# Kırılgan
if shape_type == "circle":
...
elif shape_type == "square":
...
# Daha iyi: Enum + dispatch dict veya sınıflar aracılığıyla polimorfizm
from enum import Enum, auto
class ShapeType(Enum):
CIRCLE = auto()
SQUARE = auto()
22. Araç Ekosistemi
| Amaç | Araç |
|---|---|
| Biçimlendirme | black, ruff format |
| Linting | ruff, flake8, pylint |
| Statik tipleme | mypy, pyright |
| Import sıralama | isort (veya ruff‘ın yerleşik özelliği) |
| Test | pytest, hypothesis (property-based testing) |
| Kapsam (coverage) | coverage.py |
| Bağımlılık yönetimi | poetry, uv, pip-tools |
| Güvenlik taraması | bandit, pip-audit |
| Pre-commit hook’ları | pre-commit |
| Dokümantasyon | sphinx, mkdocs |
| Veri doğrulama | pydantic |
| Görev çalıştırıcı | nox, tox, make |
Yapılandırılmış araçlarla minimal bir pyproject.toml:
[project]
name = "mypackage"
version = "0.1.0"
requires-python = ">=3.11"
[tool.ruff]
line-length = 88
select = ["E", "F", "I", "UP", "B"]
[tool.mypy]
strict = true
[tool.pytest.ini_options]
testpaths = ["tests"]
23. Son Kontrol Listesi
- Kod
black/ruff formatile biçimlendirildi,ruff/flake8ile lint edildi. - Tüm genel (public) fonksiyonlarda type hint var;
mypy --strictbaşarıyla geçiyor. - Çıplak
except:yok; domain hataları için özel exception hiyerarşisi var. - Değiştirilebilir varsayılan argüman yok.
- Tüm kaynak yönetimi için context manager kullanıldı.
-
loggingmodülü üzerinden loglama yapıldı, kütüphane kodunda aslaprint()yok. -
pytestile testler yazıldı, parametrize edildi, dış bağımlılıklar mock’landı. - Genel API’de tutarlı bir stile (Google/NumPy) uygun docstring’ler var.
-
pyproject.tomlilesrc/layout kullanıldı. - Bağımlılıklar sabitlendi (pinned); düzenli olarak güvenlik taramasından geçirildi.
- Güvenilmeyen veri üzerinde
eval/exec/picklekullanılmadı. - Tasarım desenleri kendileri için değil, karmaşıklığı azalttıkları yerlerde uygulandı — unutmayın: “Python’da zaten first-class fonksiyonlar var; çoğu zaman GoF’un sınıf tabanlı versiyonuna ihtiyacınız yok.”
Bu belge, 2026 yılı başı itibarıyla modern Python (3.10+) deyimlerini yansıtmaktadır. Dil özellikleri gelişmeye devam etmektedir — her zaman hedeflediğiniz Python sürümünün değişiklik günlüğünü (changelog) kontrol edin.