aidoc.uz

3-qism · Professional Python·15-bob

Type hint'lar, mypy va Pydantic

Tip annotatsiyalari nima beradi, asosiy va murakkab tiplar (Optional, Union, generics, Callable, TypedDict, Literal, Protocol), mypy bilan statik tekshiruv va Pydantic bilan runtime validatsiya — LLM'ning structured output'i uchun.

2-bobda ko'rdik: Python dinamik tiplangan, va narx_hisobla("3", 1500) xato bermasdan "333...3" qaytaradi. Kichik skriptda bu muammo emas. Lekin 50 ming qatorli kod bazasida, 10 kishilik jamoada, yoki AI agent tizimida — bunday xatolar qimmatga tushadi. Type hint'lar bu muammoni hal qiladi va zamonaviy professional Python'ning standart qismiga aylangan: FastAPI, Pydantic, Anthropic va OpenAI SDK'lari, PyTorch — hammasi type hint'larga tayanadi.

▶ Video dars· Corey Schafer· 18:16· ingliz tilida
Python Tutorial: Type Hinting vs Type Checking vs Data Validation - What’s the Difference?

Type hint'lar, mypy bilan statik tekshiruv va Pydantic bilan runtime validatsiya farqi — aynan shu bobning asosiy g'oyasi.

YouTube'da ochish ↗

1. Type hint'lar nima va nima emas#

def narx(soni: int, narxi: int) -> int:
    return soni * narxi

Eng muhim fakt: Python type hint'larni ishga tushirishda tekshirmaydi. narx("3", 1500) hamon ishlaydi. Hint'lar — bu metama'lumot:

Muharrir (VS Code / Pylance)
avtomatik to'ldirish, xatolarni darhol qizil chiziq bilan ko'rsatish
Statik tekshiruvchi (mypy, pyright)
kodni ishga tushirmasdan tip xatolarini topadi — CI'da
Runtime kutubxonalar (Pydantic, FastAPI, dataclass)
hint'larni o'qib, validatsiya va sxema yaratadi
Hujjat
funksiya nimani kutishi va nima qaytarishi — kodning o'zida
uv add --dev mypy
uv run mypy src/
main.py:7: error: Argument 1 to "narx" has incompatible type "str"; expected "int"  [arg-type]

2. Asosiy sintaksis#

# O'zgaruvchilar (odatda shart emas — tip xulosa qilinadi)
max_tokens: int = 1024
model_name: str = "claude-opus-5-5"
 
# Funksiyalar — eng muhim joy
def greet(name: str, excited: bool = False) -> str:
    return f"Salom, {name}{'!' if excited else '.'}"
 
def log(msg: str) -> None:        # hech narsa qaytarmaydi
    print(msg)

To'plamlar (Python 3.9+ — kichik harfli o'rnatilgan tiplar)#

scores: list[float] = [0.9, 0.85]
counts: dict[str, int] = {"a": 1}
unique: set[str] = {"x"}
point: tuple[float, float] = (1.0, 2.0)        # aniq 2 element
shape: tuple[int, ...] = (32, 3, 224, 224)     # istalgan uzunlik, hammasi int
matrix: list[list[float]] = [[1.0, 2.0]]

Eski kodda from typing import List, Dict va List[int] ko'rasiz — bu 3.8 va undan eskisi uchun edi; yangi kodda kichik harflardan foydalaning.

None bo'lishi mumkin: X | None#

def find_user(user_id: int) -> User | None:      # Python 3.10+
    ...
 
def find_user(user_id: int) -> Optional[User]:   # eski yozuv, xuddi shu ma'no
    ...

Endi mypy sizni None'ni tekshirishga majbur qiladi:

def get_count(d: dict[str, int], key: str) -> int:
    v = d.get(key)          # v: int | None
    return v + 1            # ❌ mypy: Unsupported operand types for + ("None" and "int")
def get_count(d: dict[str, int], key: str) -> int:
    v = d.get(key)
    if v is None:
        return 0
    return v + 1            # ✅ bu yerda mypy v ni int deb biladi ("type narrowing")

Bu xususiyatning o'zi AttributeError: 'NoneType' object has no attribute... xatolarining ko'pchiligini kod ishga tushmasidan oldin topadi.

Birlashma (Union)#

def to_text(value: str | bytes | int) -> str:
    if isinstance(value, bytes):
        return value.decode("utf-8")
    return str(value)

3. Abstrakt tiplar: aniq emas, imkoniyat so'rang#

from collections.abc import Iterable, Sequence, Mapping, Callable, Iterator
 
 
def total(values: Iterable[float]) -> float:        # list, tuple, set, generator — hammasi bo'ladi
    return sum(values)
 
 
def first(items: Sequence[str]) -> str:             # indeks kerak: list, tuple, str
    return items[0]
 
 
def render(params: Mapping[str, str]) -> str:       # o'qish uchun lug'atga o'xshash narsa
    return ", ".join(f"{k}={v}" for k, v in params.items())

Qoida (Postel tamoyili): parametrlarda iloji boricha keng tip qabul qiling (Iterable, Sequence, Mapping), qaytarishda — aniq tip (list, dict). def total(values: list[float]) — tuple yoki generator berilsa mypy shikoyat qiladi, garchi kod ishlasa ham.

Kerakli imkoniyatTip
Faqat aylanish (for)Iterable[T]
len() + indeks + aylanishSequence[T]
Kalit bo'yicha o'qishMapping[K, V]
O'zgartirish ham keraklist[T], dict[K, V], MutableMapping
Generator qaytaradiIterator[T] (yoki Generator[Y, S, R])

Funksiya tiplari: Callable#

from collections.abc import Callable
 
def apply(fn: Callable[[str], int], items: list[str]) -> list[int]:
    return [fn(x) for x in items]
 
apply(len, ["a", "bb"])        # ✅
 
Metric = Callable[[list[int], list[int]], float]   # tip taxallusi

4. Generics — tip parametrlari#

Funksiya har qanday tip bilan ishlasa, lekin kirish va chiqish tiplari bog'liq bo'lsa:

def first[T](items: Sequence[T]) -> T | None:      # Python 3.12+ sintaksisi
    return items[0] if items else None
 
 
x = first([1, 2, 3])        # mypy: x — int | None
y = first(["a", "b"])       # mypy: y — str | None

Eski (3.11 va oldingi) yozuv:

from typing import TypeVar
 
T = TypeVar("T")
 
def first(items: Sequence[T]) -> T | None: ...

Generik klass:

class Cache[K, V]:
    def __init__(self) -> None:
        self._data: dict[K, V] = {}
 
    def get(self, key: K) -> V | None:
        return self._data.get(key)
 
    def set(self, key: K, value: V) -> None:
        self._data[key] = value
 
 
emb_cache: Cache[str, list[float]] = Cache()

Tip taxalluslari#

type Vector = list[float]                 # Python 3.12+
type Messages = list[dict[str, str]]
 
def cosine(a: Vector, b: Vector) -> float: ...

5. Ma'lumot shakllari: TypedDict va Literal#

API'lar bilan ishlaganda lug'atlar ko'p. TypedDict lug'atning shaklini tasvirlaydi:

from typing import Literal, NotRequired, TypedDict
 
 
class Message(TypedDict):
    role: Literal["user", "assistant"]    # faqat shu ikki qiymat
    content: str
    name: NotRequired[str]                # ixtiyoriy kalit
 
 
msg: Message = {"role": "user", "content": "Salom"}          # ✅
bad: Message = {"role": "system", "content": "..."}         # ❌ mypy: "system" ruxsat etilmagan

Literal — "faqat shu aniq qiymatlar": Literal["train", "val", "test"], Literal[0, 1]. Kichik to'plam uchun Enumga (13-bob) yengil alternativa.

6. Protocol — strukturaviy tiplar#

13-bobda ko'rdik. Type hint'lar kontekstida — "shu metodlari bor har qanday narsa":

from typing import Protocol
 
 
class SupportsComplete(Protocol):
    def complete(self, prompt: str, *, max_tokens: int = ...) -> str: ...
 
 
def summarize(llm: SupportsComplete, text: str) -> str:
    return llm.complete(f"Qisqacha xulosa:\n{text}", max_tokens=200)

Endi summarizega Claude klienti, mahalliy model, yoki testdagi soxta obyekt — complete metodi bo'lgan har qanday narsa berilishi mumkin.

7. Boshqa foydali vositalar#

from typing import Any, Final, Self, cast, override, TYPE_CHECKING
 
MAX_RETRIES: Final = 5               # qayta tayinlash — mypy xatosi
 
def load(raw: Any) -> dict: ...      # Any — "tekshirma" (qochish yo'li; kam ishlating)
 
class Builder:
    def set_model(self, m: str) -> Self:    # voris klasslarda ham to'g'ri tip
        self.model = m
        return self
 
class MyModel(BaseModel):
    @override                         # ota klassda bunday metod bo'lmasa — mypy xatosi
    def predict(self, x): ...
 
if TYPE_CHECKING:                     # faqat tekshiruv uchun import (aylanma importdan qochish)
    from .models import Classifier

Any va cast — mypy'ni "o'chirish" vositalari. Ular ba'zan kerak (masalan, xom JSON bilan ishlashda), lekin har biri — tip xavfsizligida teshik. Tashqi ma'lumot uchun Any o'rniga Pydantic ishlating (pastda).

8. mypy'ni loyihada sozlash#

# pyproject.toml
[tool.mypy]
python_version = "3.12"
strict = true                     # eng qattiq rejim — yangi loyihalar uchun tavsiya
warn_unused_ignores = true
plugins = ["pydantic.mypy"]
 
[[tool.mypy.overrides]]
module = ["some_untyped_lib.*"]
ignore_missing_imports = true

Mavjud (tiplanmagan) loyihaga qo'shishda strictni birdaniga yoqmang — avval oddiy rejim, keyin modulma-modul qattiqlashtiring. Ba'zan bitta qatorni o'chirish kerak bo'ladi: # type: ignore[arg-type] — har doim xato kodini ko'rsating va nima uchunligini izohlang.

9. Pydantic — runtime validatsiya#

Type hint'lar sizning kodingizni tekshiradi. Lekin tashqaridan keladigan ma'lumot — API so'rovi, konfiguratsiya fayli, LLM javobi — statik tekshiruvdan o'tmaydi. U yerda Pydantic kerak: u type hint'larni o'qib, ma'lumotni ishga tushirish paytida tekshiradi va aylantiradi.

uv add pydantic
from typing import Literal
 
from pydantic import BaseModel, Field, ValidationError
 
 
class Review(BaseModel):
    text: str = Field(min_length=1)
    rating: int = Field(ge=1, le=5)                        # 1 ≤ rating ≤ 5
    sentiment: Literal["positive", "negative", "neutral"]
    tags: list[str] = []                                   # Pydantic'da bu xavfsiz — har obyektga nusxa
 
 
r = Review.model_validate({"text": "Zo'r", "rating": "5", "sentiment": "positive"})
r.rating, type(r.rating)       # (5, <class 'int'>) — "5" satri int'ga aylantirildi

Noto'g'ri ma'lumot:

try:
    Review(text="", rating=7, sentiment="good")
except ValidationError as e:
    print(e)
3 validation errors for Review
text
  String should have at least 1 character [type=string_too_short, input_value='', input_type=str]
rating
  Input should be less than or equal to 5 [type=less_than_equal, input_value=7, input_type=int]
sentiment
  Input should be 'positive', 'negative' or 'neutral' [type=literal_error, input_value='good', input_type=str]

Barcha xatolar birdaniga, aniq joyi va sababi bilan. e.errors() — xuddi shu ma'lumot lug'atlar ro'yxati sifatida (API javobiga qaytarish uchun).

Asosiy amallar#

Review.model_validate(data_dict)           # lug'atdan
Review.model_validate_json(json_string)    # JSON satrdan (tezroq — parse + validate birga)
r.model_dump()                             # → dict
r.model_dump_json()                        # → JSON satr
r.model_copy(update={"rating": 4})         # o'zgartirilgan nusxa
Review.model_json_schema()                 # → JSON Schema (LLM'ga berish uchun!)

Maxsus tekshiruvlar#

from pydantic import BaseModel, field_validator, model_validator
 
 
class TrainingExample(BaseModel):
    prompt: str
    completion: str
    language: str = "uz"
 
    @field_validator("prompt", "completion")
    @classmethod
    def strip_and_check(cls, v: str) -> str:
        v = v.strip()
        if not v:
            raise ValueError("bo'sh bo'lmasligi kerak")
        return v                                   # o'zgartirilgan qiymat saqlanadi
 
    @model_validator(mode="after")
    def different(self) -> "TrainingExample":
        if self.prompt == self.completion:
            raise ValueError("prompt va completion bir xil")
        return self

Qat'iy rejim#

Standart holatda Pydantic "aqlli" aylantiradi ("5" → 5). Ba'zan bu istalmaydi:

class Strict(BaseModel):
    model_config = {"strict": True}
    n: int
 
Strict(n="5")      # ValidationError: int_type

Sozlamalar uchun: pydantic-settings#

from pydantic_settings import BaseSettings, SettingsConfigDict   # uv add pydantic-settings
 
 
class Settings(BaseSettings):
    model_config = SettingsConfigDict(env_file=".env")
 
    anthropic_api_key: str                 # majburiy — yo'q bo'lsa, ishga tushishda aniq xato
    model: str = "claude-opus-5-5"
    max_tokens: int = 1024                 # ".env"dagi "2048" satri int'ga aylanadi
    debug: bool = False                    # "1", "true", "yes" → True
 
 
settings = Settings()                      # muhit o'zgaruvchilari + .env dan o'qiydi

9-bob mashqidagi config.py — bitta klassga aylandi.

10. Pydantic va LLM: structured output#

LLM bilan ishlashning eng muhim naqshlaridan biri: modeldan aniq sxemadagi JSON olish. Pydantic bu yerda ikki vazifani bajaradi:

  1. Sxema beradi — model_json_schema() modelga "javobing shunday shaklda bo'lsin" deb aytish uchun;
  2. Javobni tekshiradi — model nima qaytarsa ham, kodingizga faqat to'g'ri tiplangan obyekt yetib boradi.
from typing import Literal
 
from pydantic import BaseModel, Field
 
 
class Entity(BaseModel):
    name: str
    type: Literal["person", "organization", "location"]
 
 
class Extraction(BaseModel):
    summary: str = Field(description="1-2 gaplik xulosa")
    sentiment: Literal["positive", "negative", "neutral"]
    entities: list[Entity]
 
 
schema = Extraction.model_json_schema()
# → bu sxema API'ga beriladi (23-bobda Anthropic SDK'ning structured outputs imkoniyati)
 
raw = '{"summary": "Kompaniya yangi ofis ochdi.", "sentiment": "positive", "entities": [{"name": "Toshkent", "type": "location"}]}'
result = Extraction.model_validate_json(raw)
result.entities[0].name        # 'Toshkent' — muharrir avtomatik to'ldiradi, mypy tekshiradi

Anthropic SDK'sida client.messages.parse(..., output_format=Extraction) kabi yordamchilar aynan shu mexanizmdan foydalanadi: Pydantic modelini sxemaga aylantiradi, modelni sxemaga rioya qilishga majburlaydi va javobni qaytadan Pydantic obyektiga aylantiradi. 23-bobda to'liq misol.

11. Qayerda qanchalik tiplash kerak#

Kod turiTavsiya
Jupyter tajribalariShart emas; muhim funksiyalarga yozing
Bir martalik skriptFunksiya imzolariga yozing
Kutubxona / paketTo'liq, mypy --strict
Servis (FastAPI, agent)To'liq + tashqi ma'lumot uchun Pydantic
Tashqi ma'lumot chegarasi (API, fayl, LLM javobi)Pydantic — majburiy

Nima noto'g'ri ketishi mumkin#

XatoOqibatTuzatish
Hint'lar runtime'da tekshiriladi deb o'ylashNoto'g'ri tip jim o'tadimypy/pyright CI'da; tashqi ma'lumot uchun Pydantic
def f(x: list = [])Tip to'g'ri, lekin mutable default (8-bob)x: list[int] | None = None
-> None deb yozib qiymat qaytarishmypy xatosiTo'g'ri qaytish tipi
Optional[X] = "ixtiyoriy parametr" deb o'ylashOptional faqat "None bo'lishi mumkin" deganiDefault qiymat — alohida narsa
Hamma joyda AnyTekshiruv ma'nosizAniq tiplar, object, Protocol, Pydantic
list[float] parametr — tuple berildimypy shikoyat qiladiSequence[float] yoki Iterable[float]
LLM javobini json.loads va lug'at sifatida ishlatishKalit yo'q / noto'g'ri tip — keyinroq xatoModel.model_validate_json(...)
# type: ignore sababsizHaqiqiy xatolar yashirinadiXato kodini ko'rsating, izoh yozing
Amaliy topshiriq

1. Tiplang va mypy'dan o'tkazing

Oldingi boblardagi uchta funksiyangizni (normalize, batched, build_prompt) to'liq tiplang va uv run mypy --strict dan xatosiz o'tkazing. Ataylab noto'g'ri chaqiruv yozib, mypy uni topishini tekshiring.

Maslahatni ko'rsatish

Avval mypy'ni --strict bilan ishga tushiring, xabarlarni birma-bir tuzating. Iterable/Sequence tanlovi, None narrowing.

Amaliy topshiriq

2. Generik funksiya

group_by(items, key) generik funksiyasini yozing: key funksiyasi bo'yicha guruhlab lug'at qaytarsin. mypy group_by(["olma", "anor"], len) natijasini dict[int, list[str]] deb bilishini reveal_type bilan tekshiring.

Maslahatni ko'rsatish

def group_by[T, K](items: Iterable[T], key: Callable[[T], K]) -> dict[K, list[T]]

Amaliy topshiriq

3. Dataset validatsiyasi

TrainingExample modeli bilan JSONL faylni tekshiruvchi skript yozing: yaroqli qatorlarni yangi faylga yozsin, yaroqsizlari uchun hisobot chiqarsin — qaysi maydon qancha marta xato bergan. 11-bobdagi dataset skriptiga ulang.

Maslahatni ko'rsatish

Har qator: try: TrainingExample.model_validate_json(line) except ValidationError as e: xatolarni yig'ing. Hisobot: Counter(err['loc'][0] for err in ...).

Amaliy topshiriq

4. LLM javob sxemasi

"Ish e'lonidan ma'lumot ajratish" uchun Pydantic sxemasini loyihalang: lavozim, kompaniya, shahar, maosh oralig'i (ixtiyoriy, min ≤ max tekshiruvi bilan), talab qilinadigan ko'nikmalar ro'yxati, masofaviy ish (bool). model_json_schema()ni chiqaring va qo'lda yozilgan 3 xil "model javobi" (biri buzilgan) bilan validatsiyani sinab ko'ring.

Yodda saqlang
  • 01Type hint'lar runtime'da tekshirilmaydi — ularni muharrir, mypy/pyright va Pydantic ishlatadi.
  • 02list[int], dict[str, float], tuple[int, ...], X | None — zamonaviy sintaksis.
  • 03X | None — mypy None'ni tekshirishga majbur qiladi; bu NoneType xatolarining ko'pini oldini oladi.
  • 04Parametrda keng (Iterable, Sequence, Mapping), qaytishda aniq tip.
  • 05Generics: def f[T](x: Sequence[T]) -> T; TypedDict va Literal — lug'at shakli va aniq qiymatlar.
  • 06Protocol — almashtiriladigan komponentlar uchun strukturaviy tiplar.
  • 07Pydantic — tashqi ma'lumot chegarasida runtime validatsiya va aylantirish; ValidationError barcha xatolarni beradi.
  • 08LLM structured output: Pydantic → JSON Schema → model → model_validate_json.

Mustahkamlash

Test va amaliy masalalar

Avval testni ishlang — har javobdan keyin izoh chiqadi. Keyin masalalarni shu yerning o'zida yechib, brauzerdagi haqiqiy Python bilan avtomatik tekshiring. Natijalaringiz kurs progressi sahifasida jamlanadi.

Test: topshirilmaganMasalalar: 0/3

Bilimni tekshirish

Bob bo'yicha test

12 ta savol · har javobdan keyin to'g'ri yoki xatoligi va izoh ko'rsatiladi · oxirida natija foizda.

Amaliy masalalar

Kodni yozing va «Tekshirish»ni bosing (yoki Ctrl/⌘ + Enter). Kodingiz shu brauzerda saqlanib turadi.

01Generik group_by

group_by(items, key) — key(x) bo'yicha guruhlab dict[K, list[T]] qaytarsin (tartib saqlansin). Type hint'lar bilan yozing (def group_by[T, K](items: Iterable[T], key: Callable[[T], K]) -> dict[K, list[T]]).

02Pydantic model

Review(BaseModel): text: str (kamida 1 belgi, chetlari tozalanadi), rating: int (1..5), sentiment: Literal["positive", "negative", "neutral"], tags: list[str] = []. parse_reviews(rows) — yaroqlilarini Review ro'yxati, yaroqsizlari sonini qaytarsin: (reviews, n_bad).

03LLM javobidan ma'lumot

Extraction(BaseModel): summary: str, entities: list[Entity], bu yerda Entity(BaseModel): name: str, type: Literal["person", "organization", "location"]. extract(raw_json) — JSON satrni tekshirib Extraction qaytarsin; yaroqsiz bo'lsa None.