- Fonksiyonlara ve değişkenlere tip ipucu yazmak ve bunların çalışma zamanında denetlenmediğini açıklamak
Callable,Literal, generic fonksiyonlar veProtocolile karmaşık tipleri tanımlamak@dataclass,field(default_factory=...)ve__post_init__ile bir veri sınıfı yazmakfrozen,orderveslotsseçeneklerini yerinde kullanmak
def price(total, discount) fonksiyonuna bak: discount 0,1 mi yoksa 10 mu? total None olabilir mi? Küçük bir betikte bunu hatırlarsın; beş geliştiricinin çalıştığı 50 000 satırlık bir projede ise hatırlamazsın. Tip ipuçları (type hints) cevabı kodun kendisine yazar: def price(total: float, discount: float) -> float. Düzenleyiciler bunları otomatik tamamlama için kullanır, mypy gibi denetleyiciler de hataları program daha çalışmadan bulur. Dataclass'lar aynı ipuçları üzerine kuruludur ve bir sınıfın sıkıcı kısımlarını senin yerine yazar.
Fonksiyonlara ve değişkenlere ipucu yazmak
İpucu iki noktadan sonra, dönüş tipi ise -> okundan sonra yazılır. Python 3.9'dan beri yerleşik koleksiyonlar doğrudan parametrelendirilebilir: list[int], dict[str, float], tuple[int, int], set[str]. Python 3.10'dan beri birleşim | ile yazılır: int | None, bir tam sayı ya da None demektir. Değişkenlere de ipucu yazılabilir: count: int = 0.
def average(scores: list[float], precision: int = 1) -> float:
return round(sum(scores) / len(scores), precision)
print(average([4.5, 5, 3.5]))
print(average.__annotations__)
print(average((1, 2)))▸ Beklenen çıktı
4.3
{'scores': list[float], 'precision': <class 'int'>, 'return': <class 'float'>}
1.5Son satır önemli: ipucunda list[float] yazmasına rağmen (1, 2) demeti kabul edildi. Python, tip ipuçlarını çalışma zamanında denetlemez. İpuçları __annotations__ içinde saklanır ve araçlar tarafından okunur. Hataları gerçekten bulmak için statik bir tip denetleyicisi çalıştır:
$ mypy shop.py
shop.py:12: error: Argument 1 to "average" has incompatible type "str"; expected "list[float]" [arg-type]
Found 1 error in 1 file (checked 1 source file)pip install mypy ile kurulur ve dosyayı çalıştırmadan inceler.typing modülünün araçları
Callable[[int], int]— birintalıp birintdöndüren fonksiyon.Literal['r', 'w']— yalnızca tam olarak bu değerlere izin verilir.type Vector = list[float](Python 3.12+) — tip takma adı: uzun bir tip için kısa bir ad.def first[T](items: list[T]) -> T(Python 3.12+) — generic (genel) fonksiyon:Tbir tip değişkenidir; bu yüzden birlist[str]için sonuçstrolur. Eski sürümlerde bu,T = TypeVar('T')ile yazılır.Any— denetimi kapatır; olabildiğince az kullan.
from typing import Callable, Literal
type Vector = list[float]
Mode = Literal['r', 'w']
def scale(v: Vector, k: float) -> Vector:
return [k * x for x in v]
def first[T](items: list[T], default: T | None = None) -> T | None:
return items[0] if items else default
def apply(func: Callable[[int], int], value: int) -> int:
return func(value)
def describe(mode: Mode) -> str:
return 'read' if mode == 'r' else 'write'
print(scale([1.0, 2.5], 2))
print(first(['Aysel', 'Murad']), first([], default='nobody'))
print(apply(lambda n: n * n, 7))
print(describe('w'))▸ Beklenen çıktı
[2.0, 5.0] Aysel nobody 49 write
Python'un felsefesi ördek tiplemesidir (duck typing): bir nesnede ihtiyacın olan metot varsa o nesne işini görür. typing.Protocol bu fikri tip sistemine taşır. Bir protokol, nesnenin neler yapabildiğini tanımlar; doğru metotlara sahip her sınıf, ondan kalıtım almadan otomatik olarak ona uyar. Buna yapısal tipleme denir:
from typing import Protocol
class HasArea(Protocol):
def area(self) -> float: ...
class Square:
def __init__(self, side: float) -> None:
self.side = side
def area(self) -> float:
return self.side ** 2
class Circle:
def __init__(self, r: float) -> None:
self.r = r
def area(self) -> float:
return 3.14159 * self.r ** 2
def total_area(shapes: list[HasArea]) -> float:
return sum(s.area() for s in shapes)
print(round(total_area([Square(2), Circle(1)]), 2))▸ Beklenen çıktı
7.14
Square ve Circle, HasArea hakkında hiçbir şey bilmez; yine de tip denetleyicisi ikisini de bir list[HasArea] içinde kabul eder, çünkü her birinin area() -> float metodu vardır. area metodunu unutan bir sınıf ise program çalışmadan önce hata olarak bildirilir.
dataclasses: basmakalıp kod olmadan sınıflar
Esas olarak veri saklayan bir sınıfın, argümanları niteliklere kopyalayan bir __init__'e, okunaklı bir __repr__'a ve alan alan karşılaştıran bir __eq__'e ihtiyacı vardır. Bunları elle yazmak uzun sürer ve hataya açıktır. **@dataclass dekoratörü, ipucu yazılmış sınıf niteliklerini (alanları**) okur ve bu metotları senin için üretir:
from dataclasses import dataclass, field
@dataclass
class Student:
name: str
grade: int
scores: list[int] = field(default_factory=list)
def average(self) -> float:
return sum(self.scores) / len(self.scores) if self.scores else 0.0
a = Student('Aysel', 9, [5, 4, 5])
b = Student('Aysel', 9, [5, 4, 5])
print(a)
print(a == b, a is b)
print(round(a.average(), 2))
print(Student('Murad', 8))▸ Beklenen çıktı
Student(name='Aysel', grade=9, scores=[5, 4, 5]) True False 4.67 Student(name='Murad', grade=8, scores=[])
a == b True'dur, çünkü üretilen __eq__ alanları karşılaştırır; a is b ise False'tur, çünkü bunlar iki ayrı nesnedir. scores alanı, field(default_factory=list) sayesinde Murad için yeni ve boş bir liste aldı: fabrika her yeni nesne için ayrıca çağrılır.
from dataclasses import dataclass
try:
@dataclass
class Basket:
items: list[str] = []
except ValueError as e:
print(e)▸ Beklenen çıktı
mutable default <class 'list'> for field items is not allowed: use default_factory
frozen, order, slots ve __post_init__
Dekoratörün parametreleri ek özellikler açar. frozen=True nesneleri değiştirilemez yapar (dolayısıyla hash'lenebilir olurlar; küme elemanı ve sözlük anahtarı olabilirler), order=True alanları demetler gibi sırayla karşılaştıran <, <=, >, >= işlemlerini üretir, slots=True (Python 3.10+) ise nitelikleri bir sözlük yerine __slots__ içinde saklar; bu da bellek kazandırır ve erişimi hızlandırır. __post_init__ metodu, üretilen __init__'ten hemen sonra çalışır; doğrulamanın yeri burasıdır:
from dataclasses import dataclass, replace, asdict
@dataclass(frozen=True, order=True, slots=True)
class Version:
major: int
minor: int = 0
patch: int = 0
def __post_init__(self):
if self.major < 0:
raise ValueError('major must be >= 0')
v1 = Version(0, 1)
v2 = replace(v1, minor=2)
print(v1 < v2)
print(sorted([Version(1), v2, v1]))
print(asdict(v2))
print({v1, Version(0, 1, 0)})
try:
v1.major = 1
except AttributeError as e:
print(type(e).__name__)▸ Beklenen çıktı
True
[Version(major=0, minor=1, patch=0), Version(major=0, minor=2, patch=0), Version(major=1, minor=0, patch=0)]
{'major': 0, 'minor': 2, 'patch': 0}
{Version(major=0, minor=1, patch=0)}
FrozenInstanceErrorÇıktıyı inceleyelim. sorted, order=True sayesinde çalışır: sürümler (major, minor, patch) demetleri gibi karşılaştırılır. Kümede tek eleman kaldı, çünkü eşit ve değiştirilemez nesnelerin hash değerleri de eşittir. Atama denemesi ise AttributeError'ın bir alt sınıfı olan FrozenInstanceError verdi; nesne oluşturulduktan sonra değiştirilemez.
| Seçenek | Ne sağlar |
|---|---|
frozen=True | değiştirilemez nesneler; atama FrozenInstanceError verir; hash'lenebilirlik |
order=True | alan alan karşılaştırma < <= > >=; nesneler sıralanabilir |
slots=True | __slots__: daha az bellek, hızlı erişim, yeni nitelik eklenemez |
kw_only=True | tüm alanlar yalnızca adıyla verilir |
field(default_factory=...) | her nesne için yeni bir varsayılan değer |
field(repr=False, compare=False) | alanı repr'dan ya da karşılaştırmalardan çıkarır |
Cart dataclass'ını tamamla: varsayılan olarak boş bir sözlük olan items alanını ekle (her sepetin kendi sözlüğü olsun), add ürünü fiyatıyla kaydetsin, total ise fiyatların 2 ondalığa yuvarlanmış toplamını döndürsün.
from dataclasses import dataclass, field
@dataclass
class Cart:
owner: str
# add a field `items`: dict[str, float], an empty dict by default
def add(self, name: str, price: float) -> None:
...
def total(self) -> float:
...
a = Cart('Aysel')
b = Cart('Murad')
a.add('tea', 3.5)
a.add('bread', 0.8)
print(a)
print(b)
print(a.total())▸ Beklenen çıktı
Cart(owner='Aysel', items={'tea': 3.5, 'bread': 0.8})
Cart(owner='Murad', items={})
4.3Koşucular süreye göre (en hızlısı önce), süreler eşitse ada göre sıralanmalıdır. key argümanı kullanmadan sınıfı, sorted() tam olarak böyle sıralayacak şekilde değiştir.
from dataclasses import dataclass
@dataclass(order=True, frozen=True)
class Runner:
name: str
time: float
results = [Runner('Murad', 12.4), Runner('Leyla', 11.9), Runner('Aysel', 12.4)]
for r in sorted(results):
print(r.name, r.time)▸ Beklenen çıktı
Leyla 11.9 Aysel 12.4 Murad 12.4
Önemli noktalar
- İpuçları (
x: int,-> str,list[int],int | None) tipleri belgeler; Python bunları çalışma zamanında zorlamaz. - mypy ya da pyright ipuçlarını statik olarak, yani program çalışmadan önce denetler.
- Generic'ler (
def first[T]),Callable,LiteralveProtocoldaha zengin sözleşmeler tanımlar;Protocolyapısal tiplemedir. @dataclass, ipucu yazılmış alanlardan__init__,__repr__ve__eq__üretir; değiştirilebilir varsayılanlar içinfield(default_factory=...)gerekir.frozen,order,slotsve__post_init__; değiştirilemezlik, sıralama, bellek tasarrufu ve doğrulama ekler.
Kendini test et
10 soru. Her doğru cevap XP kazandırır.
x: int olarak işaretlenmiş bir fonksiyona çalışma zamanında bir karakter dizisi verirsen ne olur?