Mundarija (21)
- 1. Kirish va motivatsiya
- 2. Nazariya — chuqur tushuntirish
- 2.1. Generik funksiyalar
- 2.2. Generik klasslar va taxalluslar
- 2.3. Chegara va cheklovlar
- 2.4. Sukut qiymatli tur parametri (PEP 696)
- 2.5. Variantlik (variance)
- 2.6. Self va NewType
- 2.7. ParamSpec va Concatenate
- 3. Tez ma'lumotnoma
- 4. Batafsil misollar
- Misol 1 — Generik funksiyalar va klasslar
- Misol 2 — Chegara, cheklovlar va sukut qiymat
- Misol 3 — Variantlik, Self va NewType
- Misol 4 — Amaliy: turlangan kesh va repozitoriy
- 5. To'g'ri va noto'g'ri tushunishlar
- 6. Keng tarqalgan xatolar va yechimlari
- 7. Integratsiya — bu bilim qayerda kerak bo'ladi
- 8. Eng yaxshi amaliyotlar
- 9. Amaliy topshiriq
- Xulosa
13.4-dars: TypeVar va generiklar
13-QISM — TURLAR TIZIMI · 4-dars
1. Kirish va motivatsiya
Ba'zi funksiyalar turdan qat'i nazar bir xil ishlaydi: ro'yxatning birinchi elementini olish, keshga qo'yish, ikki qiymatdan kattasini tanlash. Ularni annotatsiyalashda muammo chiqadi:
def birinchi(elementlar: list) -> Any: ...Bu bilan tekshiruvchi hech narsa bilmaydi: birinchi(["a", "b"]) natijasini .upper() qilsa ham, + 1 qilsa ham jim turadi — 13.2-darsdagi Any ning "yuqumliligi".
Real vaziyat. Ombor tizimida ikki xil identifikator bor edi: foydalanuvchi_id va buyurtma_id — ikkalasi ham int. Funksiya bekor_qil(buyurtma_id, foydalanuvchi_id) chaqirilishi kerak edi, lekin bir joyda argumentlar almashtirib yuborilgan. Tur tizimi jim qoldi (ikkalasi int), test yo'q edi, natijada 300 ta buyurtma noto'g'ri bekor qilindi.
Generiklar va NewType aynan shu ikki muammoni hal qiladi: turni saqlab qolish va bir xil ko'rinishdagi qiymatlarni ajratish.
Bu darsda:
- Generik funksiya va klasslar: PEP 695 sintaksisi (
def f[T],class K[T]) - Chegara (
T: Number) va cheklovlar (T: (int, str)) farqi - Sukut qiymatli tur parametri (PEP 696, 3.13)
- Variantlik (variance): nega
list[int]—list[object]emas Self— zanjirli API lar uchun-
NewType— bir xil ko'rinishdagi qiymatlarni ajratish ParamSpecvaConcatenate— dekoratorlarni turlangan qilish- Amaliy: turlangan kesh va repozitoriy
2. Nazariya — chuqur tushuntirish
2.1. Generik funksiyalar
def birinchi[T](elementlar: Sequence[T]) -> T:
return elementlar[0]T — tur o'zgaruvchisi: chaqiruv paytida aniq turga bog'lanadi.
birinchi([1, 2, 3]) # T = int → natija int
birinchi(["a", "b"]) # T = str → natija str| Yozuv | Versiya |
|---|---|
def f[T](x: T) -> T: |
3.12+ (PEP 695) — tavsiya etiladi |
T = TypeVar("T") + def f(x: T) -> T: |
Eski uslub, hali ishlaydi |
Bitta funksiyada bir nechta parametr: def juft[K, V](k: K, v: V) -> tuple[K, V].
2.2. Generik klasslar va taxalluslar
class Quti[T]:
def __init__(self, qiymat: T) -> None:
self._qiymat = qiymat
def ol(self) -> T:
return self._qiymat
class Stek[T](list[T]):
def push(self, element: T) -> None:
self.append(element)
type Juftlik[T] = tuple[T, T] # generik taxallus (13.2-dars)Quti[int]— parametrlashtirilgan tur; ish vaqtidaQuti.__type_params__bilan ko'rish mumkin@dataclassham generik bo'la oladi:@dataclass class Juft[T]: a: T; b: T- Eski uslub:
class Quti(Generic[T])
2.3. Chegara va cheklovlar
| Yozuv | Ma'nosi | Misol |
|---|---|---|
[T] |
Har qanday tur | birinchi[T] |
[T: Number] |
Chegara: Number yoki uning vorisi |
kattaroq[T: float] |
[T: (int, str)] |
Cheklovlar: faqat shu turlardan biri | jam[T: (int, float)] |
Farqi:
def eng_katta[T: float](a: T, b: T) -> T: ... # T — float yoki vorisi; aralash bo'lishi mumkin
def qosh[T: (int, str)](a: T, b: T) -> T: ... # T — YOKI int, YOKI str; aralashtirib bo'lmaydiChegara — "kamida shu imkoniyatlar bo'lsin"; cheklovlar — "faqat shu ro'yxatdan".
2.4. Sukut qiymatli tur parametri (PEP 696)
def yukla[T = dict[str, object]](xom: str, tur: type[T] = dict) -> T: ...
class Kesh[K, V = str]: ... # Kesh[int] → V = str3.13 dan; T.has_default() va T.__default__ bilan ish vaqtida ko'rish mumkin.
2.5. Variantlik (variance)
Savol: list[int] ni list[object] kutayotgan joyga berish mumkinmi?
def qosh_hammaga(elementlar: list[object]) -> None:
elementlar.append("matn") # ⚠️ ro'yxat aslida list[int] bo'lsa?
sonlar: list[int] = [1, 2]
qosh_hammaga(sonlar) # ❌ tekshiruvchi rad etadi| Tur | Variantlik | Ma'nosi |
|---|---|---|
list[T], dict[K, V], set[T] |
Invariant | list[int] ≠ list[object] |
Sequence[T], Iterable[T], Mapping[K, V] (qiymat) |
Kovariant | Sequence[int] — Sequence[object] o'rniga yaraydi |
Callable[[T], R] argumenti |
Kontravariant | Callable[[object], R] — Callable[[int], R] o'rniga yaraydi |
Amaliy qoida: o'qish uchun kovariant turlar (Sequence, Iterable), o'zgartirish uchun invariant (list). PEP 695 da variantlik avtomatik aniqlanadi — qo'lda covariant=True yozish kerak emas.
2.6. Self va NewType
Self — "shu klassning o'zi":
class Sorov:
def filtr(self, **shartlar: object) -> Self:
...
return selfVoris klassda ham to'g'ri ishlaydi: AdminSorov().filtr() → AdminSorov (oddiy -> "Sorov" bo'lsa, tur yo'qolardi).
NewType — mavjud tur asosida alohida tur:
FoydalanuvchiID = NewType("FoydalanuvchiID", int)
BuyurtmaID = NewType("BuyurtmaID", int)
def bekor_qil(buyurtma: BuyurtmaID, kim: FoydalanuvchiID) -> None: ...
bekor_qil(FoydalanuvchiID(5), BuyurtmaID(7)) # ❌ tekshiruvchi xato beradiTaxallus (type X = int) |
NewType |
|
|---|---|---|
| Tekshiruvchi uchun | int bilan bir xil |
Alohida tur |
| Ish vaqtida | int |
int (funksiya faqat qiymatni qaytaradi) |
| Narxi | 0 | Chaqiruv (juda kichik) |
2.7. ParamSpec va Concatenate
Dekorator o'ralgan funksiyaning imzosini saqlashi kerak:
def olchov[**P, R](f: Callable[P, R]) -> Callable[P, R]:
@functools.wraps(f)
def orov(*args: P.args, **kwargs: P.kwargs) -> R:
return f(*args, **kwargs)
return orov**P— "funksiyaning barcha parametrlari" (ParamSpec)P.args/P.kwargs— faqat shu juftlikda ishlatiladiConcatenate[Ulanish, P]— dekorator birinchi argumentni qo'shsa yoki olib tashlasa
Callable[..., R] bilan imzo yo'qoladi; ParamSpec bilan saqlanadi.
TypeVarTuple (*Ts) — o'zgaruvchan sonli tur parametrlari (massiv o'lchamlari kabi murakkab holatlar uchun); kundalik kodda kam uchraydi.
3. Tez ma'lumotnoma
Sintaksis (3.12+)
def f[T](x: T) -> T: ... class Quti[T]: ...
def f[T: float](a: T, b: T) -> T: ... # chegara
def f[T: (int, str)](x: T) -> T: ... # cheklovlar
def f[T = int](x: T | None = None): ... # sukut (3.13)
type Juft[T] = tuple[T, T]
def dek[**P, R](f: Callable[P, R]) -> Callable[P, R]: ...Variantlik
o'qish → Sequence/Iterable/Mapping (kovariant)
yozish → list/dict/set (invariant)
callback argumenti → kontravariantMaxsus
Self — zanjirli metodlar va voris klasslar
NewType("ID", int) — ajratilgan tur, ish vaqtida int
T.__bound__, T.__constraints__, T.has_default()4. Batafsil misollar
Misol 1 — Generik funksiyalar va klasslar
"""Generiksiz va generik variant; tur parametrlarini ko'rish; generik klass, dataclass va list vorisligi."""
import typing
from collections.abc import Callable, Iterable, Sequence
from dataclasses import dataclass
def birinchi_any(elementlar: list) -> typing.Any:
return elementlar[0]
def birinchi[T](elementlar: Sequence[T]) -> T:
return elementlar[0]
def juftla[K, V](kalitlar: Sequence[K], qiymatlar: Sequence[V]) -> list[tuple[K, V]]:
return list(zip(kalitlar, qiymatlar))
print("=== 1. Generiksiz va generik ===")
print(f" birinchi_any([1, 2]) = {birinchi_any([1, 2])} (tekshiruvchi uchun turi: Any)")
print(f" birinchi([1, 2]) = {birinchi([1, 2])} (turi: int), birinchi(['a']) = {birinchi(['a'])} (turi: str)")
print(f" tur parametrlari: {birinchi.__type_params__}, izohlar: {typing.get_type_hints(birinchi)}")
print(f" juftla: {juftla(['a', 'b'], [1, 2])}, parametrlari: {juftla.__type_params__}")
print("\n=== 2. Generik klass ===")
class Quti[T]:
def __init__(self, qiymat: T) -> None:
self._qiymat = qiymat
def ol(self) -> T:
return self._qiymat
def almashtir(self, yangi: T) -> "Quti[T]":
return Quti(yangi)
def __repr__(self) -> str:
return f"Quti({self._qiymat!r})"
son_qutisi = Quti(42)
matn_qutisi: Quti[str] = Quti("salom")
print(f" {son_qutisi} → {son_qutisi.ol()}, {matn_qutisi} → {matn_qutisi.ol().upper()}")
print(f" Quti.__type_params__: {Quti.__type_params__}, parametrlashtirilgan: {Quti[int]}")
print(f" ish vaqtida Quti[int] va Quti bir xil klass yaratadi: {type(Quti[int](1)) is Quti}")
print("\n=== 3. Generik dataclass va meros ===")
@dataclass
class Juft[T]:
chap: T
ong: T
def almashtirilgan(self) -> "Juft[T]":
return Juft(self.ong, self.chap)
class Stek[T](list[T]):
def push(self, element: T) -> None:
self.append(element)
def pop_yoki(self, sukut: T) -> T:
return self.pop() if self else sukut
print(f" Juft(1, 2) = {Juft(1, 2)}, almashtirilgan: {Juft(1, 2).almashtirilgan()}")
print(f" Juft[str]('a', 'b') = {Juft[str]('a', 'b')}")
stek: Stek[int] = Stek()
stek.push(1)
stek.push(2)
print(f" Stek: {stek}, pop_yoki: {stek.pop_yoki(0)}, bo'shda: {Stek[int]().pop_yoki(-1)}")
print(f" Stek — haqiqiy list: {isinstance(stek, list)}")
print("\n=== 4. Generik yordamchilar ===")
def guruhla[T](elementlar: Iterable[T], kalit: Callable[[T], str]) -> dict[str, list[T]]:
natija: dict[str, list[T]] = {}
for el in elementlar:
natija.setdefault(kalit(el), []).append(el)
return natija
soz = ["olma", "anor", "nok", "uzum", "anjir"]
print(f" birinchi harf bo'yicha: {guruhla(soz, lambda s: s[0])}")
sonlar = [1, 2, 3, 4, 5, 6]
print(f" juft/toq: {guruhla(sonlar, lambda n: 'juft' if n % 2 == 0 else 'toq')}")Natijaning muhim qismi:
=== 1. Generiksiz va generik ===
birinchi_any([1, 2]) = 1 (tekshiruvchi uchun turi: Any)
birinchi([1, 2]) = 1 (turi: int), birinchi(['a']) = a (turi: str)
tur parametrlari: (T,), izohlar: {'elementlar': collections.abc.Sequence[T], 'return': T}
juftla: [('a', 1), ('b', 2)], parametrlari: (K, V)
=== 2. Generik klass ===
Quti(42) → 42, Quti('salom') → SALOM
Quti.__type_params__: (T,), parametrlashtirilgan: __main__.Quti[int]
ish vaqtida Quti[int] va Quti bir xil klass yaratadi: True
=== 3. Generik dataclass va meros ===
Juft(1, 2) = Juft(chap=1, ong=2), almashtirilgan: Juft(chap=2, ong=1)
Juft[str]('a', 'b') = Juft(chap='a', ong='b')
Stek: [1, 2], pop_yoki: 2, bo'shda: -1
Stek — haqiqiy list: True
=== 4. Generik yordamchilar ===
birinchi harf bo'yicha: {'o': ['olma'], 'a': ['anor', 'anjir'], 'n': ['nok'], 'u': ['uzum']}
juft/toq: {'toq': [1, 3, 5], 'juft': [2, 4, 6]}Nima ko'rsatdi: 2.1, 2.2-bo'limlar.
Misol 2 — Chegara, cheklovlar va sukut qiymat
"""T: chegara va cheklovlar farqi; ish vaqtidagi atributlar; sukut qiymatli tur parametri; qachon qaysi biri."""
import typing
from collections.abc import Sequence
def eng_katta[T: float](qiymatlar: Sequence[T]) -> T:
"""Chegara: T — float yoki uning vorisi (int ham mos, chunki son protokoliga kiradi)."""
natija = qiymatlar[0]
for q in qiymatlar[1:]:
if q > natija:
natija = q
return natija
def ikki_marta[T: (int, str)](qiymat: T) -> T:
"""Cheklovlar: faqat int yoki str — boshqasi mumkin emas."""
return qiymat * 2
def yukla[T = list[str]](xom: str, turi: type[T] = list) -> T:
"""Sukut tur parametri (PEP 696)."""
return turi(xom.split(","))
print("=== 1. Chegara (bound) ===")
print(f" eng_katta([3, 9, 5]) = {eng_katta([3, 9, 5])}")
print(f" eng_katta([2.5, 1.5]) = {eng_katta([2.5, 1.5])}")
chegara_param = eng_katta.__type_params__[0]
print(f" T.__bound__ = {chegara_param.__bound__}, cheklovlari: {chegara_param.__constraints__}")
print("\n=== 2. Cheklovlar (constraints) ===")
print(f" ikki_marta(21) = {ikki_marta(21)}, ikki_marta('ha') = {ikki_marta('ha')!r}")
cheklov_param = ikki_marta.__type_params__[0]
print(f" T.__constraints__ = {cheklov_param.__constraints__}, chegarasi: {cheklov_param.__bound__}")
print(f" ⚠️ ish vaqtida cheklov majburlanmaydi: ikki_marta([1, 2]) = {ikki_marta([1, 2])}")
print(" (tekshiruvchi esa bu chaqiruvni rad etadi)")
print("\n=== 3. Sukut qiymatli tur parametri ===")
sukut_param = yukla.__type_params__[0]
print(f" has_default(): {sukut_param.has_default()}, __default__: {sukut_param.__default__}")
print(f" yukla('a,b,c') = {yukla('a,b,c')}")
print(f" yukla('a,b,c', tuple) = {yukla('a,b,c', tuple)}")
print(f" yukla('a,b,c', set) = {sorted(yukla('a,b,c', set))}")
print("\n=== 4. Qachon qaysi biri ===")
jadval = [
("[T]", "har qanday tur", "birinchi(), guruhla()"),
("[T: Chegara]", "kamida shu imkoniyatlar bo'lsin", "eng_katta[T: float]"),
("[T: (A, B)]", "faqat shu turlardan biri", "ikki_marta[T: (int, str)]"),
("[T = Sukut]", "ko'rsatilmasa — sukut tur", "yukla[T = list[str]]"),
]
for yozuv, mano, misol in jadval:
print(f" {yozuv:16} {mano:34} {misol}")
print("\n=== 5. Chegara bilan ishlaydigan amallar ===")
class Pul(float):
"""float vorisi — chegaraga mos keladi."""
def __repr__(self) -> str:
return f"{float(self):,.2f} so'm"
narxlar = [Pul(250_000), Pul(120_000), Pul(890_000)]
print(f" eng_katta(narxlar) = {eng_katta(narxlar)!r}, turi saqlandi: {type(eng_katta(narxlar)).__name__}")Natijaning muhim qismi:
=== 1. Chegara (bound) ===
eng_katta([3, 9, 5]) = 9
eng_katta([2.5, 1.5]) = 2.5
T.__bound__ = <class 'float'>, cheklovlari: ()
=== 2. Cheklovlar (constraints) ===
ikki_marta(21) = 42, ikki_marta('ha') = 'haha'
T.__constraints__ = (<class 'int'>, <class 'str'>), chegarasi: None
⚠️ ish vaqtida cheklov majburlanmaydi: ikki_marta([1, 2]) = [1, 2, 1, 2]
(tekshiruvchi esa bu chaqiruvni rad etadi)
=== 3. Sukut qiymatli tur parametri ===
has_default(): True, __default__: list[str]
yukla('a,b,c') = ['a', 'b', 'c']
yukla('a,b,c', tuple) = ('a', 'b', 'c')
yukla('a,b,c', set) = ['a', 'b', 'c']
=== 4. Qachon qaysi biri ===
[T] har qanday tur birinchi(), guruhla()
[T: Chegara] kamida shu imkoniyatlar bo'lsin eng_katta[T: float]
[T: (A, B)] faqat shu turlardan biri ikki_marta[T: (int, str)]
[T = Sukut] ko'rsatilmasa — sukut tur yukla[T = list[str]]
=== 5. Chegara bilan ishlaydigan amallar ===
eng_katta(narxlar) = 890,000.00 so'm, turi saqlandi: PulNima ko'rsatdi: 2.3, 2.4-bo'limlar.
Misol 3 — Variantlik, Self va NewType
"""Nega list invariant: ish vaqtidagi buzilish; Sequence bilan xavfsiz o'qish; Self bilan zanjir; NewType bilan ID larni ajratish."""
import typing
from collections.abc import Sequence
def notogri_qosh(elementlar: list[object]) -> None:
"""Tekshiruvchi bunga list[int] berishga ruxsat bermaydi — mana nega."""
elementlar.append("men satrman")
def xavfsiz_yigindi(qiymatlar: Sequence[float]) -> float:
"""Faqat o'qiydi — kovariant tur yetarli."""
return sum(qiymatlar)
print("=== 1. Nega list[int] ≠ list[object] ===")
sonlar: list[int] = [1, 2, 3]
notogri_qosh(sonlar) # type: ignore[arg-type] — tekshiruvchi rad etadi
print(f" 'list[object]' deb qabul qilingandan keyin ro'yxat: {sonlar}")
try:
print(sum(sonlar))
except TypeError as xato:
print(f" endi sum(sonlar) → TypeError: {xato}")
print(" ⭐ shuning uchun list invariant: o'zgartiriladigan konteynerni kengroq turga bermaysiz")
print("\n=== 2. O'qish uchun kovariant turlar ===")
sonlar = [1, 2, 3]
print(f" xavfsiz_yigindi(list): {xavfsiz_yigindi(sonlar)}")
print(f" xavfsiz_yigindi(tuple): {xavfsiz_yigindi((1.5, 2.5))}")
print(f" xavfsiz_yigindi(range): {xavfsiz_yigindi(range(5))}")
print(" ⭐ Sequence[float] — o'qish uchun; funksiya o'zgartira olmaydi")
print("\n=== 3. Self bilan zanjirli API ===")
class Sorov:
def __init__(self) -> None:
self.shartlar: list[str] = []
def filtr(self, shart: str) -> typing.Self:
self.shartlar.append(shart)
return self
def matn(self) -> str:
return " AND ".join(self.shartlar) or "TRUE"
class AdminSorov(Sorov):
def faqat_faol(self) -> typing.Self:
return self.filtr("faol = true")
sorov = AdminSorov().filtr("shahar = 'Toshkent'").faqat_faol().filtr("ball > 80")
print(f" natija: {sorov.matn()}")
print(f" zanjirdan keyin ham tur saqlandi: {type(sorov).__name__}")
print(f" filtr() izohi: {typing.get_type_hints(Sorov.filtr)['return']}")
print("\n=== 4. NewType bilan ID larni ajratish ===")
FoydalanuvchiID = typing.NewType("FoydalanuvchiID", int)
BuyurtmaID = typing.NewType("BuyurtmaID", int)
BEKOR_QILINGANLAR: list[tuple[int, int]] = []
def bekor_qil(buyurtma: BuyurtmaID, kim: FoydalanuvchiID) -> None:
BEKOR_QILINGANLAR.append((buyurtma, kim))
kim = FoydalanuvchiID(77)
nima = BuyurtmaID(1042)
bekor_qil(nima, kim)
print(f" to'g'ri chaqiruv: {BEKOR_QILINGANLAR[-1]}")
bekor_qil(kim, nima) # type: ignore[arg-type] — argumentlar almashtirilgan
print(f" almashtirilgan chaqiruv ham ishladi: {BEKOR_QILINGANLAR[-1]} ⚠️ ish vaqtida ikkalasi ham int")
print(f" NewType ish vaqtida: {type(kim).__name__}, asl turi: {FoydalanuvchiID.__supertype__.__name__}")
print(" ⭐ lekin tekshiruvchi ikkinchi chaqiruvni xato deb belgilaydi — bu yetarli")
print(f" taxallus bilan bunday himoya yo'q: type FID = int → tekshiruvchi uchun ham oddiy int")Natijaning muhim qismi:
=== 1. Nega list[int] ≠ list[object] ===
'list[object]' deb qabul qilingandan keyin ro'yxat: [1, 2, 3, 'men satrman']
endi sum(sonlar) → TypeError: unsupported operand type(s) for +: 'int' and 'str'
⭐ shuning uchun list invariant: o'zgartiriladigan konteynerni kengroq turga bermaysiz
=== 2. O'qish uchun kovariant turlar ===
xavfsiz_yigindi(list): 6
xavfsiz_yigindi(tuple): 4.0
xavfsiz_yigindi(range): 10
⭐ Sequence[float] — o'qish uchun; funksiya o'zgartira olmaydi
=== 3. Self bilan zanjirli API ===
natija: shahar = 'Toshkent' AND faol = true AND ball > 80
zanjirdan keyin ham tur saqlandi: AdminSorov
filtr() izohi: typing.Self
=== 4. NewType bilan ID larni ajratish ===
to'g'ri chaqiruv: (1042, 77)
almashtirilgan chaqiruv ham ishladi: (77, 1042) ⚠️ ish vaqtida ikkalasi ham int
NewType ish vaqtida: int, asl turi: int
⭐ lekin tekshiruvchi ikkinchi chaqiruvni xato deb belgilaydi — bu yetarli
taxallus bilan bunday himoya yo'q: type FID = int → tekshiruvchi uchun ham oddiy intNima ko'rsatdi: 2.5, 2.6-bo'limlar — kirishdagi ID almashtirish xatosi aynan shunday yuz bergan.
Misol 4 — Amaliy: turlangan kesh va repozitoriy
Ma'lumotlar bilan ishlaydigan qatlam: generik repozitoriy (har model uchun bitta klass emas), ParamSpec bilan imzoni saqlaydigan kesh dekoratori va NewType identifikatorlari.
"""Generik repozitoriy; ParamSpec bilan imzoni saqlaydigan kesh; statistika; NewType identifikatorlar."""
import functools
import typing
from collections.abc import Callable, Iterator
from dataclasses import dataclass
FoydalanuvchiID = typing.NewType("FoydalanuvchiID", int)
MahsulotID = typing.NewType("MahsulotID", int)
@dataclass(frozen=True)
class Foydalanuvchi:
id: FoydalanuvchiID
ism: str
@dataclass(frozen=True)
class Mahsulot:
id: MahsulotID
nom: str
narx: float
class Repozitoriy[T, K]:
"""Har qanday model uchun umumiy saqlash qatlami."""
def __init__(self, nom: str) -> None:
self.nom = nom
self._yozuvlar: dict[K, T] = {}
def qosh(self, kalit: K, yozuv: T) -> T:
self._yozuvlar[kalit] = yozuv
return yozuv
def ol(self, kalit: K) -> T | None:
return self._yozuvlar.get(kalit)
def hammasi(self) -> Iterator[T]:
return iter(self._yozuvlar.values())
def __len__(self) -> int:
return len(self._yozuvlar)
def kesh_bilan[**P, R](f: Callable[P, R]) -> Callable[P, R]:
"""Imzoni saqlaydigan kesh: P — parametrlar, R — natija."""
saqlangan: dict[tuple[object, ...], R] = {}
statistika = {"topildi": 0, "hisoblandi": 0}
@functools.wraps(f)
def orov(*args: P.args, **kwargs: P.kwargs) -> R:
kalit = (args, tuple(sorted(kwargs.items())))
if kalit in saqlangan:
statistika["topildi"] += 1
return saqlangan[kalit]
statistika["hisoblandi"] += 1
natija = f(*args, **kwargs)
saqlangan[kalit] = natija
return natija
orov.statistika = statistika # type: ignore[attr-defined]
return orov
foydalanuvchilar: Repozitoriy[Foydalanuvchi, FoydalanuvchiID] = Repozitoriy("foydalanuvchilar")
mahsulotlar: Repozitoriy[Mahsulot, MahsulotID] = Repozitoriy("mahsulotlar")
print("=== 1. Generik repozitoriy ===")
for i, ism in enumerate(["Aziz", "Dilnoza", "Jasur"], start=1):
foydalanuvchilar.qosh(FoydalanuvchiID(i), Foydalanuvchi(FoydalanuvchiID(i), ism))
for i, (nom, narx) in enumerate([("Klaviatura", 250_000.0), ("Sichqoncha", 120_000.0)], start=100):
mahsulotlar.qosh(MahsulotID(i), Mahsulot(MahsulotID(i), nom, narx))
print(f" {foydalanuvchilar.nom}: {len(foydalanuvchilar)} ta, 2-si: {foydalanuvchilar.ol(FoydalanuvchiID(2))}")
print(f" {mahsulotlar.nom}: {len(mahsulotlar)} ta, eng qimmati: {max(mahsulotlar.hammasi(), key=lambda m: m.narx).nom}")
print(f" yo'q kalit: {foydalanuvchilar.ol(FoydalanuvchiID(99))}")
print(f" bitta klass ikki model uchun: {Repozitoriy.__type_params__}")
print("\n=== 2. Imzoni saqlaydigan kesh ===")
@kesh_bilan
def buyurtma_summasi(mahsulot_id: MahsulotID, soni: int = 1, *, chegirma: float = 0.0) -> float:
mahsulot = mahsulotlar.ol(mahsulot_id)
if mahsulot is None:
raise KeyError(mahsulot_id)
return round(mahsulot.narx * soni * (1 - chegirma), 2)
print(f" 1 dona: {buyurtma_summasi(MahsulotID(100))}")
print(f" 3 dona: {buyurtma_summasi(MahsulotID(100), 3)}")
print(f" 3 dona 10% chegirma: {buyurtma_summasi(MahsulotID(100), 3, chegirma=0.1)}")
print(f" takroriy chaqiruv: {buyurtma_summasi(MahsulotID(100), 3)}")
print(f" statistika: {buyurtma_summasi.statistika}")
print(f" imzo saqlandi: {typing.get_type_hints(buyurtma_summasi)}")
print(f" nom va hujjat ham: {buyurtma_summasi.__name__}")
print("\n=== 3. Turlar nimani ushlaydi ===")
print(" ✅ foydalanuvchilar.ol(FoydalanuvchiID(1)) — to'g'ri")
print(" ❌ foydalanuvchilar.ol(MahsulotID(100)) — tekshiruvchi: MahsulotID ≠ FoydalanuvchiID")
print(" ❌ buyurtma_summasi(MahsulotID(100), soni='uch') — tekshiruvchi: int kutilgan")
print(" ❌ mahsulotlar.qosh(MahsulotID(1), Foydalanuvchi(...)) — model turi mos emas")
xato_chaqiruv = mahsulotlar.ol(MahsulotID(100))
print(f" ish vaqtida esa faqat haqiqiy xatolar chiqadi: {xato_chaqiruv.nom if xato_chaqiruv else None}")Natijaning muhim qismi:
=== 1. Generik repozitoriy ===
foydalanuvchilar: 3 ta, 2-si: Foydalanuvchi(id=2, ism='Dilnoza')
mahsulotlar: 2 ta, eng qimmati: Klaviatura
yo'q kalit: None
bitta klass ikki model uchun: (T, K)
=== 2. Imzoni saqlaydigan kesh ===
1 dona: 250000.0
3 dona: 750000.0
3 dona 10% chegirma: 675000.0
takroriy chaqiruv: 750000.0
statistika: {'topildi': 1, 'hisoblandi': 3}
imzo saqlandi: {'mahsulot_id': __main__.MahsulotID, 'soni': <class 'int'>, 'chegirma': <class 'float'>, 'return': <class 'float'>}
nom va hujjat ham: buyurtma_summasi
=== 3. Turlar nimani ushlaydi ===
✅ foydalanuvchilar.ol(FoydalanuvchiID(1)) — to'g'ri
❌ foydalanuvchilar.ol(MahsulotID(100)) — tekshiruvchi: MahsulotID ≠ FoydalanuvchiID
❌ buyurtma_summasi(MahsulotID(100), soni='uch') — tekshiruvchi: int kutilgan
❌ mahsulotlar.qosh(MahsulotID(1), Foydalanuvchi(...)) — model turi mos emas
ish vaqtida esa faqat haqiqiy xatolar chiqadi: KlaviaturaNima ko'rsatdi: 2.1, 2.6, 2.7-bo'limlar.
5. To'g'ri va noto'g'ri tushunishlar
| Noto'g'ri fikr | To'g'risi |
|---|---|
| "Generiklar ish vaqtida turni tekshiradi" | Yo'q — faqat tekshiruvchi uchun |
"Quti[int] yangi klass yaratadi" |
Xuddi shu klass; parametrlashtirish faqat annotatsiya |
"list[int] ni list[object] o'rniga berish mumkin" |
Invariant — mumkin emas (va sabab bor) |
| "Chegara va cheklovlar bir xil" | Chegara — "shu turdan meros"; cheklovlar — "shu ro'yxatdan biri" |
"NewType yangi klass yaratadi" |
Ish vaqtida asl tur; faqat tekshiruvchi ajratadi |
"Dekoratorda Callable[..., Any] yetarli" |
Imzo yo'qoladi — ParamSpec ishlating |
"Self o'rniga klass nomini yozsa bo'ladi" |
Voris klasslarda tur noto'g'ri bo'ladi |
| "PEP 695 sintaksisi faqat yangi uslub" | Variantlikni ham avtomatik aniqlaydi, TypeVar e'lonlari kerak emas |
6. Keng tarqalgan xatolar va yechimlari
1. Any bilan turni yo'qotish
def birinchi(x: list) -> Any: ... # ❌
def birinchi[T](x: Sequence[T]) -> T: ... # ✅2. Bir nechta parametrni bitta T bilan bog'lash
def juft[T](k: T, v: T) -> tuple[T, T]: ... # ⚠️ kalit va qiymat bir xil turda bo'ladi
def juft[K, V](k: K, v: V) -> tuple[K, V]: ... # ✅3. O'zgartiriladigan konteynerni kovariant deb o'ylash
def ishla(elementlar: list[object]) -> None: ... # ⚠️ list[int] berib bo'lmaydi
def ishla(elementlar: Sequence[object]) -> None: # ✅ faqat o'qiydi4. Dekoratorda imzoni yo'qotish
def dek(f: Callable[..., Any]) -> Callable[..., Any]: ... # ❌
def dek[**P, R](f: Callable[P, R]) -> Callable[P, R]: ... # ✅5. NewType ni klass deb ishlatish
class FoydalanuvchiID(int): pass # ⚠️ ish vaqtida yangi klass — ortiqcha narx
FoydalanuvchiID = NewType("FoydalanuvchiID", int) # ✅ tekin6. Eski TypeVar uslubini aralashtirish
T = TypeVar("T")
def f[T](x: T) -> T: ... # ⚠️ ikki xil T — chalkashlik7. Chegara o'rniga cheklov ishlatish
def eng_katta[T: (int, float)](a: T, b: T) -> T: ... # ⚠️ int va float aralash bo'lolmaydi
def eng_katta[T: float](a: T, b: T) -> T: ... # ✅8. Self ni statik metodda kutish
@staticmethod
def yarat() -> Self: ... # ⚠️ statik metodda Self yo'q
@classmethod
def yarat(cls) -> Self: ... # ✅7. Integratsiya — bu bilim qayerda kerak bo'ladi
- 8-qism (o'tilgan): klasslar va meros
- 10.8–10.11-darslar (o'tilgan): dekoratorlar —
ParamSpecular uchun - 13.2-dars (o'tilgan): kolleksiya turlari va taxalluslar
- 13.5-dars:
Protocol— generik strukturaviy turlar - 13.6-dars:
TypedDict,NamedTuple - 13.9-dars: turlar bilan loyiha dizayni
- 20–23-qismlar: repozitoriylar, ORM va API qatlamlari
- 24-qism: NumPy va shakl (shape) turlari —
TypeVarTupleqo'llanadigan soha
8. Eng yaxshi amaliyotlar
Yangi kodda PEP 695 sintaksisi (
def f[T],class K[T]).Turni saqlang:
Anyo'rniga tur parametri.O'qish uchun kovariant, o'zgartirish uchun invariant turlar.
Dekoratorlarda
ParamSpec— imzo yo'qolmasin.Bir xil ko'rinishdagi identifikatorlarni
NewTypebilan ajrating.Zanjirli metodlarda
Self.Cheklov emas, chegara — aralash turlar kerak bo'lsa.
Generiklarni haddan oshirmang: ikki-uch parametrdan ko'pi o'qishni qiyinlashtiradi.
9. Amaliy topshiriq
Vazifa 1: Natijani bashorat qiling
import typing
1. def f[T](x: T) -> T:
return x
print(f.__type_params__)
2. print(f(5), f("a"))
3. class Quti[T]:
pass
print(Quti.__type_params__)
4. print(type(Quti[int]( )) is Quti)
5. def g[T: float](x: T) -> T:
return x
print(g.__type_params__[0].__bound__)
6. def h[T: (int, str)](x: T) -> T:
return x
print(h.__type_params__[0].__constraints__)
7. def k[T = int](x: T | None = None) -> T | None:
return x
print(k.__type_params__[0].has_default(), k.__type_params__[0].__default__)
8. ID = typing.NewType("ID", int)
x = ID(5)
print(x, type(x), x + 1)
9. print(ID.__supertype__)
10. type Juft[T] = tuple[T, T]
print(Juft.__type_params__, Juft[int])
11. class A:
def o(self) -> typing.Self:
return self
class B(A):
pass
print(type(B().o()).__name__)
12. print(typing.get_type_hints(A.o)["return"])Javoblar
(T,)5 a(T,)True— parametrlashtirish yangi klass yaratmaydi<class 'float'>(<class 'int'>, <class 'str'>)True <class 'int'>5 <class 'int'> 6— ish vaqtida oddiyint<class 'int'>(T,) Juft[int]— taxallus indekslanganda o'z nomi bilan ko'rinadi (asl turiJuft.__value__)B—Selfvoris klassni saqlaydityping.Self
Vazifa 2: Xatolarni tuzating
1. def oxirgi(elementlar: list) -> typing.Any:
return elementlar[-1]
2. def juftlik[T](kalit: T, qiymat: T) -> dict[T, T]:
return {kalit: qiymat} # kalit str, qiymat int bo'lishi kerak
3. def olchov(f: typing.Callable[..., typing.Any]) -> typing.Callable[..., typing.Any]:
@functools.wraps(f)
def orov(*a, **k):
return f(*a, **k)
return orov
4. def jami(qiymatlar: list[float]) -> float:
return sum(qiymatlar) # kortej va generator ham beriladi
5. class Sorov:
def filtr(self, shart: str) -> "Sorov":
self.shartlar.append(shart)
return self
class AdminSorov(Sorov):
def faqat_faol(self) -> "AdminSorov": ...Javoblar
1. def oxirgi[T](elementlar: Sequence[T]) -> T:
return elementlar[-1]
2. def juftlik[K, V](kalit: K, qiymat: V) -> dict[K, V]:
return {kalit: qiymat}
3. def olchov[**P, R](f: Callable[P, R]) -> Callable[P, R]:
@functools.wraps(f)
def orov(*a: P.args, **k: P.kwargs) -> R:
return f(*a, **k)
return orov
4. def jami(qiymatlar: Iterable[float]) -> float:
return sum(qiymatlar)
5. class Sorov:
def filtr(self, shart: str) -> typing.Self: # voris klassda ham to'g'ri
self.shartlar.append(shart)
return selfVazifa 3: Generik kolleksiyalar
Quyidagilarni generik qilib yozing va sinovdan o'tkazing:
Navbat[T]—qo'sh,ol,__len__,bo'shmiKesh[K, V]— hajm chegarasi va LRU siqib chiqarish bilanNatija[T, X]— muvaffaqiyat (T) yoki xato (X) ni ifodalaydi (RustResultkabi)- Har biri uchun
mypyo'tadigan misollar va o'tmaydigan misollar yozing (13.7-darsda tekshirasiz)
Vazifa 4: ParamSpec bilan dekoratorlar to'plami
qayta_urin(urinishlar=3)— imzoni saqlaydigan qayta urinish dekoratorivaqt_olch— bajarilish vaqtini yozadigan dekoratorConcatenatebilanulanish_bilan— birinchi argument sifatida ulanish qo'shadigan dekorator- Har biri uchun
functools.wrapsva__wrapped__ni tekshiring (10.11-dars) - Dekoratorlar tartibini o'zgartirib, natijani solishtiring
Vazifa 5: NewType auditi
O'z loyihangizda (yoki namunada):
- Bir xil turdagi (
int,str) identifikatorlarni toping - Ular uchun
NewTypelar e'lon qiling va imzolarni yangilang - Chegarada o'girish funksiyalarini yozing (
foydalanuvchi_id(xom: object) -> FoydalanuvchiID) - Almashtirib yuborilishi mumkin bo'lgan joylarni sanab, hisobot yozing
Vazifa 6: Variantlik tajribalari
list,Sequence,Iterable,Callableuchun kichik funksiyalar yozing- Har birida "kengroq tur" berishga urinib ko'ring va tekshiruvchi qanday javob berishini yozib oling
list[int]nilist[object]sifatida berib, ish vaqtida qanday buzilish yuz berishini ko'rsating (Misol 3)- Natijani "qaysi holatda qaysi tur" jadvaliga aylantiring
Vazifa 7: O'ylash
Java generiklari tur o'chirish (type erasure) bilan ishlaydi: ish vaqtida List<String> va List<Integer> — bir xil; C# esa generiklarni ish vaqtida saqlaydi (reified); TypeScript'da generiklar faqat kompilyatsiyada; Rust monomorfizatsiya qiladi (har tur uchun alohida kod). Python generiklari qaysi modelga yaqin va bu dasturchi uchun nimani anglatadi?
Javob
Qisqa javob: Python generiklari TypeScript modeliga eng yaqin: ular faqat tekshiruvchi uchun mavjud va ish vaqtida deyarli hech narsa qilmaydi (Quti[int] — o'sha klass). Java'dagi tur o'chirishga ham o'xshaydi, lekin Python'da "o'chirish" ham yo'q — turlar hech qachon kompilyatsiyaga kirmaydi.
1. Modellar
| Til | Generiklar ish vaqtida | Oqibat |
|---|---|---|
C#, Kotlin (reified) |
Saqlanadi | typeof(T) mumkin; ixtisoslashgan kod |
| Rust | Monomorfizatsiya | Har tur uchun alohida mashina kodi — tez |
| Java | O'chiriladi | new T[] mumkin emas; instanceof List<String> yo'q |
| TypeScript | Faqat kompilyatsiyada | Ish vaqtida umuman yo'q |
| Python | Faqat annotatsiya | isinstance(x, Quti[int]) — TypeError |
2. Python'da amaliy oqibatlar
class Quti[T]: ...
isinstance(Quti(5), Quti) # ✅ ishlaydi
isinstance(Quti(5), Quti[int]) # ❌ TypeError- Tur parametri bo'yicha dispatch qilib bo'lmaydi — kerak bo'lsa aniq argument (
turi: type[T]) - Konstruktorda tur kerak bo'lsa, uni qiymat sifatida uzating:
def yukla[T](xom: str, turi: type[T]) -> T - Generik konteyner ish vaqtida hech qanday tekshiruv qilmaydi — noto'g'ri element qo'shilsa, xato keyinroq chiqadi
3. Nima uchun shunday
- Python annotatsiyalarni ish vaqtida majburlamaydi (13.1-dars) — generiklar ham shu qoidaga bo'ysunadi
- Ish vaqtida tur parametrlarini saqlash har obyektga qo'shimcha xotira va narx qo'shardi
- Dinamik til uchun "tur bo'yicha ixtisoslashuv" ning foydasi kam: CPython baribir interpretator (12.8-dars)
4. Amaliy tavsiyalar
- Generiklardan hujjat va tekshiruv vositasi sifatida foydalaning
- Ish vaqtida tur kerak bo'lsa — uni aniq uzating (
type[T]) yokipydantic(13.8-dars) ishlating isinstancebilan parametrlashtirilgan turni tekshirishga urinmang- Java tajribasi: o'chirish tufayli ba'zi naqshlar (generik massivlar) imkonsiz — Python'da ham shunga o'xshash cheklovlarni oldindan hisobga oling
Nimani mustahkamlaydi: 2.1–2.7-bo'limlar.
Xulosa
Bu darsda generiklar bilan turlarni saqlashni o'rgandik.
Eng muhim uch fikr:
Generiklar turni yo'qotmaydi.
def birinchi[T](x: Sequence[T]) -> T— chaqiruv paytidaTaniq turga bog'lanadi, shuning uchun natija turi ham ma'lum bo'ladi (Anybilan esa yo'qoladi). PEP 695 sintaksisi (3.12+)TypeVare'lonlarini keraksiz qildi va variantlikni avtomatik aniqlaydi; generik klasslar,dataclasslar va taxalluslar ham shu sintaksis bilan yoziladi.Chegara, cheklov va variantlik — aniq ma'noga ega.
[T: float]— "shu tur yoki vorisi" (aralash qiymatlar mumkin),[T: (int, str)]— "faqat shu turlardan biri". O'zgartiriladigan konteynerlar (list,dict) invariant, chunki aks holdalist[int]ga satr qo'shib yuborish mumkin bo'lardi; faqat o'qiladiganSequence,Iterable,Mappingesa kovariant.Self,NewTypevaParamSpec— kundalik vositalar.Selfzanjirli metodlarda voris klass turini saqlaydi;NewTypebir xil ko'rinishdagi identifikatorlarni ajratadi (ish vaqtida tekin, tekshiruvchi uchun alohida tur);ParamSpecesa dekoratorlarda o'ralgan funksiyaning imzosini saqlaydi —Callable[..., Any]bilan u yo'qoladi.
Keyingi darsda strukturaviy turlashga o'tamiz: Protocol bilan "duck typing" ni tekshiruvchiga tushuntirish, runtime_checkable, standart protokollar va meros o'rniga interfeys yondashuvi.
Izohlar (0)
Izoh yozish uchun kiring.
- Hozircha izoh yo'q. Birinchi bo'ling!