Mundarija (21)
- 1. Kirish va motivatsiya
- 2. Nazariya — chuqur tushuntirish
- 2.1. TypedDict
- 2.2. Majburiy va ixtiyoriy kalitlar
- 2.3. Meros va ichma-ich tuzilmalar
- 2.4. NamedTuple
- 2.5. Qaysi birini qachon
- 2.6. Ish vaqtida hech qanday tekshiruv yo'q
- 2.7. TypedDict bilan ishlash naqshlari
- 3. Tez ma'lumotnoma
- 4. Batafsil misollar
- Misol 1 — TypedDict asoslari
- Misol 2 — NamedTuple
- Misol 3 — TypedDict, NamedTuple, dataclass solishtiruvi
- Misol 4 — Amaliy: API javobidan domen modeliga
- 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.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:
def foydalanuvchi_ol(id: int) -> dict: ...
f = foydalanuvchi_ol(5)
print(f["ismi"]) # ❌ kalit "ism" edi — xato faqat ish vaqtidaReal 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 ReadOnly3.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
from typing import TypedDict
class Foydalanuvchi(TypedDict):
id: int
ism: str
email: strf: 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
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
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):
Qator = TypedDict("Qator", {"sana": str, "umumiy-summa": float})2.4. NamedTuple
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
f: Foydalanuvchi = {"id": 1} # ⚠️ 'ism' yo'q — ish vaqtida xato yo'q
f["qoshimcha"] = 5 # ⚠️ ortiqcha kalit ham o'tadi
isinstance(f, Foydalanuvchi) # ❌ TypeErrorShuning uchun tashqi ma'lumot uchun:
- Statik tekshiruv — kod ichida
- Ish vaqtidagi validatsiya — chegarada: qo'lda tekshiruv yoki
pydantic(13.8-dars)
2.7. TypedDict bilan ishlash naqshlari
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 fbilan tekshirish - Yangilash:
yangi: Foydalanuvchi = {**f, "ism": "Yangi"}
3. Tez ma'lumotnoma
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.0Introspeksiya
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
"""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:
=== 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 emasNima ko'rsatdi: 2.1–2.3, 2.6-bo'limlar.
Misol 2 — NamedTuple
"""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:
=== 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
"""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:
=== 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: TrueNima 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.
"""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:
=== 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
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
kocha = manzil["kocha"] # ❌ KeyError bo'lishi mumkin
kocha = manzil.get("kocha") # ✅3. Tashqi ma'lumotni tekshirmasdan TypedDict deb e'lon qilish
malumot: Javob = json.loads(matn) # ⚠️ hech narsa tekshirilmadi
malumot = javob_tekshir(json.loads(matn)) # ✅ chegarada validatsiya4. NamedTuple ni katta modellar uchun ishlatish
class Buyurtma(NamedTuple): # ⚠️ 12 maydon, o'zgartirish kerak
...
@dataclasses.dataclass # ✅
class Buyurtma: ...5. Kortej tengligiga tayanish
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
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
class Sozlama(TypedDict, total=False):
til: str # ⚠️ aslida majburiy edi
til: Required[str] # ✅8. Tashqi kalit nomlarini ichkariga tarqatish
def hisobot(b: BuyurtmaJSON) -> str:
return f"{b['order_id']}" # ⚠️ tashqi nom butun kodga tarqaydiChegarada 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,
NotRequiredkabi 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
Tashqi JSON shakli uchun
TypedDict, ichki model uchundataclass.Ixtiyoriy kalitlarni
NotRequiredbilan aniq belgilang.Chegarada validatsiya qiling —
TypedDicto'zi tekshirmaydi.Ko'p nusxali yengil qiymatlar uchun
NamedTuple._replacebilan o'zgartirilgan nusxa oling — o'zgarmaslikni saqlang.Kortej tengligiga tayanmang — turni tekshiring.
Tashqi kalit nomlarini chegarada qoldiring.
__required_keys__va__optional_keys__ni validatsiyada ishlating.
9. Amaliy topshiriq
Vazifa 1: Natijani bashorat qiling
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
['a'] ['b']dict {'a': 1}TypeErrorTrue FalseN(x=3, y=0) 0 2True— kortej bilan teng{'x': 3, 'y': 0}N(x=3, y=5) N(x=3, y=0)('x', 'y') {'y': 0}AttributeErrorTrue True{'a': <class 'int'>, 'b': <class 'str'>}
Vazifa 2: Xatolarni tuzating
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
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:
- Javob shakli uchun
TypedDictlar yozing (ichma-ich tuzilmalar bilan) - Ixtiyoriy maydonlarni
NotRequiredbilan belgilang - Validatsiya funksiyasini yozing (
__required_keys__dan foydalaning) - Domen
dataclasslariga o'giring - Real javobni saqlab, testlar yozing
Vazifa 4: Xotira va tezlik o'lchovi
Misol 3 ni kengaytiring:
dict,TypedDict,NamedTuple,dataclass,dataclass(slots=True),dataclass(frozen=True)variantlarini solishtiring- 1 000 000 yozuvda xotirani
tracemallocbilan o'lchang - Yaratish, maydonga kirish va JSON ga o'girish tezligini alohida o'lchang
- Natijalarni jadval qilib, qaysi holatda qaysi birini tanlashni asoslang
Vazifa 5: TypedDict validatori
Umumiy validator yozing:
tekshir(xom, TD)—TypedDictta'rifiga qarab kalitlar va turlarni tekshiradi- Ichma-ich
TypedDictlarni rekursiv tekshirsin NotRequired,Required,ReadOnlyni to'g'ri hisobga olsin (get_type_hints(include_extras=True))- Xatolarni yo'l (
natijalar[0].id) ko'rinishida qaytarsin pydanticbilan solishtiring (13.8-darsdan keyin)
Vazifa 6: Migratsiya
Mavjud kodda dict bilan ishlaydigan modulni turlangan qiling:
- Kod bo'ylab qanday kalitlar ishlatilishini AST bilan aniqlang
- Shu asosda
TypedDictyozing - Funksiya imzolarini yangilang va tekshiruvchini ishga tushiring
- 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
- Tarix:
namedtuple(2008) →NamedTuple(2016) →dataclass3.7-bob →TypedDict3.8-bob →pydantic(ekotizim) - Turli ehtiyojlar: JSON shakli (
dictbo'lib qolishi kerak), yengil qiymat (kortej), domen modeli (xulq), tashqi ma'lumot (validatsiya) - Moslik: mavjud kod
dictvatuplebilan to'lgan — ularni turlangan qilish uchunTypedDict/NamedTuplekerak 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 NamedTuple4. 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
- To'rt vosita — to'rt muammo; "eng yaxshisi" yo'q
- Jamoada yozma qoida: chegarada nima, ichkarida nima
- Go va Rust'dagi kabi bitta tuzilmani hamma joyda ishlatish Python'da
pydanticbilan yaqinlashadi — lekin uning ish vaqtidagi narxi bor - 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:
TypedDict— kalitlari ma'lum lug'at. U JSON va API javoblari uchun ideal: ish vaqtida oddiydictbo'lib qoladi (tezlik,json.dumps, mavjud kod bilan moslik), tekshiruvchi esa noto'g'ri kalit va turlarni ushlaydi.NotRequired,Required,total=FalsevaReadOnlybilan shakl aniq belgilanadi;__required_keys__va__optional_keys__validatsiyada asqotadi. Lekinisinstanceishlamaydi va hech narsa tekshirilmaydi — chegarada o'zingiz tekshirasiz.NamedTuple— turlangan, o'zgarmas, ixcham qiymat. Indekslash, ochib olish,_asdict,_replace, metodlar va xossalar — hammasi bor; lug'atdan ikki barobar ixcham (lekindataclass(slots=True)undan ham tejamkor). Tuzoq: u kortej bo'lgani uchun boshqa kortejlar bilan teng bo'lishi mumkin (Nuqta(1, 2) == (1, 2)).Chegarada shakl, ichkarida model. Tashqi ma'lumot
TypedDictbilan ta'riflanadi va chegarada tekshiriladi; keyin domendataclassiga o'giriladi — shundan so'ng ichki koddadictham,Anyham, 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.
Izohlar (0)
Izoh yozish uchun kiring.
- Hozircha izoh yo'q. Birinchi bo'ling!