Type Hints ve mypy
Giriş: Yol Tabelası
Python dinamik tipli bir dil. Değişkenin tipini bildirmek zorunda değilsin — Python çalışma zamanında halleder. Bu esneklik harika ama büyük projelerde sorun olabilir. Bir fonksiyonun ne tür parametre beklediğini, ne döndürdüğünü anlamak için kodu okuman gerekir.
Type hints (tip ipuçları) tam olarak bu sorunu çözer. Yol tabelası gibi düşün: "Bu yol İstanbul'a gider" yazıyor. Oraya gitmek zorunda değilsin ama levha sana yardımcı olur.
Type hints de öyle: Python onları zorunlu kılmaz, ama sen ve araçlar (IDE, linter, mypy) için çok faydalıdır.
Temel Syntax
Fonksiyon Type Hints
# Type hint olmadan
def greet(name):
return f"Merhaba, {name}!"
# Type hint ile
def greet(name: str) -> str:
return f"Merhaba, {name}!"
# Birden fazla parametre
def add(a: int, b: int) -> int:
return a + b
# Default değerli parametreler
def power(base: float, exponent: int = 2) -> float:
return base ** exponentSyntax basit:
Parametre sonrasına
: tipFonksiyon sonrasına
-> dönüş_tipi
Değişken Type Hints
# Değişken annotasyonları (Python 3.6+)
name: str = "Ali"
age: int = 25
height: float = 1.75
is_student: bool = True
# Sadece annotasyon (değer atamadan)
score: int # Henüz değer yok, ama tip belirtildiRuntime'da Etkisi: YOK!
Çok önemli bir nokta: type hints runtime'da hiçbir şeyi kontrol etmez. Yanlış tip verse bile Python çalışır:
def add(a: int, b: int) -> int:
return a + b
# Yanlış tip — Python hata VERMEZ!
result = add("hello", " world")
print(result) # "hello world" — Çalışır!
# Type hints sadece DOKÜMANTASYON ve ARAÇLAR içindir⚠️ Dikkat: Type hints Python'u statik tipli yapmaz! Sadece "bu fonksiyon int bekliyor" der. Gerçek tip kontrolü için
mypygibi araçlar kullanmalısın.
Temel Tipler
Basit Tipler
# Temel Python tipleri doğrudan kullanılır
def process(
name: str,
age: int,
height: float,
is_active: bool,
data: bytes,
) -> None: # None = hiçbir şey döndürmez
passNone ve Optional
# None döndüren fonksiyon
def print_message(msg: str) -> None:
print(msg)
# None olabilecek değer: Optional
from typing import Optional
def find_user(user_id: int) -> Optional[str]:
"""Kullanıcıyı bul. Bulunamazsa None döner."""
users = {1: "Ali", 2: "Veli"}
return users.get(user_id) # str veya None
# Python 3.10+ syntax
def find_user(user_id: int) -> str | None:
users = {1: "Ali", 2: "Veli"}
return users.get(user_id)typing Modülü: Karmaşık Tipler
List, Dict, Tuple, Set
from typing import List, Dict, Tuple, Set
# Liste — elemanların tipi
def get_names() -> List[str]:
return ["Ali", "Veli", "Ayşe"]
# Dict — key ve value tipleri
def get_scores() -> Dict[str, int]:
return {"Ali": 90, "Veli": 85}
# Tuple — sabit uzunluk ve tipler
def get_point() -> Tuple[float, float]:
return (3.14, 2.71)
# Değişken uzunluklu tuple
def get_ids() -> Tuple[int, ...]: # ... ile değişken uzunluk
return (1, 2, 3, 4, 5)
# Set
def get_unique_words(text: str) -> Set[str]:
return set(text.split())Python 3.9+: Built-in Generics
Python 3.9'dan itibaren typing.List yerine doğrudan list kullanabilirsin:
# Python 3.9+ — typing import'u gereksiz
def get_names() -> list[str]:
return ["Ali", "Veli", "Ayşe"]
def get_scores() -> dict[str, int]:
return {"Ali": 90, "Veli": 85}
def get_point() -> tuple[float, float]:
return (3.14, 2.71)
def get_unique() -> set[str]:
return {"a", "b", "c"}Union: Birden Fazla Tip
from typing import Union
# int veya float kabul eden fonksiyon
def double(value: Union[int, float]) -> Union[int, float]:
return value * 2
# Python 3.10+ syntax (daha temiz!)
def double(value: int | float) -> int | float:
return value * 2
# Karmaşık Union
def process(data: Union[str, int, List[str]]) -> str:
if isinstance(data, list):
return ", ".join(data)
return str(data)Optional = Union[X, None]
from typing import Optional, Union
# Bu ikisi tamamen aynı
def func1(x: Optional[int]) -> None: pass
def func2(x: Union[int, None]) -> None: pass
def func3(x: int | None) -> None: pass # Python 3.10+Python 3.10+: Modern Syntax
Python 3.10 ile type hint syntax'ı çok daha temiz hale geldi:
# Eski yol (Python 3.9 öncesi)
from typing import List, Dict, Optional, Union, Tuple
def old_style(
names: List[str],
scores: Dict[str, int],
config: Optional[Dict[str, str]],
value: Union[int, float],
) -> Tuple[str, int]:
pass
# Yeni yol (Python 3.10+)
def new_style(
names: list[str],
scores: dict[str, int],
config: dict[str, str] | None,
value: int | float,
) -> tuple[str, int]:
passGördün mü? typing modülünden import'a gerek kalmadı ve | operatörü Union'ı gereksiz kıldı.
Callable, Any, TypeVar
Callable: Fonksiyon Tipi
from typing import Callable
# Fonksiyon parametre olarak alan fonksiyon
def apply(func: Callable[[int, int], int], a: int, b: int) -> int:
return func(a, b)
# Callable[[parametre_tipleri], dönüş_tipi]
result = apply(lambda x, y: x + y, 3, 5)
print(result) # 8
# Callback fonksiyon
def on_complete(callback: Callable[[str], None]) -> None:
callback("İşlem tamamlandı!")
def my_callback(message: str) -> None:
print(message)
on_complete(my_callback)
# Herhangi bir callable (parametre umursamaz)
from typing import Callable
def run(func: Callable[..., None]) -> None:
func()Any: Her Şey
from typing import Any
def log(message: Any) -> None:
"""Herhangi bir şeyi logla."""
print(f"LOG: {message}")
log("text") # OK
log(42) # OK
log([1, 2, 3]) # OKAny kullanmak type hint'in amacını biraz zayıflatır — mümkünse spesifik tip yaz.
TypeVar: Jenerik Tip Değişkeni
from typing import TypeVar, List
T = TypeVar("T")
def first(items: List[T]) -> T:
"""Listenin ilk elemanını döner — tip korunur."""
return items[0]
# int listesi → int döner
num = first([1, 2, 3]) # tip: int
# str listesi → str döner
name = first(["a", "b", "c"]) # tip: str
# Sınırlı TypeVar
Number = TypeVar("Number", int, float)
def add(a: Number, b: Number) -> Number:
return a + bType Alias: Tip Takma Adı
Karmaşık tiplere kısa ad ver:
# Python 3.12+
type Vector = list[float]
type Matrix = list[list[float]]
type UserDict = dict[str, dict[str, str | int]]
# Python 3.9 - 3.11
from typing import TypeAlias
Vector: TypeAlias = list[float]
Matrix: TypeAlias = list[list[float]]
# Önceki versiyonlar
Vector = list[float] # Basit atama da çalışır
# Kullanım
def scale(vector: Vector, factor: float) -> Vector:
return [x * factor for x in vector]
def dot_product(a: Vector, b: Vector) -> float:
return sum(x * y for x, y in zip(a, b))
v1: Vector = [1.0, 2.0, 3.0]
v2: Vector = [4.0, 5.0, 6.0]
print(dot_product(v1, v2)) # 32.0Karmaşık Type Alias
from typing import TypeAlias
# API yanıt tipi
JSONValue: TypeAlias = str | int | float | bool | None | list["JSONValue"] | dict[str, "JSONValue"]
# Veritabanı kayıt tipi
Record: TypeAlias = dict[str, str | int | float | None]
QueryResult: TypeAlias = list[Record]
def fetch_users() -> QueryResult:
return [
{"name": "Ali", "age": 25, "email": "ali@example.com"},
{"name": "Veli", "age": 30, "email": None},
]Generic Types
Kendi generic sınıfını yazabilirsin:
from typing import TypeVar, Generic
T = TypeVar("T")
class Stack(Generic[T]):
"""Tip-güvenli stack."""
def __init__(self) -> None:
self._items: list[T] = []
def push(self, item: T) -> None:
self._items.append(item)
def pop(self) -> T:
if not self._items:
raise IndexError("Stack boş!")
return self._items.pop()
def peek(self) -> T:
if not self._items:
raise IndexError("Stack boş!")
return self._items[-1]
def __len__(self) -> int:
return len(self._items)
# Kullanım
int_stack: Stack[int] = Stack()
int_stack.push(1)
int_stack.push(2)
print(int_stack.pop()) # 2
str_stack: Stack[str] = Stack()
str_stack.push("hello")
# mypy bunu yakalayabilir:
# str_stack.push(42) # Hata: int beklenmiyorduPython 3.12+: Basit Generic Syntax
# Python 3.12+
class Stack[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()
# Fonksiyon generics de basitleşti
def first[T](items: list[T]) -> T:
return items[0]mypy: Statik Tip Kontrolü
mypy, Python kodundaki tip hatalarını çalıştırmadan bulur.
Kurulum ve Kullanım
pip install mypy
mypy my_script.pyÖrnek
# my_script.py
def add(a: int, b: int) -> int:
return a + b
result: str = add(3, 5) # mypy hata verir!
# error: Incompatible types in assignment
# (expression has type "int", variable has type "str")
def greet(name: str) -> str:
return f"Hello, {name}"
greet(42) # mypy hata verir!
# error: Argument 1 to "greet" has incompatible type "int"; expected "str"mypy Konfigürasyonu
# mypy.ini veya pyproject.toml
[mypy]
python_version = 3.12
warn_return_any = True
warn_unused_configs = True
disallow_untyped_defs = True # Tüm fonksiyonlarda type hint zorunluYaygın mypy Kullanım Senaryoları
from typing import Optional
def process(data: Optional[str]) -> str:
# mypy: data None olabilir, .upper() çağırmak tehlikeli
# return data.upper() # error: Item "None" has no attribute "upper"
# Doğru yol: None kontrolü
if data is None:
return ""
return data.upper() # Artık güvenli
# Dict erişimi
config: dict[str, str] = {"host": "localhost"}
port: int = config["port"] # mypy: str'den int'e uyumsuz tip
port = int(config.get("port", "8080")) # DoğruIDE Desteği: Autocompletion ve Hata Tespiti
Type hints'in en pratik faydası IDE desteği:
Autocompletion
def get_user() -> dict[str, str | int]:
return {"name": "Ali", "age": 25}
user = get_user()
# IDE artık user'ın dict olduğunu bilir:
# user. → keys(), values(), items(), get() vs. önerir
def process_names(names: list[str]) -> None:
for name in names:
# IDE artık name'in str olduğunu bilir:
# name. → upper(), lower(), split(), strip() vs. önerir
print(name.upper())Hata Tespiti
def calculate_total(prices: list[float], tax_rate: float) -> float:
subtotal = sum(prices)
return subtotal * (1 + tax_rate)
# IDE uyarı verir:
calculate_total([10, 20, 30], "0.18") # ⚠️ str beklenmiyordu
calculate_total("not a list", 0.18) # ⚠️ str beklenmiyorduRefactoring Güvenliği
Type hints ile IDE, refactoring (yeniden yapılandırma) sırasında tip uyumsuzluklarını yakalayabilir. Bir fonksiyonun dönüş tipini değiştirdiğinde, kullanan tüm yerler otomatik kontrol edilir.
İleri Type Hints
Literal: Sabit Değerler
from typing import Literal
def set_direction(direction: Literal["north", "south", "east", "west"]) -> None:
print(f"Yön: {direction}")
set_direction("north") # OK
# set_direction("up") # mypy hatası!
# HTTP metodları
def request(method: Literal["GET", "POST", "PUT", "DELETE"], url: str) -> None:
passTypedDict: Tip-güvenli Dict
from typing import TypedDict
class UserDict(TypedDict):
name: str
age: int
email: str
def create_user(data: UserDict) -> None:
print(f"Kullanıcı: {data['name']}, {data['age']}")
# Doğru
user: UserDict = {"name": "Ali", "age": 25, "email": "ali@test.com"}
create_user(user)
# mypy uyarır:
# bad: UserDict = {"name": "Ali"} # age ve email eksik!Protocol: Structural Typing
from typing import Protocol
class Drawable(Protocol):
def draw(self) -> None: ...
class Circle:
def draw(self) -> None:
print("○")
class Square:
def draw(self) -> None:
print("□")
# Circle ve Square explicit olarak Drawable'dan türemese de
# draw() metodu olduğu için Protocol'ü sağlar
def render(shape: Drawable) -> None:
shape.draw()
render(Circle()) # ○
render(Square()) # □Final ve Override
from typing import final, override
class Base:
def process(self) -> str:
return "base"
@final
def critical(self) -> str:
"""Bu metod override EDİLEMEZ."""
return "sabit"
class Child(Base):
@override
def process(self) -> str:
"""Üst sınıftaki metodu override ediyorum."""
return "child"
# @override
# def processs(self) -> str: # Typo! mypy yakalar
# return "child"Type Hints Best Practices
1. Kademeli Ekle
# Hepsini birden eklemeye çalışma
# Önce public API'yi annotate et
# 1. Adım: Fonksiyon imzaları
def calculate_total(items: list[dict]) -> float: ...
# 2. Adım: Detaylandır
def calculate_total(items: list[dict[str, float]]) -> float: ...
# 3. Adım: TypedDict ile
class Item(TypedDict):
name: str
price: float
quantity: int
def calculate_total(items: list[Item]) -> float: ...2. Fazla Spesifik Olma
# ❌ Çok kısıtlayıcı
def process(data: list[int]) -> list[int]: ...
# ✅ Daha esnek (Iterable kabul et)
from typing import Iterable
def process(data: Iterable[int]) -> list[int]: ...3. Dönüş Tipini Belirt
# ❌ Dönüş tipi belirsiz
def parse(text):
return text.split(",")
# ✅ Dönüş tipi belli
def parse(text: str) -> list[str]:
return text.split(",")💡 İpucu: Type hints eklemek zaman alır ama büyük projelerde geri dönüşü çok yüksek. Hata yakalama, dokümantasyon ve IDE desteği — hepsi bedava gelir.
Gerçek Dünya Örnekleri
REST API Response Typing
from typing import TypedDict, Literal
class APIError(TypedDict):
code: int
message: str
class UserResponse(TypedDict):
id: int
name: str
email: str
role: Literal["admin", "user", "guest"]
class PaginatedResponse(TypedDict):
data: list[UserResponse]
total: int
page: int
per_page: int
def fetch_users(page: int = 1, per_page: int = 20) -> PaginatedResponse:
"""API'den kullanıcıları çek — dönüş tipi açık ve net."""
# Gerçek API çağrısı yerine simülasyon
users: list[UserResponse] = [
{"id": 1, "name": "Ali", "email": "ali@test.com", "role": "admin"},
{"id": 2, "name": "Veli", "email": "veli@test.com", "role": "user"},
]
return {
"data": users,
"total": len(users),
"page": page,
"per_page": per_page,
}
# IDE artık response.data'nın list[UserResponse] olduğunu bilir
response = fetch_users()
for user in response["data"]:
print(f"{user['name']} ({user['role']})")Decorator ile Type Hints
from typing import TypeVar, Callable, ParamSpec
from functools import wraps
import time
P = ParamSpec("P")
R = TypeVar("R")
def timer(func: Callable[P, R]) -> Callable[P, R]:
"""Fonksiyonun çalışma süresini ölçen decorator — tip bilgisi korunur."""
@wraps(func)
def wrapper(*args: P.args, **kwargs: P.kwargs) -> R:
start = time.perf_counter()
result = func(*args, **kwargs)
elapsed = time.perf_counter() - start
print(f"⏱️ {func.__name__}: {elapsed:.4f}s")
return result
return wrapper
@timer
def calculate_sum(numbers: list[int]) -> int:
return sum(numbers)
# IDE artık calculate_sum'ın list[int] alıp int döndüğünü bilir
result = calculate_sum([1, 2, 3, 4, 5])Configuration Class
from dataclasses import dataclass, field
from typing import Literal
@dataclass
class DatabaseConfig:
host: str = "localhost"
port: int = 5432
name: str = "mydb"
user: str = "admin"
password: str = ""
pool_size: int = 10
ssl: bool = False
@dataclass
class AppConfig:
debug: bool = False
log_level: Literal["DEBUG", "INFO", "WARNING", "ERROR"] = "INFO"
secret_key: str = ""
allowed_hosts: list[str] = field(default_factory=lambda: ["localhost"])
database: DatabaseConfig = field(default_factory=DatabaseConfig)
def create_app(config: AppConfig) -> None:
"""Uygulama oluştur — config'in yapısı tamamen belirli."""
print(f"Debug: {config.debug}")
print(f"DB: {config.database.host}:{config.database.port}")
print(f"Log: {config.log_level}")
# IDE tüm config alanlarını bilir ve autocomplete yapar
config = AppConfig(
debug=True,
database=DatabaseConfig(host="db.prod.com", port=5433),
)
create_app(config)Overload: Farklı Parametrelere Göre Farklı Dönüş
from typing import overload, Literal
@overload
def parse(data: str, format: Literal["json"]) -> dict: ...
@overload
def parse(data: str, format: Literal["csv"]) -> list[list[str]]: ...
@overload
def parse(data: str, format: Literal["text"]) -> str: ...
def parse(data: str, format: str) -> dict | list | str:
"""Format'a göre farklı tip döner — mypy bunu bilir."""
if format == "json":
import json
return json.loads(data)
elif format == "csv":
return [line.split(",") for line in data.strip().split("\n")]
else:
return data
# mypy bilir: result1 dict tipinde
result1 = parse('{"key": "value"}', "json")
# mypy bilir: result2 list[list[str]] tipinde
result2 = parse("a,b\nc,d", "csv")Yaygın Hatalar
1. Mutable Default Argüman
# ❌ Klasik Python tuzağı
def append_to(item: str, items: list[str] = []) -> list[str]:
items.append(item)
return items
# ✅ None kullan
def append_to(item: str, items: list[str] | None = None) -> list[str]:
if items is None:
items = []
items.append(item)
return items2. Forward Reference
# Sınıf henüz tanımlanmadan kendisine referans
class Node:
def __init__(self, value: int, next: "Node | None" = None) -> None:
self.value = value
self.next = next
# Python 3.10+ ile annotations import'u
from __future__ import annotations
class Node:
def __init__(self, value: int, next: Node | None = None) -> None:
self.value = value
self.next = next3. Any'yi Aşırı Kullanmak
# ❌ Her yerde Any: type hint'in anlamı kalmadı
from typing import Any
def process(data: Any) -> Any: ...
# ✅ Spesifik tip yaz veya TypeVar kullan
from typing import TypeVar
T = TypeVar("T")
def process(data: T) -> T: ...Özet
Type hints, Python'da tip bilgisi eklemenin isteğe bağlı yoludur. Runtime'da kontrol edilmez, sadece dokümantasyon ve araçlar için geçerlidir.
Temel syntax:
parametre: tipve-> dönüş_tipi. Basit tipler (int,str,float,bool) doğrudan kullanılır.`typing` modülü karmaşık tipler için
Optional,Union,Callable,TypeVargibi araçlar sunar. Python 3.10+ ile|operatörüUnion'ı gereksiz kılar.Python 3.9+ ile
list[str],dict[str, int]gibi built-in generics doğrudan kullanılabilir;typing.Listartık gerekli değildir.`mypy`, statik tip kontrolü yapan araçtır. Kodu çalıştırmadan tip hatalarını bulur — büyük projelerde hayat kurtarır.
Type hints'in en pratik faydası IDE desteği: autocompletion, hata tespiti ve güvenli refactoring sağlar.
AI Asistan
Sorularını yanıtlamaya hazır