Python'da tip notları (type hints), bir değişkenin, parametrenin veya dönüş değerinin hangi tipte olmasının beklendiğini x: int ve -> str gibi işaretlerle belirtmenizi sağlar. En önemli kural şudur: Python yorumlayıcısı bu notları çalışma zamanında zorlamaz. typing belgesinin en başındaki not da bunu söyler: notlar tip denetleyicileri, IDE'ler ve linter'lar gibi üçüncü taraf araçlar tarafından kullanılır. Yani tip notları programın davranışını değiştirmez, hataları kod çalışmadan önce yakalamanıza ve kodu belgelemenize yardım eder.
Bu yazıda temel sözdizimini, yerleşik koleksiyonlarla list[int] yazımını, X | None birleşimlerini, 3.12 ile gelen type deyimi ve generic sözdizimini, TypedDict, Protocol, NewType, Literal gibi araçları ve Python 3.14'te değişen ertelenmiş annotation değerlendirmesini ele alıyoruz. Örnekler Python 3.15.2 ile çalıştırıldı. Serinin bütünü için Python öğrenme rehberi sayfasına bakabilirsiniz.
Temel sözdizimi ve "zorlanmaz" kuralı

Parametreden sonra iki nokta üst üste ve tip, parametre listesinden sonra -> ve dönüş tipi yazılır. Değişkenler için de stok: dict[str, int] = {} biçimi kullanılır. Aşağıdaki örnekte kare fonksiyonu int beklediğini söylüyor, ama 2.5 verdiğimizde Python hiçbir itirazda bulunmadan 6.25 döndürüyor. Notlar fonksiyonun __annotations__ özniteliğinde saklanır; tip denetleyicisi ise bu çağrıyı, kod çalışmadan önce, hata olarak işaretler.
def kare(x: int) -> int:
return x * x
print(kare(3))
print(kare(2.5))
print(kare.__annotations__)
# çıktı: 9
# çıktı: 6.25
# çıktı: {'x': <class 'int'>, 'return': <class 'int'>}Bu davranış hem güç hem sorumluluktur. Dış dünyadan (kullanıcı girdisi, JSON, veritabanı) gelen verinin tipini çalışma zamanında gerçekten doğrulamanız gerekiyorsa, tip notuna güvenmek yerine isinstance() kontrolü yapmanız ya da bir doğrulama katmanı kullanmanız gerekir. Değişkenlerin ve tiplerin genel mantığı için Python değişkenler ve veri tipleri yazısına bakabilirsiniz.
Koleksiyonlar, birleşimler ve None
Python 3.9'dan beri (PEP 585) yerleşik list, dict, tuple, set gibi sınıflar köşeli parantezle parametrelenebilir: list[int], dict[str, float]. Eski typing.List, typing.Dict gibi takma adlar 3.9'dan beri kullanımdan kaldırılmış (deprecated) durumdadır; belgeye göre şimdilik kaldırılmaları planlanmıyor ve yorumlayıcı uyarı vermiyor, ama yeni kodda yerleşik adları kullanmanız önerilir.
Tuple'lar listelerden farklı not edilir: tuple[int, int] tam olarak iki tamsayılık bir tuple anlamına gelir, tuple[str, ...] ise herhangi uzunlukta str tuple'ıdır. Liste ise tek tip argüman alır; list[int, str] yazmak tip denetleyicisinde hatadır. Birden fazla olası tip için Python 3.10'dan beri int | str yazabilirsiniz; Optional[X] da X | None ile eşdeğerdir.
def ortalama(notlar: list[float]) -> float | None:
if not notlar:
return None
return sum(notlar) / len(notlar)
stok: dict[str, int] = {'elma': 3}
nokta: tuple[int, int] = (1, 2)
etiketler: tuple[str, ...] = ('a', 'b', 'c')
print(ortalama([70, 80, 90]))
print(ortalama([]))
print(list[int], int | None)
print(isinstance(5, int | str))
# çıktı: 80.0
# çıktı: None
# çıktı: list[int] int | None
# çıktı: TrueBelge Optional ile ilgili önemli bir ayrımı da vurgular: "isteğe bağlı argüman" (varsayılan değeri olan parametre) ile "None olabilen değer" aynı şey değildir. def f(arg: int = 0) parametresi isteğe bağlıdır ama None kabul etmez, bu yüzden Optional gerekmez. None açıkça izinli bir değerse int | None yazmak doğrudur. Fonksiyon gövdesinde böyle bir değeri kullanmadan önce if deger is None kontrolü yapmanız, tip denetleyicisinin tipi daraltmasını (narrowing) sağlar.
Python 3.14: Union tek bir tipte birleşti
Python 3.14 What's New belgesine göre typing.Union ile types.UnionType artık birbirinin takma adıdır. Hem Union[int, str] hem int | str aynı çalışma zamanı tipini üretir ve repr() çıktısı her ikisi için int | str olur. Eski yazımla üretilen birleşimler artık önbelleğe alınmadığı için iki birleşimi is ile değil, == ile karşılaştırmanız gerekir.
from typing import Union
try:
isinstance([1, 2], list[int])
except TypeError as e:
print(type(e).__name__ + ':', e)
print(repr(Union[int, str]))
print(Union[int, str] == (int | str))
# çıktı: TypeError: isinstance() argument 2 cannot be a parameterized generic
# çıktı: int | str
# çıktı: TrueÖrneğin ilk satırı başka bir kurala dikkat çekiyor: isinstance() ikinci argüman olarak parametrelenmiş generic kabul etmez. Bir değerin liste olduğunu kontrol edebilirsiniz, ama "tamsayı listesi" olduğunu tek bir isinstance çağrısıyla kontrol edemezsiniz; öğeleri ayrıca dolaşmanız gerekir.
type deyimi ve generic sözdizimi (3.12+)
Karmaşık tipleri tekrar tekrar yazmamak için tip takma adı tanımlarsınız. Güncel typing belgesi bunu type deyimiyle anlatır: type Koordinat = tuple[float, float] satırı bir TypeAliasType örneği oluşturur ve tip denetleyicileri bu adı hedef tiple eşdeğer kabul eder. Takma adın değeri tembel değerlendirilir; gerçek değere __value__ ile ulaşılır.
Python 3.12 ile generic fonksiyon ve sınıflar için köşeli parantezli tip parametresi sözdizimi geldi: def ilk[T](ogeler: list[T]) -> T yazımı, "hangi tipte liste verirseniz o tipte öğe dönecek" demektir. Sınıflar için de class Kutu[T]: yazılır ve böyle bir sınıf örtük olarak Generic sınıfından türer. 3.11 ve öncesiyle uyumluluk gerekiyorsa TypeVar ve Generic[T] ile eski yazım hâlâ geçerlidir.
type Koordinat = tuple[float, float]
def ilk[T](ogeler: list[T]) -> T:
return ogeler[0]
class Kutu[T]:
def __init__(self, icerik: T) -> None:
self.icerik = icerik
def al(self) -> T:
return self.icerik
print(ilk([3, 4]))
print(Kutu('kitap').al())
print(type(Koordinat).__name__, Koordinat.__value__)
print(ilk.__type_params__)
print(Kutu[int])
# çıktı: 3
# çıktı: kitap
# çıktı: TypeAliasType tuple[float, float]
# çıktı: (T,)
# çıktı: __main__.Kutu[int]Tip parametresine sınır da verebilirsiniz: [T: Sequence[bytes]] T'nin bir bayt dizisi alt tipi olmasını, [S: (int, str)] ise yalnızca bu iki tipten biri olmasını ister. Generic yapıların yanında soyut koleksiyon tiplerini (Sequence, Mapping, Iterable) tercih etmek de iyi bir alışkanlıktır: belge, argümanları not ederken somut dict yerine Mapping gibi soyut tipleri önerir; böylece fonksiyonunuz daha geniş bir girdi yelpazesini kabul eder.
Any ve object: kaçış kapısı ile güvenli genellik

Any özel bir tiptir: tip denetleyicisi her tipi Any'ye, Any'yi de her tipe atanabilir kabul eder ve Any tipli bir değer üzerinde yapılan hiçbir işlemi denetlemez. Not yazılmamış parametreler ve dönüş değerleri de örtük olarak Any sayılır. Bu, dinamik ve statik tipli kodu karıştırırken işe yarayan bir kaçış kapısıdır.
object ise ilk bakışta benzer görünse de tam tersini yapar: her tip object'in alt tipidir, ama object tipli bir değer üzerinde denetleyici neredeyse hiçbir işleme izin vermez. Belgenin özeti şöyledir: değer "herhangi bir şey olabilir" ve bunu güvenli biçimde ifade etmek istiyorsanız object, değer gerçekten dinamik tipliyse Any kullanın.
TypedDict: belirli anahtarları olan sözlükler
JSON verisi gibi sabit anahtarlı sözlükleri not etmek için TypedDict kullanılır. Çalışma zamanında bu nesneler sıradan dict'tir; anahtar ve değer tipleri yalnızca tip denetleyicisi tarafından kontrol edilir. Varsayılan olarak bütün anahtarlar zorunludur; tek tek anahtarları NotRequired ile isteğe bağlı yapabilir ya da sınıfa total=False vererek hepsini isteğe bağlı hale getirebilirsiniz.
from typing import TypedDict, NotRequired
class Kullanici(TypedDict):
ad: str
yas: int
eposta: NotRequired[str]
k: Kullanici = {'ad': 'Ali', 'yas': 30}
print(type(k).__name__, k)
print(sorted(Kullanici.__required_keys__), sorted(Kullanici.__optional_keys__))
# çıktı: dict {'ad': 'Ali', 'yas': 30}
# çıktı: ['ad', 'yas'] ['eposta']Python 3.15 ile gelen PEP 728, TypedDict sınıflarına closed ve extra_items argümanlarını ekledi. closed=True tanımlı anahtarlar dışında anahtar kabul etmeyen kapalı bir sözlük tipi tanımlar; extra_items=str ise ek anahtarlara izin verir ama değerlerinin belirtilen tipte olmasını ister. Bu kurallar da yalnızca tip denetleyicisi içindir; çalışma zamanında sözlük yine sözlüktür. Sözlüklerin kendisi için Python sözlük (dict) metotları yazısına bakabilirsiniz.
from typing import TypedDict
class Nokta(TypedDict, closed=True):
x: int
y: int
class Etiketli(TypedDict, extra_items=str):
ad: str
p: Nokta = {'x': 1, 'y': 2}
e: Etiketli = {'ad': 'sunucu', 'bolge': 'eu'}
print(p, e)
print(Nokta.__closed__, Etiketli.__extra_items__)
# çıktı: {'x': 1, 'y': 2} {'ad': 'sunucu', 'bolge': 'eu'}
# çıktı: True <class 'str'>Protocol: yapısal alt tipleme (statik duck typing)
Python'un ilk tip sistemi (PEP 484) "nominal" alt tiplemeye dayanıyordu: bir sınıfın beklenen tipe uyması için ondan açıkça türemesi gerekiyordu. PEP 544 ile gelen Protocol, yapısal alt tiplemeyi mümkün kılar: bir sınıf, protokoldeki yöntemlere sahipse ondan türemeden de o protokole uyar. Bu, Python'un "ördek gibi yürüyorsa ördektir" yaklaşımının statik karşılığıdır.
from typing import Protocol, runtime_checkable
@runtime_checkable
class Kapatilabilir(Protocol):
def close(self) -> None: ...
class Baglanti:
def close(self) -> None:
print('bağlantı kapatıldı')
def kapat(nesne: Kapatilabilir) -> None:
nesne.close()
kapat(Baglanti())
print(isinstance(Baglanti(), Kapatilabilir))
print(isinstance(42, Kapatilabilir))
# çıktı: bağlantı kapatıldı
# çıktı: True
# çıktı: False@runtime_checkable ile işaretlenen protokoller isinstance() içinde de kullanılabilir; ancak belge bu kontrolün yalnızca gerekli özniteliklerin var olup olmadığına baktığını, imzalarını ve tiplerini denetlemediğini açıkça belirtir. Ayrıca bu özellik kalıtımla geçmez: çalışma zamanında denetlenebilir bir protokolden türeyen alt protokol, kendisi de işaretlenmedikçe öyle sayılmaz. Python 3.15'te, işaretlenmemiş böyle bir alt protokolle isinstance() ya da issubclass() çağırmak kullanımdan kaldırıldı ve belgeye göre Python 3.20'de TypeError verecek. Sınıflar ve kalıtım için Python kalıtım, super ve MRO yazısına bakabilirsiniz.
NewType, Literal, Final ve Callable
NewType, tip denetleyicisinin ayrı bir tip gibi davrandığı ama çalışma zamanında orijinal tipe dönüşen bir yardımcıdır: KullaniciId(42) ifadesinin sonucu düz bir int'tir. Böylece kullanıcı kimliğiyle sipariş kimliğini karıştırmak gibi mantık hatalarını denetleyici yakalar. Literal yalnızca belirli değerlere izin verir, Final ise bir adın yeniden atanmaması gerektiğini bildirir. Bu kısıtların hiçbiri çalışma zamanında zorlanmaz.
from typing import NewType, Literal, Final
KullaniciId = NewType('KullaniciId', int)
u = KullaniciId(42)
print(u, type(u).__name__)
Yon = Literal['kuzey', 'guney']
def git(yon: Yon) -> str:
return f'{yon} yönüne gidiliyor'
print(git('kuzey'))
SINIR: Final = 100
print(SINIR)
# çıktı: 42 int
# çıktı: kuzey yönüne gidiliyor
# çıktı: 100Fonksiyon alan ya da döndüren fonksiyonlar için collections.abc.Callable kullanılır: Callable[[int], int] "bir int alıp int döndüren çağrılabilir" demektir. Generator fonksiyonlarının dönüş tipi genellikle Iterator[int] ya da daha ayrıntılı Generator[...] ile not edilir; generator'lar Python generator ve yield yazısında anlatılıyor. Decorator'larda imzayı korumak için ParamSpec kullanılır.
from collections.abc import Callable, Iterator
def uygula(fonk: Callable[[int], int], deger: int) -> int:
return fonk(deger)
def sayac(n: int) -> Iterator[int]:
yield from range(n)
print(uygula(lambda x: x + 1, 41))
print(list(sayac(3)))
# çıktı: 42
# çıktı: [0, 1, 2]Python 3.14: annotation'lar artık ertelenerek değerlendiriliyor
Python 3.14 What's New belgesine göre (PEP 649 ve PEP 749), fonksiyon, sınıf ve modüllerdeki annotation'lar artık tanım anında hemen değerlendirilmiyor. Bunun yerine özel "annotate" fonksiyonlarında saklanıyor ve yalnızca gerektiğinde değerlendiriliyor (from __future__ import annotations kullanılan dosyalar hariç). Bunun en görünür sonucu şudur: henüz tanımlanmamış bir sınıfa atıf yapan notları (forward reference) artık tırnak içine almanız gerekmiyor.
Yeni annotationlib modülü notları üç biçimde okumanızı sağlar: VALUE biçimi notları gerçek değerlere çevirir (eski davranışa benzer) ve tanımsız bir ad varsa NameError verir; FORWARDREF tanımsız adları özel işaretçilerle değiştirir; STRING ise notları metin olarak döndürür. Aşağıdaki örnekte Agac.ekle metodu, kendisinden sonra tanımlanan Dugum sınıfına tırnaksız atıf yapıyor ve sorun çıkmıyor.
from annotationlib import get_annotations, Format
class Agac:
def ekle(self, cocuk: Dugum) -> None: ...
class Dugum:
pass
print(get_annotations(Agac.ekle))
def f(x: Tanimsiz) -> int: ...
print(get_annotations(f, format=Format.STRING))
try:
get_annotations(f)
except NameError as e:
print(type(e).__name__ + ':', e)
# çıktı: {'cocuk': <class '__main__.Dugum'>, 'return': None}
# çıktı: {'x': 'Tanimsiz', 'return': 'int'}
# çıktı: NameError: name 'Tanimsiz' is not definedAnnotations Best Practices belgesi, 3.10 ile 3.13 arasında notlara erişmenin en iyi yolunun inspect.get_annotations() olduğunu, 3.14 ve sonrasında ise bunun yerini annotationlib.get_annotations() fonksiyonunun aldığını söyler. Bilinmeyen bir nesnenin notlarına doğrudan __annotations__ ile erişmek gerekiyorsa üç argümanlı getattr(o, '__annotations__', None) önerilir. typing.get_type_hints() ise notları çözümlenmiş tiplerle döndüren klasik yardımcıdır.
from typing import Any, get_type_hints
def isle(veri: Any, sayi: int = 0) -> str:
return str(veri)
print(get_type_hints(isle))
# çıktı: {'veri': typing.Any, 'sayi': <class 'int'>, 'return': <class 'str'>}Python 3.15'te typing
3.15 What's New belgesi typing için birkaç yenilik listeler. PEP 747 ile gelen TypeForm, değerin kendisi bir tip ifadesi olduğunda (örneğin bir fonksiyona int veya list[int] geçirildiğinde) bunu kesin biçimde not etmeyi sağlar; çalışma zamanında TypeForm(x) yalnızca x döndürür. PEP 800 ile gelen @typing.disjoint_base, yerleşik ya da derlenmiş tiplerin çoklu kalıtım kısıtlarını tip denetleyicilerine aktarmak için kullanılan ileri bir decorator'dır. TypeVarTuple artık bound ve varyans argümanlarını kabul ediyor.
Aynı sürümde kaldırılanlar da var: NamedTuple sınıflarını anahtar kelime argümanlarıyla oluşturan belgelenmemiş yazım (NamedTuple("Point", x=int, y=int)) artık desteklenmiyor; alansız TypedDict("TD") yazımı da kaldırıldı, yerine class TD(TypedDict): pass ya da TypedDict("TD", {}) kullanılmalı. Sürümün tamamı için Python 3.15 yenilikleri yazısına bakabilirsiniz.
Tip denetleyicisi kullanmak
Tip notlarından gerçek fayda görmek için kodunuzu bir statik tip denetleyicisiyle çalıştırmanız gerekir. Standart kütüphanede bir tip denetleyicisi yoktur; typing belgesi topluluk tarafından geliştirilen araçlara (örneğin mypy belgelerine) ve Python tip sistemi spesifikasyonuna yönlendirir. Bu araçlar üçüncü taraf paketlerdir ve sanal ortama venv ve pip ile kurulur. Çoğu modern editör de aynı bilgiyi otomatik tamamlama ve satır içi uyarılar için kullanır. Yeni typing özelliklerini eski Python sürümlerinde kullanmak isterseniz belge, bu özelliklerin geri taşındığı typing_extensions paketini anar.
Sık yapılan hatalar
![Sık yapılan hatalar: Notların çalışma zamanında zorlandığını sanmak, Optional'ı varsayılan değerle karıştırmak, list[int] ile isinstance, Union'ları is ile karşılaştırmak](/assets/blog/python-type-hints-typing-b11.webp)
- Notların çalışma zamanında zorlandığını sanmak:
def f(x: int)fonksiyonuna str geçirirseniz Python hata vermez. Doğrulama gerekiyorsa açıkça yapın. - Optional'ı varsayılan değerle karıştırmak: Varsayılan değeri olan parametre,
Nonekabul etmiyorsaOptionalgerektirmez. - list[int] ile isinstance: Parametrelenmiş generic'ler
isinstance()içinde kullanılamaz;TypeErrorverir. - Union'ları is ile karşılaştırmak: 3.14'ten beri eski yazımlı birleşimler önbelleğe alınmıyor;
==kullanın. - runtime_checkable'ın imzayı denetlediğini sanmak: Yalnızca özniteliğin varlığına bakılır.
- Her yere Any yazmak:
Anydenetimi kapatır. Gerçekten bilinmeyen değerler içinobjectdaha güvenlidir. - Çalışma zamanında __annotations__ okuyup her şeyin değer olduğunu varsaymak: 3.14'ten beri notlar tembel değerlendiriliyor; tanımsız adlar varsa
annotationlibbiçimlerini kullanın.
Sık Sorulan Sorular
Tip notları programı yavaşlatır mı?
Notlar çalışma zamanında zorlanmadığı için çağrı başına bir denetim maliyeti yoktur. Python 3.14 What's New belgesi, ertelenmiş değerlendirmenin notları tanımlamanın çalışma zamanı maliyetini en aza indirdiğini belirtir.
Küçük betiklerde tip notu yazmalı mıyım?
Zorunlu değildir; not yazılmamış kod örtük olarak Any kabul edilir ve sorunsuz çalışır. Notlar özellikle fonksiyonların başkaları tarafından kullanıldığı, kodun büyüdüğü ve editör desteğinin önem kazandığı projelerde fayda sağlar. Kademeli olarak, önce dışa açık fonksiyonların imzalarından başlayarak ekleyebilirsiniz.
dataclass ile TypedDict arasında nasıl seçim yaparım?
TypedDict çalışma zamanında sıradan bir sözlüktür; JSON gibi zaten sözlük biçiminde gelen veriyi not etmek için uygundur. Davranışı (metotları) olan, öznitelik erişimiyle kullanılacak nesneler için Python dataclasses daha uygundur.
from __future__ import annotations hâlâ gerekli mi?
3.14 ve sonrasında forward reference için genellikle gerekmez, çünkü notlar zaten ertelenerek değerlendirilir. 3.7'de PEP 563 ile gelen bu ifade bütün notları metne çevirir ve 3.14'te davranışı değişmedi. Ancak What's New belgesine göre artık kullanımdan kaldırılmış durumda ve gelecekteki bir sürümde kaldırılması bekleniyor; bu, ertelenmiş değerlendirmeyi desteklemeyen son sürüm olan 3.13'ün 2029'daki destek sonundan önce olmayacak. Yalnızca 3.14 ve sonrasını destekliyorsanız bu satırı büyük olasılıkla kaldırabilirsiniz; notları okuyan üçüncü taraf kütüphanelerin de buna uyumlu olması gerekir.
Marka adları sahiplerine aittir. Bu yazı bağımsız bir teknik rehberdir; adı geçen kuruluşlarla bayilik veya ortaklık ilişkisi bulunduğu anlamına gelmez.
İlgili içerikler: Python öğrenme rehberi · dataclasses · Class ve nesne · Decorator yazma · Fonksiyon tanımlama · Python 3.15 yenilikleri