IlmHamroh
Python kursi/Turlar tizimi4/9-dars22 daqiqa
Mundarija (21)

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:

python
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
  • ParamSpec va Concatenate — dekoratorlarni turlangan qilish
  • Amaliy: turlangan kesh va repozitoriy

2. Nazariya — chuqur tushuntirish

2.1. Generik funksiyalar

python
def birinchi[T](elementlar: Sequence[T]) -> T:
    return elementlar[0]

T — tur o'zgaruvchisi: chaqiruv paytida aniq turga bog'lanadi.

python
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

python
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 vaqtida Quti.__type_params__ bilan ko'rish mumkin
  • @dataclass ham 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:

python
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'lmaydi

Chegara — "kamida shu imkoniyatlar bo'lsin"; cheklovlar — "faqat shu ro'yxatdan".

2.4. Sukut qiymatli tur parametri (PEP 696)

python
def yukla[T = dict[str, object]](xom: str, tur: type[T] = dict) -> T: ...

class Kesh[K, V = str]: ...            # Kesh[int] → V = str

3.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?

python
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":

python
class Sorov:
    def filtr(self, **shartlar: object) -> Self:
        ...
        return self

Voris klassda ham to'g'ri ishlaydi: AdminSorov().filtr() → AdminSorov (oddiy -> "Sorov" bo'lsa, tur yo'qolardi).

NewType — mavjud tur asosida alohida tur:

python
FoydalanuvchiID = NewType("FoydalanuvchiID", int)
BuyurtmaID = NewType("BuyurtmaID", int)

def bekor_qil(buyurtma: BuyurtmaID, kim: FoydalanuvchiID) -> None: ...

bekor_qil(FoydalanuvchiID(5), BuyurtmaID(7))    # ❌ tekshiruvchi xato beradi
Taxallus (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:

python
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 ishlatiladi
  • Concatenate[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+)

python
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 → kontravariant

Maxsus

python
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

python
"""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:

text
=== 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

python
"""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:

text
=== 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: Pul

Nima ko'rsatdi: 2.3, 2.4-bo'limlar.

Misol 3 — Variantlik, Self va NewType

python
"""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:

text
=== 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 int

Nima 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.

python
"""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:

text
=== 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: Klaviatura

Nima 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

python
def birinchi(x: list) -> Any: ...            # ❌
def birinchi[T](x: Sequence[T]) -> T: ...    # ✅

2. Bir nechta parametrni bitta T bilan bog'lash

python
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

python
def ishla(elementlar: list[object]) -> None: ...   # ⚠️ list[int] berib bo'lmaydi
def ishla(elementlar: Sequence[object]) -> None:   # ✅ faqat o'qiydi

4. Dekoratorda imzoni yo'qotish

python
def dek(f: Callable[..., Any]) -> Callable[..., Any]: ...   # ❌
def dek[**P, R](f: Callable[P, R]) -> Callable[P, R]: ...   # ✅

5. NewType ni klass deb ishlatish

python
class FoydalanuvchiID(int): pass             # ⚠️ ish vaqtida yangi klass — ortiqcha narx
FoydalanuvchiID = NewType("FoydalanuvchiID", int)   # ✅ tekin

6. Eski TypeVar uslubini aralashtirish

python
T = TypeVar("T")
def f[T](x: T) -> T: ...                     # ⚠️ ikki xil T — chalkashlik

7. Chegara o'rniga cheklov ishlatish

python
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

python
@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 — ParamSpec ular 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 — TypeVarTuple qo'llanadigan soha

8. Eng yaxshi amaliyotlar

  1. Yangi kodda PEP 695 sintaksisi (def f[T], class K[T]).

  2. Turni saqlang: Any o'rniga tur parametri.

  3. O'qish uchun kovariant, o'zgartirish uchun invariant turlar.

  4. Dekoratorlarda ParamSpec — imzo yo'qolmasin.

  5. Bir xil ko'rinishdagi identifikatorlarni NewType bilan ajrating.

  6. Zanjirli metodlarda Self.

  7. Cheklov emas, chegara — aralash turlar kerak bo'lsa.

  8. Generiklarni haddan oshirmang: ikki-uch parametrdan ko'pi o'qishni qiyinlashtiradi.


9. Amaliy topshiriq

Vazifa 1: Natijani bashorat qiling

python
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
  1. (T,)
  2. 5 a
  3. (T,)
  4. True — parametrlashtirish yangi klass yaratmaydi
  5. <class 'float'>
  6. (<class 'int'>, <class 'str'>)
  7. True <class 'int'>
  8. 5 <class 'int'> 6 — ish vaqtida oddiy int
  9. <class 'int'>
  10. (T,) Juft[int] — taxallus indekslanganda o'z nomi bilan ko'rinadi (asl turi Juft.__value__)
  11. B — Self voris klassni saqlaydi
  12. typing.Self

Vazifa 2: Xatolarni tuzating

python
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
python
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 self

Vazifa 3: Generik kolleksiyalar

Quyidagilarni generik qilib yozing va sinovdan o'tkazing:

  1. Navbat[T] — qo'sh, ol, __len__, bo'shmi
  2. Kesh[K, V] — hajm chegarasi va LRU siqib chiqarish bilan
  3. Natija[T, X] — muvaffaqiyat (T) yoki xato (X) ni ifodalaydi (Rust Result kabi)
  4. Har biri uchun mypy o'tadigan misollar va o'tmaydigan misollar yozing (13.7-darsda tekshirasiz)

Vazifa 4: ParamSpec bilan dekoratorlar to'plami

  1. qayta_urin(urinishlar=3) — imzoni saqlaydigan qayta urinish dekoratori
  2. vaqt_olch — bajarilish vaqtini yozadigan dekorator
  3. Concatenate bilan ulanish_bilan — birinchi argument sifatida ulanish qo'shadigan dekorator
  4. Har biri uchun functools.wraps va __wrapped__ ni tekshiring (10.11-dars)
  5. Dekoratorlar tartibini o'zgartirib, natijani solishtiring

Vazifa 5: NewType auditi

O'z loyihangizda (yoki namunada):

  1. Bir xil turdagi (int, str) identifikatorlarni toping
  2. Ular uchun NewType lar e'lon qiling va imzolarni yangilang
  3. Chegarada o'girish funksiyalarini yozing (foydalanuvchi_id(xom: object) -> FoydalanuvchiID)
  4. Almashtirib yuborilishi mumkin bo'lgan joylarni sanab, hisobot yozing

Vazifa 6: Variantlik tajribalari

  1. list, Sequence, Iterable, Callable uchun kichik funksiyalar yozing
  2. Har birida "kengroq tur" berishga urinib ko'ring va tekshiruvchi qanday javob berishini yozib oling
  3. list[int] ni list[object] sifatida berib, ish vaqtida qanday buzilish yuz berishini ko'rsating (Misol 3)
  4. 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

python
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

  1. Python annotatsiyalarni ish vaqtida majburlamaydi (13.1-dars) — generiklar ham shu qoidaga bo'ysunadi
  2. Ish vaqtida tur parametrlarini saqlash har obyektga qo'shimcha xotira va narx qo'shardi
  3. Dinamik til uchun "tur bo'yicha ixtisoslashuv" ning foydasi kam: CPython baribir interpretator (12.8-dars)

4. Amaliy tavsiyalar

  1. Generiklardan hujjat va tekshiruv vositasi sifatida foydalaning
  2. Ish vaqtida tur kerak bo'lsa — uni aniq uzating (type[T]) yoki pydantic (13.8-dars) ishlating
  3. isinstance bilan parametrlashtirilgan turni tekshirishga urinmang
  4. 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:

  1. Generiklar turni yo'qotmaydi. def birinchi[T](x: Sequence[T]) -> T — chaqiruv paytida T aniq turga bog'lanadi, shuning uchun natija turi ham ma'lum bo'ladi (Any bilan esa yo'qoladi). PEP 695 sintaksisi (3.12+) TypeVar e'lonlarini keraksiz qildi va variantlikni avtomatik aniqlaydi; generik klasslar, dataclass lar va taxalluslar ham shu sintaksis bilan yoziladi.

  2. 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 holda list[int] ga satr qo'shib yuborish mumkin bo'lardi; faqat o'qiladigan Sequence, Iterable, Mapping esa kovariant.

  3. Self, NewType va ParamSpec — kundalik vositalar. Self zanjirli metodlarda voris klass turini saqlaydi; NewType bir xil ko'rinishdagi identifikatorlarni ajratadi (ish vaqtida tekin, tekshiruvchi uchun alohida tur); ParamSpec esa 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.

Ulashish:Telegram'da

Izohlar (0)

Izoh yozish uchun kiring.

  • Hozircha izoh yo'q. Birinchi bo'ling!
13.4-dars: TypeVar va generiklar — IlmHamroh