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

13.6-dars: TypedDict va NamedTuple

13-QISM — TURLAR TIZIMI · 6-dars


1. Kirish va motivatsiya

Python kodida ma'lumot ko'pincha lug'at yoki kortejda yuradi: JSON javoblari, CSV qatorlari, konfiguratsiya, funksiyadan qaytgan bir nechta qiymat. Ular qulay, lekin turlar tizimi uchun ko'r nuqta:

python
def foydalanuvchi_ol(id: int) -> dict: ...

f = foydalanuvchi_ol(5)
print(f["ismi"])          # ❌ kalit "ism" edi — xato faqat ish vaqtida

Real vaziyat. Mobil ilova API si {"user_id": ..., "full_name": ...} qaytarardi. Backend jamoasi maydonni fullName ga o'zgartirdi. Python mijozida dict ishlatilgani uchun hech qanday ogohlantirish bo'lmadi — xato faqat foydalanuvchilar profil sahifasi bo'sh chiqqanda ma'lum bo'ldi.

TypedDict aynan shu bo'shliqni to'ldiradi: lug'at qanday kalitlarga ega ekanini turlar tilida yozadi — ish vaqtida esa u oddiy dict bo'lib qolaveradi (tezlik va JSON mosligi saqlanadi).

Bu darsda:

  • TypedDict: kalitlari ma'lum lug'atlar
  • Majburiy va ixtiyoriy kalitlar: total=False, Required, NotRequired
  • ReadOnly 3.13-bob va meros
  • NamedTuple: turlangan kortej, _asdict, _replace
  • TypedDict / NamedTuple / dataclass — qaysi birini qachon
  • Ish vaqtidagi xulq: hech qanday tekshiruv yo'q
  • Amaliy: API javobidan domen modeliga

2. Nazariya — chuqur tushuntirish

2.1. TypedDict

python
from typing import TypedDict


class Foydalanuvchi(TypedDict):
    id: int
    ism: str
    email: str
python
f: Foydalanuvchi = {"id": 1, "ism": "Aziz", "email": "aziz@wisar.uz"}
f["ismi"]        # ❌ tekshiruvchi: bunday kalit yo'q
f["id"] + 1      # ✅ int ekani ma'lum

Ish vaqtida Foydalanuvchi — oddiy dict: qo'shimcha xotira yo'q, json.dumps to'g'ridan-to'g'ri ishlaydi, isinstance esa ishlamaydi (TypeError).

2.2. Majburiy va ixtiyoriy kalitlar

python
class Manzil(TypedDict):
    shahar: str                      # majburiy
    kocha: NotRequired[str]          # ixtiyoriy


class Sozlama(TypedDict, total=False):
    tema: str                        # ixtiyoriy (total=False)
    til: Required[str]               # majburiy
Vosita Ma'nosi
Oddiy maydon Majburiy
NotRequired[T] Ixtiyoriy
total=False Hamma maydon ixtiyoriy (bundan Required[T] mustasno)
ReadOnly[T] (3.13) O'zgartirib bo'lmaydi (tekshiruvchi uchun)

Ish vaqtida ko'rish: Klass.__required_keys__, __optional_keys__, __readonly_keys__.

2.3. Meros va ichma-ich tuzilmalar

python
class Asos(TypedDict):
    id: int


class Kengaytirilgan(Asos):
    qoshimcha: str


class Javob(TypedDict):
    foydalanuvchi: Foydalanuvchi        # ichma-ich
    natijalar: list[Manzil]

Funksional sintaksis (kalit nomi Python identifikatori bo'lmasa):

python
Qator = TypedDict("Qator", {"sana": str, "umumiy-summa": float})

2.4. NamedTuple

python
class Nuqta(NamedTuple):
    x: float
    y: float = 0.0

    def masofa(self) -> float:
        return (self.x ** 2 + self.y ** 2) ** 0.5
Xususiyat Tafsilot
Kortej Indekslash, ochib olish (x, y = nuqta), len
O'zgarmas Maydonni o'zgartirib bo'lmaydi
_asdict() Lug'atga o'girish (JSON uchun)
_replace(**) O'zgartirilgan nusxa
_fields, _field_defaults Metama'lumot
Metodlar va xossalar Qo'shish mumkin

Kortej bo'lgani uchun boshqa kortejlar bilan teng bo'lishi mumkin: Nuqta(1, 2) == (1, 2) → True.

2.5. Qaysi birini qachon

TypedDict NamedTuple dataclass
Ish vaqtida dict tuple Obyekt (__slots__ bilan ixcham)
O'zgaruvchanlik O'zgaruvchan O'zgarmas Tanlanadi (frozen=True)
JSON To'g'ridan-to'g'ri _asdict() asdict()
Metodlar
Validatsiya Yo'q Yo'q __post_init__
Xotira (3 maydon, o'lchangan) Eng ko'p: ~216 bayt ~112 bayt slots=True bilan eng kam: ~88 bayt
Qachon Tashqi JSON/API shakli Yengil qiymat, ko'p nusxa Domen modeli, xulq bilan

Amaliy naqsh: chegarada TypedDict (tashqi shakl), ichkarida dataclass (domen modeli).

2.6. Ish vaqtida hech qanday tekshiruv yo'q

python
f: Foydalanuvchi = {"id": 1}               # ⚠️ 'ism' yo'q — ish vaqtida xato yo'q
f["qoshimcha"] = 5                          # ⚠️ ortiqcha kalit ham o'tadi
isinstance(f, Foydalanuvchi)                # ❌ TypeError

Shuning uchun tashqi ma'lumot uchun:

  1. Statik tekshiruv — kod ichida
  2. Ish vaqtidagi validatsiya — chegarada: qo'lda tekshiruv yoki pydantic (13.8-dars)

2.7. TypedDict bilan ishlash naqshlari

python
def ism_ol(f: Foydalanuvchi) -> str:
    return f["ism"]                         # ✅ majburiy kalit

def kocha_ol(m: Manzil) -> str | None:
    return m.get("kocha")                   # ✅ ixtiyoriy kalit — .get()
  • Majburiy kalit — f["kalit"]
  • Ixtiyoriy kalit — f.get("kalit") yoki "kalit" in f bilan tekshirish
  • Yangilash: yangi: Foydalanuvchi = {**f, "ism": "Yangi"}

3. Tez ma'lumotnoma

python
class Foydalanuvchi(TypedDict):
    id: ReadOnly[int]
    ism: str
    manzil: NotRequired[Manzil]

class Sozlama(TypedDict, total=False):
    til: Required[str]
    tema: str

Qator = TypedDict("Qator", {"umumiy-summa": float})      # funksional

class Nuqta(NamedTuple):
    x: float
    y: float = 0.0

Introspeksiya

Foydalanuvchi.__required_keys__   __optional_keys__   __readonly_keys__
typing.is_typeddict(X)
Nuqta._fields   Nuqta._field_defaults   n._asdict()   n._replace(x=1)

4. Batafsil misollar

Misol 1 — TypedDict asoslari

python
"""Kalitlar va turlar; majburiy/ixtiyoriy; meros; ichma-ich tuzilma; ish vaqtida oddiy dict ekani."""

import json
import typing
from typing import NotRequired, ReadOnly, Required, TypedDict


class Manzil(TypedDict):
    shahar: str
    kocha: NotRequired[str]


class Foydalanuvchi(TypedDict):
    id: ReadOnly[int]
    ism: str
    email: str
    manzil: NotRequired[Manzil]


class Admin(Foydalanuvchi):
    huquqlar: list[str]


class Sozlama(TypedDict, total=False):
    tema: str
    til: Required[str]


print("=== 1. Kalitlar tuzilishi ===")
for klass in (Manzil, Foydalanuvchi, Admin, Sozlama):
    print(f"  {klass.__name__:14} majburiy: {sorted(klass.__required_keys__)}, "
          f"ixtiyoriy: {sorted(klass.__optional_keys__)}")
print(f"  faqat o'qiladigan kalitlar (Foydalanuvchi): {sorted(Foydalanuvchi.__readonly_keys__)}")
print(f"  turlar: {typing.get_type_hints(Manzil)}")

print("\n=== 2. Ish vaqtida — oddiy dict ===")
aziz: Foydalanuvchi = {"id": 1, "ism": "Aziz", "email": "aziz@wisar.uz"}
print(f"  turi: {type(aziz).__name__}, dict mi: {isinstance(aziz, dict)}")
print(f"  qiymat: {aziz}")
print(f"  JSON: {json.dumps(aziz, ensure_ascii=False)}")
try:
    isinstance(aziz, Foydalanuvchi)
except TypeError as xato:
    print(f"  isinstance: TypeError: {xato}")
print(f"  is_typeddict: {typing.is_typeddict(Foydalanuvchi)}")

print("\n=== 3. Ichma-ich va meros ===")
dilnoza: Admin = {
    "id": 2,
    "ism": "Dilnoza",
    "email": "dilnoza@wisar.uz",
    "manzil": {"shahar": "Toshkent", "kocha": "Amir Temur 15"},
    "huquqlar": ["oqish", "yozish"],
}
print(f"  admin: {dilnoza['ism']}, shahar: {dilnoza['manzil']['shahar']}, huquqlar: {dilnoza['huquqlar']}")


def kocha_ol(manzil: Manzil) -> str:
    return manzil.get("kocha", "ko'rsatilmagan")      # ixtiyoriy kalit — .get()


print(f"  ko'cha bor: {kocha_ol(dilnoza['manzil'])}")
print(f"  ko'cha yo'q: {kocha_ol({'shahar': 'Buxoro'})}")

print("\n=== 4. ⚠️ Ish vaqtida tekshiruv yo'q ===")
notogri: Foydalanuvchi = {"id": 3}                      # 'ism' va 'email' yo'q
print(f"  yetishmayotgan kalitlar bilan yaratildi: {notogri}")
notogri["qoshimcha"] = "ortiqcha"                        # type: ignore[typeddict-unknown-key]
print(f"  ortiqcha kalit ham qo'shildi: {notogri}")
try:
    print(notogri["ism"])
except KeyError as xato:
    print(f"  o'qishda: KeyError: {xato}")
print("  ⭐ tekshiruvchi bu uch xatoni ham kod yozilayotganda ko'rsatadi")

print("\n=== 5. Chegarada tekshirish ===")


def foydalanuvchi_tekshir(xom: object) -> Foydalanuvchi | str:
    if not isinstance(xom, dict):
        return "lug'at emas"
    yetishmayotgan = Foydalanuvchi.__required_keys__ - xom.keys()
    if yetishmayotgan:
        return f"yetishmayotgan kalitlar: {sorted(yetishmayotgan)}"
    if not isinstance(xom.get("id"), int) or not isinstance(xom.get("ism"), str):
        return "turlar mos emas"
    return typing.cast(Foydalanuvchi, xom)


for xom in [{"id": 9, "ism": "Jasur", "email": "j@wisar.uz"}, {"id": 9}, {"id": "to'qqiz", "ism": "J", "email": "x"}, [1, 2]]:
    natija = foydalanuvchi_tekshir(xom)
    print(f"  {str(xom)[:44]:46} → {natija if isinstance(natija, str) else 'OK: ' + natija['ism']}")

Natijaning muhim qismi:

text
=== 1. Kalitlar tuzilishi ===
  Manzil         majburiy: ['shahar'], ixtiyoriy: ['kocha']
  Foydalanuvchi  majburiy: ['email', 'id', 'ism'], ixtiyoriy: ['manzil']
  Admin          majburiy: ['email', 'huquqlar', 'id', 'ism'], ixtiyoriy: ['manzil']
  Sozlama        majburiy: ['til'], ixtiyoriy: ['tema']
  faqat o'qiladigan kalitlar (Foydalanuvchi): ['id']
  turlar: {'shahar': <class 'str'>, 'kocha': <class 'str'>}

=== 2. Ish vaqtida — oddiy dict ===
  turi: dict, dict mi: True
  qiymat: {'id': 1, 'ism': 'Aziz', 'email': 'aziz@wisar.uz'}
  JSON: {"id": 1, "ism": "Aziz", "email": "aziz@wisar.uz"}
  isinstance: TypeError: TypedDict does not support instance and class checks
  is_typeddict: True

=== 3. Ichma-ich va meros ===
  admin: Dilnoza, shahar: Toshkent, huquqlar: ['oqish', 'yozish']
  ko'cha bor: Amir Temur 15
  ko'cha yo'q: ko'rsatilmagan

=== 4. ⚠️ Ish vaqtida tekshiruv yo'q ===
  yetishmayotgan kalitlar bilan yaratildi: {'id': 3}
  ortiqcha kalit ham qo'shildi: {'id': 3, 'qoshimcha': 'ortiqcha'}
  o'qishda: KeyError: 'ism'
  ⭐ tekshiruvchi bu uch xatoni ham kod yozilayotganda ko'rsatadi

=== 5. Chegarada tekshirish ===
  {'id': 9, 'ism': 'Jasur', 'email': 'j@wisar.   → OK: Jasur
  {'id': 9}                                      → yetishmayotgan kalitlar: ['email', 'ism']
  {'id': "to'qqiz", 'ism': 'J', 'email': 'x'}    → turlar mos emas
  [1, 2]                                         → lug'at emas

Nima ko'rsatdi: 2.1–2.3, 2.6-bo'limlar.

Misol 2 — NamedTuple

python
"""Turlangan kortej; sukut qiymatlar; metodlar; _asdict/_replace; kortej bilan tenglik tuzog'i; ochib olish."""

import json
import typing
from typing import NamedTuple


class Nuqta(NamedTuple):
    x: float
    y: float = 0.0

    def masofa(self) -> float:
        return (self.x ** 2 + self.y ** 2) ** 0.5

    def siljitilgan(self, dx: float = 0.0, dy: float = 0.0) -> "Nuqta":
        return self._replace(x=self.x + dx, y=self.y + dy)


class Olcham(NamedTuple):
    eni: int
    boyi: int

    @property
    def yuza(self) -> int:
        return self.eni * self.boyi


print("=== 1. Asoslar ===")
n = Nuqta(3, 4)
print(f"  {n}, x={n.x}, indeks bo'yicha [1]={n[1]}, len={len(n)}")
print(f"  masofa: {n.masofa()}, sukut y bilan: {Nuqta(5)}")
x, y = n
print(f"  ochib olish: x={x}, y={y}")
print(f"  maydonlar: {Nuqta._fields}, sukutlar: {Nuqta._field_defaults}")

print("\n=== 2. O'zgarmaslik va nusxalar ===")
try:
    n.x = 10          # type: ignore[misc]
except AttributeError as xato:
    print(f"  o'zgartirishga urinish: AttributeError: {xato}")
print(f"  _replace: {n._replace(y=9)}, asl: {n}")
print(f"  siljitilgan: {n.siljitilgan(dx=1, dy=-1)}")
print(f"  xeshlanadi: {hash(n) == hash(Nuqta(3, 4))}, to'plamda: {len({Nuqta(3, 4), Nuqta(3, 4), Nuqta(0, 0)})} ta")

print("\n=== 3. JSON va lug'at ===")
print(f"  _asdict(): {n._asdict()}")
print(f"  JSON: {json.dumps(n._asdict())}")
print(f"  lug'atdan: {Nuqta(**{'x': 1.5, 'y': 2.5})}")
print(f"  kortejdan: {Nuqta._make([7, 8])}")

print("\n=== 4. ⚠️ Kortej bilan tenglik ===")
print(f"  Nuqta(3, 4) == (3, 4): {Nuqta(3, 4) == (3, 4)}")
print(f"  Nuqta(3, 4) == Olcham(3, 4): {Nuqta(3, 4) == Olcham(3, 4)}  ← turlar boshqa, lekin tengligi True")
print(f"  turni aniq tekshirish: {type(Nuqta(3, 4)) is type(Olcham(3, 4))}")
print("  ⭐ qat'iy ajratish kerak bo'lsa — dataclass yoki NewType")

print("\n=== 5. Ko'p qiymat qaytarish ===")


class Statistika(NamedTuple):
    eng_kichik: float
    eng_katta: float
    ortacha: float


def statistika(qiymatlar: list[float]) -> Statistika:
    return Statistika(min(qiymatlar), max(qiymatlar), sum(qiymatlar) / len(qiymatlar))


s = statistika([4.5, 2.0, 9.25, 7.0])
print(f"  {s}")
print(f"  nom bilan: eng kattasi = {s.eng_katta}, o'rtacha = {s.ortacha:.2f}")
print(f"  eski uslubda ochib olish ham ishlaydi: {statistika([1.0, 3.0])}")
print(f"  Olcham.yuza xossasi: {Olcham(3, 4).yuza}")
print(f"  izohlar: {typing.get_type_hints(Statistika)}")

Natijaning muhim qismi:

text
=== 1. Asoslar ===
  Nuqta(x=3, y=4), x=3, indeks bo'yicha [1]=4, len=2
  masofa: 5.0, sukut y bilan: Nuqta(x=5, y=0.0)
  ochib olish: x=3, y=4
  maydonlar: ('x', 'y'), sukutlar: {'y': 0.0}

=== 2. O'zgarmaslik va nusxalar ===
  o'zgartirishga urinish: AttributeError: can't set attribute
  _replace: Nuqta(x=3, y=9), asl: Nuqta(x=3, y=4)
  siljitilgan: Nuqta(x=4, y=3)
  xeshlanadi: True, to'plamda: 2 ta

=== 3. JSON va lug'at ===
  _asdict(): {'x': 3, 'y': 4}
  JSON: {"x": 3, "y": 4}
  lug'atdan: Nuqta(x=1.5, y=2.5)
  kortejdan: Nuqta(x=7, y=8)

=== 4. ⚠️ Kortej bilan tenglik ===
  Nuqta(3, 4) == (3, 4): True
  Nuqta(3, 4) == Olcham(3, 4): True  ← turlar boshqa, lekin tengligi True
  turni aniq tekshirish: False
  ⭐ qat'iy ajratish kerak bo'lsa — dataclass yoki NewType

=== 5. Ko'p qiymat qaytarish ===
  Statistika(eng_kichik=2.0, eng_katta=9.25, ortacha=5.6875)
  nom bilan: eng kattasi = 9.25, o'rtacha = 5.69
  eski uslubda ochib olish ham ishlaydi: Statistika(eng_kichik=1.0, eng_katta=3.0, ortacha=2.0)
  Olcham.yuza xossasi: 12
  izohlar: {'eng_kichik': <class 'float'>, 'eng_katta': <class 'float'>, 'ortacha': <class 'float'>}

Nima ko'rsatdi: 2.4-bo'lim.

Misol 3 — TypedDict, NamedTuple, dataclass solishtiruvi

python
"""Bir xil ma'lumot uch shaklda: xotira, yaratish tezligi, imkoniyatlar; qaysi birini qachon tanlash."""

import dataclasses
import gc
import sys
import time
import tracemalloc
import typing
from typing import NamedTuple, TypedDict


class YozuvTD(TypedDict):
    id: int
    nom: str
    narx: float


class YozuvNT(NamedTuple):
    id: int
    nom: str
    narx: float


@dataclasses.dataclass(slots=True)
class YozuvDC:
    id: int
    nom: str
    narx: float


@dataclasses.dataclass(frozen=True, slots=True)
class YozuvDCF:
    id: int
    nom: str
    narx: float


N = 100_000


def yasovchilar():
    return {
        "TypedDict": lambda i: YozuvTD(id=i, nom="mahsulot", narx=1.5),
        "NamedTuple": lambda i: YozuvNT(i, "mahsulot", 1.5),
        "dataclass(slots)": lambda i: YozuvDC(i, "mahsulot", 1.5),
        "dataclass(frozen)": lambda i: YozuvDCF(i, "mahsulot", 1.5),
    }


def xotira(yasovchi) -> int:
    gc.collect()
    tracemalloc.start()
    yozuvlar = [yasovchi(i) for i in range(N)]
    jami, _ = tracemalloc.get_traced_memory()
    tracemalloc.stop()
    natija = round((jami - sys.getsizeof(yozuvlar)) / N)
    del yozuvlar
    return natija


def tezlik(yasovchi, takror=3) -> float:
    eng = float("inf")
    for _ in range(takror):
        bosh = time.perf_counter()
        for i in range(50_000):
            yasovchi(i)
        eng = min(eng, time.perf_counter() - bosh)
    return eng


print("=== 1. Bir nusxaga xotira ===")
olchovlar = {nom: xotira(y) for nom, y in yasovchilar().items()}
for nom, bayt in olchovlar.items():
    print(f"  {nom:20} {bayt:>4} bayt")
print(f"  eng ixcham: {min(olchovlar, key=olchovlar.get)}, eng ko'p joy oladigan: {max(olchovlar, key=olchovlar.get)}")

print("\n=== 2. Yaratish tezligi ===")
tezliklar = {nom: tezlik(y) for nom, y in yasovchilar().items()}
eng_tez = min(tezliklar, key=tezliklar.get)
print(f"  eng tez yaratiladigan: {eng_tez}")
print(f"  dict literal (TypedDict) yaratish NamedTuple dan tez: {tezliklar['TypedDict'] < tezliklar['NamedTuple']}")
print(f"  NamedTuple esa xotirada ixchamroq: {olchovlar['NamedTuple'] < olchovlar['TypedDict']}")

print("\n=== 3. Imkoniyatlar ===")
td = YozuvTD(id=1, nom="Klaviatura", narx=250_000.0)
nt = YozuvNT(1, "Klaviatura", 250_000.0)
dc = YozuvDC(1, "Klaviatura", 250_000.0)
dcf = YozuvDCF(1, "Klaviatura", 250_000.0)

print(f"  {'imkoniyat':28} {'TypedDict':>10} {'NamedTuple':>11} {'dataclass':>10}")
qatorlar = [
    ("indeks bo'yicha kirish", "—", str(nt[0]), "—"),
    ("kalit/atribut", str(td["id"]), str(nt.id), str(dc.id)),
    ("o'zgartirish", "✅", "❌", "✅"),
    ("xeshlanadi", "❌", "✅", "frozen bilan ✅"),
    ("JSON ga tayyor", "✅", "_asdict()", "asdict()"),
    ("metodlar", "❌", "✅", "✅"),
]
for imkoniyat, a, b, c in qatorlar:
    print(f"  {imkoniyat:28} {a:>10} {b:>11} {c:>10}")

print("\n=== 4. Amaliy tanlov ===")
print(f"  JSON javobi (tashqi shakl): TypedDict → {dict(td)}")
print(f"  yengil qiymat (ko'p nusxa):  NamedTuple → {nt}")
print(f"  domen modeli (xulq bilan):   dataclass → {dc}")
print(f"  kalit sifatida / o'zgarmas:  frozen dataclass yoki NamedTuple → xeshi bor: {hash(dcf) == hash(YozuvDCF(1, 'Klaviatura', 250_000.0))}")

Natijaning muhim qismi:

text
=== 1. Bir nusxaga xotira ===
  TypedDict             216 bayt
  NamedTuple            112 bayt
  dataclass(slots)       88 bayt
  dataclass(frozen)      88 bayt
  eng ixcham: dataclass(slots), eng ko'p joy oladigan: TypedDict

=== 2. Yaratish tezligi ===
  eng tez yaratiladigan: dataclass(slots)
  dict literal (TypedDict) yaratish NamedTuple dan tez: True
  NamedTuple esa xotirada ixchamroq: True

=== 3. Imkoniyatlar ===
  imkoniyat                     TypedDict  NamedTuple  dataclass
  indeks bo'yicha kirish                —           1          —
  kalit/atribut                         1           1          1
  o'zgartirish                          ✅           ❌          ✅
  xeshlanadi                            ❌           ✅ frozen bilan ✅
  JSON ga tayyor                        ✅   _asdict()   asdict()
  metodlar                              ❌           ✅          ✅

=== 4. Amaliy tanlov ===
  JSON javobi (tashqi shakl): TypedDict → {'id': 1, 'nom': 'Klaviatura', 'narx': 250000.0}
  yengil qiymat (ko'p nusxa):  NamedTuple → YozuvNT(id=1, nom='Klaviatura', narx=250000.0)
  domen modeli (xulq bilan):   dataclass → YozuvDC(id=1, nom='Klaviatura', narx=250000.0)
  kalit sifatida / o'zgarmas:  frozen dataclass yoki NamedTuple → xeshi bor: True

Nima ko'rsatdi: 2.5-bo'lim.

Misol 4 — Amaliy: API javobidan domen modeliga

Tashqi API TypedDict shaklida javob qaytaradi (kalitlari snake_case, ba'zi maydonlar ixtiyoriy). Ichki kod esa qulay domen modeli bilan ishlaydi. Chegarada validatsiya va o'girish bo'ladi — shundan keyin ichkarida hech qanday dict va Any qolmaydi.

python
"""Tashqi TypedDict shakli; validatsiya; domen dataclass; xatolarni yig'ish; statistika."""

import dataclasses
import json
import typing
from decimal import Decimal, InvalidOperation
from typing import NotRequired, TypedDict


class ManzilJSON(TypedDict):
    shahar: str
    kocha: NotRequired[str]


class BuyurtmaJSON(TypedDict):
    order_id: int
    customer_name: str
    total_amount: str
    manzil: NotRequired[ManzilJSON]
    izoh: NotRequired[str]


@dataclasses.dataclass(frozen=True, slots=True)
class Manzil:
    shahar: str
    kocha: str | None = None

    def qisqa(self) -> str:
        return f"{self.shahar}" + (f", {self.kocha}" if self.kocha else "")


@dataclasses.dataclass(frozen=True, slots=True)
class Buyurtma:
    id: int
    mijoz: str
    summa: Decimal
    manzil: Manzil | None = None

    def katta_buyurtmami(self, chegara: Decimal = Decimal("200000")) -> bool:
        return self.summa >= chegara


def manzil_ogir(xom: object) -> Manzil | None:
    if not isinstance(xom, dict) or not isinstance(xom.get("shahar"), str):
        return None
    kocha = xom.get("kocha")
    return Manzil(xom["shahar"], kocha if isinstance(kocha, str) else None)


def buyurtma_ogir(xom: object) -> Buyurtma | str:
    """Tashqi shakldan domen modeliga; xato bo'lsa — sabab."""
    if not isinstance(xom, dict):
        return "lug'at emas"
    yetishmayotgan = BuyurtmaJSON.__required_keys__ - xom.keys()
    if yetishmayotgan:
        return f"yetishmayotgan kalitlar: {sorted(yetishmayotgan)}"
    if not isinstance(xom["order_id"], int) or isinstance(xom["order_id"], bool):
        return "order_id butun son emas"
    if not isinstance(xom["customer_name"], str) or not xom["customer_name"].strip():
        return "customer_name bo'sh"
    try:
        summa = Decimal(str(xom["total_amount"]))
    except InvalidOperation:
        return f"total_amount noto'g'ri: {xom['total_amount']!r}"
    if summa < 0:
        return "summa manfiy"
    return Buyurtma(xom["order_id"], xom["customer_name"].strip(), summa, manzil_ogir(xom.get("manzil")))


XOM = json.loads('''[
  {"order_id": 101, "customer_name": "Aziz", "total_amount": "250000.00",
   "manzil": {"shahar": "Toshkent", "kocha": "Amir Temur 15"}},
  {"order_id": 102, "customer_name": "Dilnoza", "total_amount": "120000", "izoh": "tezkor"},
  {"order_id": 103, "customer_name": "  Jasur  ", "total_amount": "99000.50",
   "manzil": {"shahar": "Buxoro"}},
  {"order_id": "104", "customer_name": "Xato", "total_amount": "1000"},
  {"order_id": 105, "customer_name": "", "total_amount": "1000"},
  {"order_id": 106, "customer_name": "Summa", "total_amount": "nomalum"},
  {"customer_name": "Kalitsiz", "total_amount": "5000"}
]''')

print("=== 1. Chegarada o'girish ===")
buyurtmalar: list[Buyurtma] = []
xatolar: list[str] = []
for xom in XOM:
    natija = buyurtma_ogir(xom)
    if isinstance(natija, str):
        xatolar.append(natija)
        print(f"  ❌ {natija}")
    else:
        buyurtmalar.append(natija)
        print(f"  ✅ {natija.id}: {natija.mijoz} — {natija.summa}")

print("\n=== 2. Ichkarida faqat domen modeli ===")
for b in buyurtmalar:
    manzil = b.manzil.qisqa() if b.manzil else "manzilsiz"
    print(f"  {b.id}: {b.mijoz:8} {b.summa:>10} {'KATTA' if b.katta_buyurtmami() else '     '}  {manzil}")

jami = sum((b.summa for b in buyurtmalar), Decimal(0))
print(f"\n=== 3. Xulosa ===")
print(f"  qabul qilingan: {len(buyurtmalar)}, rad etilgan: {len(xatolar)}")
print(f"  jami summa: {jami}, katta buyurtmalar: {sum(b.katta_buyurtmami() for b in buyurtmalar)}")
print(f"  domen modeli o'zgarmas: {dataclasses.fields(Buyurtma)[0].name}, frozen: {Buyurtma.__dataclass_params__.frozen}")
print(f"  tashqi shakl kalitlari: {sorted(BuyurtmaJSON.__required_keys__)} + ixtiyoriy {sorted(BuyurtmaJSON.__optional_keys__)}")
print("  ⭐ 'order_id' kabi tashqi nomlar faqat chegarada qoladi; ichkarida — 'id'")

Natijaning muhim qismi:

text
=== 1. Chegarada o'girish ===
  ✅ 101: Aziz — 250000.00
  ✅ 102: Dilnoza — 120000
  ✅ 103: Jasur — 99000.50
  ❌ order_id butun son emas
  ❌ customer_name bo'sh
  ❌ total_amount noto'g'ri: 'nomalum'
  ❌ yetishmayotgan kalitlar: ['order_id']

=== 2. Ichkarida faqat domen modeli ===
  101: Aziz      250000.00 KATTA  Toshkent, Amir Temur 15
  102: Dilnoza      120000        manzilsiz
  103: Jasur      99000.50        Buxoro

=== 3. Xulosa ===
  qabul qilingan: 3, rad etilgan: 4
  jami summa: 469000.50, katta buyurtmalar: 1
  domen modeli o'zgarmas: id, frozen: True
  tashqi shakl kalitlari: ['customer_name', 'order_id', 'total_amount'] + ixtiyoriy ['izoh', 'manzil']
  ⭐ 'order_id' kabi tashqi nomlar faqat chegarada qoladi; ichkarida — 'id'

Nima ko'rsatdi: 2.5–2.7-bo'limlar.


5. To'g'ri va noto'g'ri tushunishlar

Noto'g'ri fikr To'g'risi
"TypedDict yangi klass yaratadi" Ish vaqtida oddiy dict
"isinstance(x, MeningTD) ishlaydi" TypeError — TypedDict buni qo'llab-quvvatlamaydi
"TypedDict kalitlarni tekshiradi" Faqat tekshiruvchi; ish vaqtida hech narsa
"NamedTuple — eski namedtuple" Turlangan versiyasi; qo'shimcha metodlar mumkin
"NamedTuple boshqa kortej bilan teng emas" Nuqta(1, 2) == (1, 2) → True
"dataclass har doim yaxshiroq" Ko'p nusxada NamedTuple ixchamroq, JSON uchun TypedDict qulayroq
"total=False hamma joyda qulay" Ko'pincha NotRequired aniqroq
"ReadOnly o'zgartirishni taqiqlaydi" Faqat tekshiruvchi uchun; ish vaqtida dict o'zgaradi

6. Keng tarqalgan xatolar va yechimlari

1. dict bilan ishlash

python
def profil(f: dict) -> str:
    return f["ismi"]                         # ❌ xato kalit jim o'tadi
def profil(f: Foydalanuvchi) -> str:
    return f["ism"]                          # ✅

2. Ixtiyoriy kalitni to'g'ridan-to'g'ri olish

python
kocha = manzil["kocha"]                      # ❌ KeyError bo'lishi mumkin
kocha = manzil.get("kocha")                  # ✅

3. Tashqi ma'lumotni tekshirmasdan TypedDict deb e'lon qilish

python
malumot: Javob = json.loads(matn)            # ⚠️ hech narsa tekshirilmadi
malumot = javob_tekshir(json.loads(matn))    # ✅ chegarada validatsiya

4. NamedTuple ni katta modellar uchun ishlatish

python
class Buyurtma(NamedTuple):                  # ⚠️ 12 maydon, o'zgartirish kerak
    ...
@dataclasses.dataclass                        # ✅
class Buyurtma: ...

5. Kortej tengligiga tayanish

python
if natija == (200, "OK"):                    # ⚠️ boshqa NamedTuple ham teng bo'lishi mumkin
if isinstance(natija, Javob) and natija.kod == 200:   # ✅

6. TypedDict ga metod qo'shishga urinish

python
class Foydalanuvchi(TypedDict):
    def toliq_ism(self) -> str: ...          # ❌ TypedDict da metod bo'lmaydi

Funksiya yozing yoki dataclass ga o'ting.

7. total=False bilan majburiy maydonlarni yo'qotish

python
class Sozlama(TypedDict, total=False):
    til: str                                  # ⚠️ aslida majburiy edi
    til: Required[str]                        # ✅

8. Tashqi kalit nomlarini ichkariga tarqatish

python
def hisobot(b: BuyurtmaJSON) -> str:
    return f"{b['order_id']}"                 # ⚠️ tashqi nom butun kodga tarqaydi

Chegarada domen modeliga o'giring (Misol 4).


7. Integratsiya — bu bilim qayerda kerak bo'ladi

  • 6.6-dars (o'tilgan): namedtuple
  • 8.17-dars (o'tilgan): dataclass
  • 13.2–13.3-darslar (o'tilgan): kolleksiya turlari, NotRequired kabi vositalar
  • 13.8-dars: pydantic — validatsiya bilan model
  • 16-qism: JSON, CSV va boshqa formatlar
  • 20-qism: FastAPI — so'rov va javob modellari
  • 23-qism: baza yozuvlari va ORM modellari
  • 24-qism: jadvalli ma'lumot

8. Eng yaxshi amaliyotlar

  1. Tashqi JSON shakli uchun TypedDict, ichki model uchun dataclass.

  2. Ixtiyoriy kalitlarni NotRequired bilan aniq belgilang.

  3. Chegarada validatsiya qiling — TypedDict o'zi tekshirmaydi.

  4. Ko'p nusxali yengil qiymatlar uchun NamedTuple.

  5. _replace bilan o'zgartirilgan nusxa oling — o'zgarmaslikni saqlang.

  6. Kortej tengligiga tayanmang — turni tekshiring.

  7. Tashqi kalit nomlarini chegarada qoldiring.

  8. __required_keys__ va __optional_keys__ ni validatsiyada ishlating.


9. Amaliy topshiriq

Vazifa 1: Natijani bashorat qiling

python
import typing
from typing import TypedDict, NotRequired, NamedTuple
class M(TypedDict):
    a: int
    b: NotRequired[str]
class N(NamedTuple):
    x: int
    y: int = 0
1.  print(sorted(M.__required_keys__), sorted(M.__optional_keys__))
2.  m: M = {"a": 1}
    print(type(m).__name__, m)
3.  try:
        isinstance(m, M)
    except TypeError:
        print("TypeError")
4.  print(typing.is_typeddict(M), typing.is_typeddict(dict))
5.  n = N(3)
    print(n, n.y, len(n))
6.  print(n == (3, 0))
7.  print(n._asdict())
8.  print(n._replace(y=5), n)
9.  print(N._fields, N._field_defaults)
10. try:
        n.x = 5
    except AttributeError:
        print("AttributeError")
11. print(isinstance(n, tuple), isinstance(m, dict))
12. print(typing.get_type_hints(M))
Javoblar
  1. ['a'] ['b']
  2. dict {'a': 1}
  3. TypeError
  4. True False
  5. N(x=3, y=0) 0 2
  6. True — kortej bilan teng
  7. {'x': 3, 'y': 0}
  8. N(x=3, y=5) N(x=3, y=0)
  9. ('x', 'y') {'y': 0}
  10. AttributeError
  11. True True
  12. {'a': <class 'int'>, 'b': <class 'str'>}

Vazifa 2: Xatolarni tuzating

python
1.  def profil(f: dict) -> str:
        return f"{f['ism']} ({f['email']})"

2.  class Sozlama(TypedDict):
        til: str
        tema: str
    s: Sozlama = {"til": "uz"}        # tema ixtiyoriy bo'lishi kerak

3.  def shahar(m: Manzil) -> str:
        return m["kocha"]             # kocha — NotRequired

4.  malumot: Javob = json.loads(tashqi_matn)
    print(malumot["natijalar"][0]["id"])

5.  class Buyurtma(NamedTuple):
        id: int
        holat: str
    b = Buyurtma(1, "yangi")
    b.holat = "yakunlangan"
Javoblar
python
1.  class Foydalanuvchi(TypedDict):
        ism: str
        email: str
    def profil(f: Foydalanuvchi) -> str:
        return f"{f['ism']} ({f['email']})"

2.  class Sozlama(TypedDict):
        til: str
        tema: NotRequired[str]
    s: Sozlama = {"til": "uz"}

3.  def shahar(m: Manzil) -> str:
        return m.get("kocha", m["shahar"])          # ixtiyoriy kalit

4.  xom = json.loads(tashqi_matn)
    malumot = javob_tekshir(xom)                    # chegarada validatsiya
    if isinstance(malumot, str):
        raise ValueError(malumot)
    print(malumot["natijalar"][0]["id"])

5.  @dataclasses.dataclass                          # o'zgaradigan model
    class Buyurtma:
        id: int
        holat: str
    b = Buyurtma(1, "yangi")
    b.holat = "yakunlangan"
    # yoki NamedTuple qolsa: b = b._replace(holat="yakunlangan")

Vazifa 3: API shakllari

Ishlatayotgan biror ochiq API (masalan, ob-havo yoki valyuta kursi) uchun:

  1. Javob shakli uchun TypedDict lar yozing (ichma-ich tuzilmalar bilan)
  2. Ixtiyoriy maydonlarni NotRequired bilan belgilang
  3. Validatsiya funksiyasini yozing (__required_keys__ dan foydalaning)
  4. Domen dataclass lariga o'giring
  5. Real javobni saqlab, testlar yozing

Vazifa 4: Xotira va tezlik o'lchovi

Misol 3 ni kengaytiring:

  1. dict, TypedDict, NamedTuple, dataclass, dataclass(slots=True), dataclass(frozen=True) variantlarini solishtiring
  2. 1 000 000 yozuvda xotirani tracemalloc bilan o'lchang
  3. Yaratish, maydonga kirish va JSON ga o'girish tezligini alohida o'lchang
  4. Natijalarni jadval qilib, qaysi holatda qaysi birini tanlashni asoslang

Vazifa 5: TypedDict validatori

Umumiy validator yozing:

  1. tekshir(xom, TD) — TypedDict ta'rifiga qarab kalitlar va turlarni tekshiradi
  2. Ichma-ich TypedDict larni rekursiv tekshirsin
  3. NotRequired, Required, ReadOnly ni to'g'ri hisobga olsin (get_type_hints(include_extras=True))
  4. Xatolarni yo'l (natijalar[0].id) ko'rinishida qaytarsin
  5. pydantic bilan solishtiring (13.8-darsdan keyin)

Vazifa 6: Migratsiya

Mavjud kodda dict bilan ishlaydigan modulni turlangan qiling:

  1. Kod bo'ylab qanday kalitlar ishlatilishini AST bilan aniqlang
  2. Shu asosda TypedDict yozing
  3. Funksiya imzolarini yangilang va tekshiruvchini ishga tushiring
  4. Topilgan xatolarni (noto'g'ri kalit, yetishmayotgan tekshiruv) hisobot qiling

Vazifa 7: O'ylash

TypeScript'da obyekt shakllari interface va type bilan tabiiy ifodalanadi va ular tilning markazida; Go'da struct teglari JSON bilan bog'lanadi; Rust'da serde makroslari serializatsiyani turga bog'laydi. Python'da TypedDict, NamedTuple, dataclass va pydantic — to'rt xil yo'l. Bu xilma-xillik yaxshimi yoki chalkashlikmi?

Javob

Qisqa javob: xilma-xillik Python'ning bosqichma-bosqich rivojlanishidan kelib chiqqan: har vosita boshqa muammoni hal qiladi (shakl, yengil qiymat, domen modeli, validatsiya). Chalkashlik esa tanlov mezoni aniq bo'lmaganda paydo bo'ladi — shuning uchun jamoada qoida kelishib olinadi.

1. Boshqa tillarda

Til Yechim Xususiyat
TypeScript interface / type Shakl — tilning markazi; validatsiya alohida (zod)
Go struct + teglar Bitta tuzilma ham shakl, ham model; encoding/json
Rust struct + serde Makroslar kompilyatsiyada serializator yaratadi
Java Record + Jackson Yozuv turlari va annotatsiyalar
Python TypedDict, NamedTuple, dataclass, pydantic Har biri alohida maqsad uchun

2. Nega Python'da to'rt yo'l

  1. Tarix: namedtuple (2008) → NamedTuple (2016) → dataclass 3.7-bob → TypedDict 3.8-bob → pydantic (ekotizim)
  2. Turli ehtiyojlar: JSON shakli (dict bo'lib qolishi kerak), yengil qiymat (kortej), domen modeli (xulq), tashqi ma'lumot (validatsiya)
  3. Moslik: mavjud kod dict va tuple bilan to'lgan — ularni turlangan qilish uchun TypedDict/NamedTuple kerak edi

3. Tanlov mezoni (amaliy qoida)

Tashqi JSON/API shakli          → TypedDict
Ish vaqtida validatsiya kerak   → pydantic (13.8)
Domen modeli, xulq, o'zgarmaslik → dataclass
Ko'p nusxali yengil qiymat      → NamedTuple
Kalit sifatida / xeshlanishi kerak → frozen dataclass yoki NamedTuple

4. Xilma-xillikning narxi va foydasi

Foyda Narx
Har vazifa uchun eng mos vosita Yangi dasturchi uchun chalkashlik
Mavjud kod bilan moslik Loyihada bir nechta uslub aralashishi
Ish vaqtidagi narx nazorat ostida (dict — dict bo'lib qoladi) Konvertatsiya kodi yozish kerak

5. Xulosa

  1. To'rt vosita — to'rt muammo; "eng yaxshisi" yo'q
  2. Jamoada yozma qoida: chegarada nima, ichkarida nima
  3. Go va Rust'dagi kabi bitta tuzilmani hamma joyda ishlatish Python'da pydantic bilan yaqinlashadi — lekin uning ish vaqtidagi narxi bor
  4. Eng muhimi: tashqi shakl va domen modeli ni ajratish — qaysi vositani tanlashingizdan qat'i nazar

Nimani mustahkamlaydi: 2.1–2.7-bo'limlar.


Xulosa

Bu darsda lug'at va kortejlarni turlangan qilishni o'rgandik.

Eng muhim uch fikr:

  1. TypedDict — kalitlari ma'lum lug'at. U JSON va API javoblari uchun ideal: ish vaqtida oddiy dict bo'lib qoladi (tezlik, json.dumps, mavjud kod bilan moslik), tekshiruvchi esa noto'g'ri kalit va turlarni ushlaydi. NotRequired, Required, total=False va ReadOnly bilan shakl aniq belgilanadi; __required_keys__ va __optional_keys__ validatsiyada asqotadi. Lekin isinstance ishlamaydi va hech narsa tekshirilmaydi — chegarada o'zingiz tekshirasiz.

  2. NamedTuple — turlangan, o'zgarmas, ixcham qiymat. Indekslash, ochib olish, _asdict, _replace, metodlar va xossalar — hammasi bor; lug'atdan ikki barobar ixcham (lekin dataclass(slots=True) undan ham tejamkor). Tuzoq: u kortej bo'lgani uchun boshqa kortejlar bilan teng bo'lishi mumkin (Nuqta(1, 2) == (1, 2)).

  3. Chegarada shakl, ichkarida model. Tashqi ma'lumot TypedDict bilan ta'riflanadi va chegarada tekshiriladi; keyin domen dataclass iga o'giriladi — shundan so'ng ichki kodda dict ham, Any ham, tashqi kalit nomlari ham qolmaydi.

Keyingi darsda turlarni haqiqatan tekshiradigan vositaga o'tamiz: mypy ni o'rnatish, sozlash, xatolarini o'qish, bosqichma-bosqich qat'iylashtirish va CI ga qo'shish.

Ulashish:Telegram'da

Izohlar (0)

Izoh yozish uchun kiring.

  • Hozircha izoh yo'q. Birinchi bo'ling!
13.6-dars: TypedDict va NamedTuple — IlmHamroh