← Kursa Dön
📄 Text · 15 min

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 ** exponent

Syntax basit:

  • Parametre sonrasına : tip

  • Fonksiyon 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 belirtildi

Runtime'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 mypy gibi 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
    pass

None 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]:
    pass

Gö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])  # OK

Any 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 + b

Type 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.0

Karmaşı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 beklenmiyordu

Python 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 zorunlu

Yaygı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ğru

IDE 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 beklenmiyordu

Refactoring 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:
    pass

TypedDict: 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 items

2. 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 = next

3. 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: tip ve -> 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, TypeVar gibi 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.List artı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.