aidoc.uz

3-qism · Professional Python·13-bob

OOP chuqurroq: dunder metodlar, dataclass, ABC, Protocol va Enum

Python ma'lumot modeli: __repr__, __eq__, __hash__, __len__, __getitem__, __iter__, __call__ va operatorlar; dataclass bilan toza ma'lumot klasslari; abstrakt klasslar, Protocol (duck typing), Enum va __slots__.

Nega len(lst) ishlaydi, lekin lst.length() emas? Nega NumPy massivlarini a + b deb qo'shish mumkin? Nega PyTorch modelini model(x) deb chaqirasiz? Javob — Python'ning ma'lumot modeli (data model): maxsus nomli metodlar (__len__, __add__, __call__...) orqali o'z klasslaringizni tilning o'rnatilgan amallariga "ulash" mumkin. Bu bob kutubxonalar ichida nima bo'layotganini tushunishga va o'zingiz shunday qulay API'lar yaratishga o'rgatadi.

▶ Video dars· ArjanCodes· 22:19· ingliz tilida
This Is Why Python Data Classes Are Awesome

dataclass nima uchun qulay: avtomatik __init__, __repr__, __eq__, frozen va default_factory — bobning 7-bo'limi amalda.

YouTube'da ochish ↗

1. Dunder metodlar — Python'ning "ilgaklari"#

Dunder — "double underscore" (__nom__). Bu metodlarni o'zingiz to'g'ridan-to'g'ri chaqirmaysiz — Python ularni ma'lum amallarda chaqiradi:

Siz yozasizPython chaqiradi
len(x)x.__len__()
x[i]x.__getitem__(i)
for a in xx.__iter__()
a + ba.__add__(b)
a == ba.__eq__(b)
x(arg)x.__call__(arg)
print(x), str(x)x.__str__()
REPL'da x, repr(x)x.__repr__()
with x:x.__enter__() / x.__exit__()
if x:x.__bool__() (yo'q bo'lsa __len__)
hash(x), dict kalitix.__hash__()

2. __repr__ va __str__ — obyektni ko'rsatish#

class Point:
    def __init__(self, x, y):
        self.x, self.y = x, y
 
p = Point(1, 2)
print(p)       # <__main__.Point object at 0x104a8f2d0> — foydasiz
class Point:
    def __init__(self, x, y):
        self.x, self.y = x, y
 
    def __repr__(self) -> str:                 # dasturchi uchun: aniq, iloji bo'lsa — qayta yaratish kodi
        return f"Point(x={self.x!r}, y={self.y!r})"
 
    def __str__(self) -> str:                  # foydalanuvchi uchun: chiroyli
        return f"({self.x}, {self.y})"
 
p = Point(1, 2)
repr(p)        # 'Point(x=1, y=2)'
str(p)         # '(1, 2)'
f"{p}"         # '(1, 2)'
f"{p!r}"       # 'Point(x=1, y=2)'

Har bir klassga __repr__ yozing — debug, log va Jupyter'da vaqtni juda tejaydi. __str__ ixtiyoriy: yo'q bo'lsa, __repr__ ishlatiladi.

3. Tenglik va hash#

Standart holatda == — is bilan bir xil (bitta obyektmi). Qiymat bo'yicha solishtirish uchun __eq__:

class Point:
    ...
    def __eq__(self, other: object) -> bool:
        if not isinstance(other, Point):
            return NotImplemented          # "bilmayman" — Python boshqa tomonni sinab ko'radi
        return (self.x, self.y) == (other.x, other.y)
 
    def __hash__(self) -> int:
        return hash((self.x, self.y))

Ikki muhim qoida:

  1. __eq__ yozsangiz, Python __hash__ni None qiladi — obyekt lug'at kaliti yoki to'plam elementi bo'la olmay qoladi. Agar obyekt o'zgarmas bo'lsa — __hash__ni ham yozing; o'zgaruvchan bo'lsa — hash qilinmasin.
  2. Teng obyektlarning hashi teng bo'lishi shart: a == b bo'lsa, hash(a) == hash(b). Aks holda lug'atlar buziladi.

NotImplemented (NotImplementedError emas!) — bu maxsus qiymat: "men bu tip bilan ishlashni bilmayman". Python keyin boshqa operandning metodini sinab ko'radi va hech kim bilmasa — False (yoki operatorlar uchun TypeError) qaytaradi.

4. Operatorlarni qayta yuklash: Vector klassi#

12-bobdagi mashqni davom ettiramiz — ML'dagi embedding vektorlarining soddalashtirilgan versiyasi:

import math
 
 
class Vector:
    def __init__(self, *values: float) -> None:
        self.values = tuple(float(v) for v in values)
 
    def __repr__(self) -> str:
        return f"Vector{self.values}"
 
    def __len__(self) -> int:
        return len(self.values)
 
    def __getitem__(self, i):
        return self.values[i]
 
    def __iter__(self):
        return iter(self.values)
 
    def __eq__(self, other: object) -> bool:
        if not isinstance(other, Vector):
            return NotImplemented
        return self.values == other.values
 
    def __hash__(self) -> int:
        return hash(self.values)
 
    def __add__(self, other: "Vector") -> "Vector":
        if not isinstance(other, Vector):
            return NotImplemented
        return Vector(*(a + b for a, b in zip(self, other, strict=True)))
 
    def __mul__(self, k: float) -> "Vector":        # v * 2
        if not isinstance(k, (int, float)):
            return NotImplemented
        return Vector(*(a * k for a in self))
 
    __rmul__ = __mul__                               # 2 * v
 
    def __matmul__(self, other: "Vector") -> float:  # v @ w — skalyar ko'paytma
        return sum(a * b for a, b in zip(self, other, strict=True))
 
    def __abs__(self) -> float:                      # abs(v) — uzunlik
        return math.sqrt(self @ self)
 
    def __bool__(self) -> bool:                      # nol vektor — False
        return any(self.values)
 
 
v = Vector(1, 2)
w = Vector(3, 4)
v + w          # Vector(4.0, 6.0)
2 * v          # Vector(2.0, 4.0) — __rmul__
v @ w          # 11.0
abs(w)         # 5.0
v[0], len(v)   # (1.0, 2)
list(v)        # [1.0, 2.0]
v == Vector(1, 2)          # True
bool(Vector(0, 0))         # False
{v: "embedding"}           # hash bor — kalit bo'la oladi

Teskari operatorlar#

2 * v da Python avval int.__mul__(2, v) ni sinaydi — int Vectorni bilmaydi va NotImplemented qaytaradi. Keyin Python v.__rmul__(2) ni chaqiradi. Shuning uchun __rmul__, __radd__ kabi "r" (reflected) metodlar kerak.

Operatorlar jadvali#

OperatorMetodTeskariJoyida
+__add____radd____iadd__ (+=)
-__sub____rsub____isub__
*__mul____rmul____imul__
/__truediv____rtruediv____itruediv__
@__matmul____rmatmul____imatmul__
<, <=__lt__, __le__
-x, abs(x)__neg__, __abs__

__iadd__ bo'lmasa, a += b → a = a + b (yangi obyekt). listda __iadd__ bor (joyida o'zgartiradi) — 2-bobdagi += farqi shundan.

5. Konteyner protokollari#

__len__, __getitem__ va __iter__ yozsangiz, klassingiz "ro'yxatga o'xshab" ishlaydi. ML'dagi eng muhim misol — dataset klassi. PyTorch Dataset aynan shu ikki metodni talab qiladi:

import json
from pathlib import Path
 
 
class JsonlDataset:
    """PyTorch Dataset uslubidagi klass: len() va [i] qo'llab-quvvatlanadi."""
 
    def __init__(self, path: Path, transform=None) -> None:
        with open(path, encoding="utf-8") as f:
            self.records = [json.loads(line) for line in f if line.strip()]
        self.transform = transform
 
    def __len__(self) -> int:
        return len(self.records)
 
    def __getitem__(self, idx: int) -> dict:
        record = self.records[idx]
        return self.transform(record) if self.transform else record
 
 
ds = JsonlDataset(Path("train.jsonl"), transform=lambda r: {**r, "text": r["text"].lower()})
len(ds)           # yozuvlar soni
ds[0]             # birinchi yozuv (transform qo'llangan)
for rec in ds:    # __iter__ yo'q, lekin __getitem__ + IndexError orqali aylanish ishlaydi
    ...

__contains__ (x in obj) ham qo'shish mumkin; yo'q bo'lsa Python aylanib qidiradi.

6. __call__ — chaqiriladigan obyektlar#

Obyektni funksiya kabi chaqirish mumkin bo'lsin:

class Tokenizer:
    def __init__(self, vocab: dict[str, int]) -> None:
        self.vocab = vocab
        self.unk = vocab.get("<unk>", 0)
 
    def __call__(self, text: str) -> list[int]:
        return [self.vocab.get(w, self.unk) for w in text.lower().split()]
 
 
tok = Tokenizer({"<unk>": 0, "salom": 1, "dunyo": 2})
tok("Salom dunyo!")        # [1, 0] — "dunyo!" lug'atda yo'q

Holatli funksiya kerak bo'lsa (sozlamalari, keshi, hisoblagichi bor) — __call__li klass closure'dan (8-bob) ko'pincha tushunarliroq. PyTorch'da model(x) aynan nn.Module.__call__ni chaqiradi — u esa hook'larni ishlatib, sizning forward(x) metodingizni chaqiradi. Shuning uchun model.forward(x) emas, model(x) deb yozish kerak.

7. dataclass — toza ma'lumot klasslari#

Ko'p klasslar asosan ma'lumot saqlaydi. Ular uchun __init__, __repr__, __eq__ yozish — zerikarli takrorlash. @dataclass ularni avtomatik yaratadi:

from dataclasses import dataclass, field, asdict, replace
 
 
@dataclass
class TrainConfig:
    model: str = "small"
    lr: float = 3e-4
    epochs: int = 10
    batch_size: int = 32
    tags: list[str] = field(default_factory=list)    # mutable default — faqat shunday!
 
 
cfg = TrainConfig(lr=1e-3)
cfg                       # TrainConfig(model='small', lr=0.001, epochs=10, batch_size=32, tags=[])
cfg == TrainConfig(lr=1e-3)   # True — __eq__ maydonlar bo'yicha
asdict(cfg)               # {'model': 'small', 'lr': 0.001, ...} — JSON'ga saqlash uchun
replace(cfg, epochs=5)    # yangi obyekt, bitta maydon o'zgargan

Type annotatsiyalar bu yerda majburiy — dataclass maydonlarni aynan shular orqali topadi. Mutable default'ni to'g'ridan-to'g'ri yozsangiz (tags: list = []), dataclass o'zi xato beradi: ValueError: mutable default <class 'list'> for field tags is not allowed: use default_factory.

Muhim parametrlar#

@dataclass(frozen=True, slots=True)
class Point:
    x: float
    y: float
 
p = Point(1, 2)
p.x = 5                 # FrozenInstanceError — o'zgarmas
{p: "markaz"}           # frozen → avtomatik __hash__ → kalit bo'la oladi
ParametrNima beradi
frozen=TrueO'zgarmas obyekt + __hash__. Konfiguratsiyalar, kalitlar uchun
slots=True__slots__ (pastda) — kamroq xotira, tezroq atributlar
order=True<, > maydonlar tartibida — saralash mumkin
kw_only=TrueHamma maydonlar faqat kalit so'z bilan

__post_init__ — tekshiruv va hisoblanadigan maydonlar#

@dataclass
class Rect:
    w: float
    h: float
    area: float = field(init=False)      # __init__ da berilmaydi
 
    def __post_init__(self) -> None:
        if self.w <= 0 or self.h <= 0:
            raise ValueError(f"O'lchamlar musbat bo'lishi kerak: {self.w}x{self.h}")
        self.area = self.w * self.h
 
 
Rect(2, 3)        # Rect(w=2, h=3, area=6)

Dataclass, NamedTuple, dict yoki Pydantic?#

dictNamedTuple@dataclassPydantic BaseModel
Atribut orqali kirish❌✅✅✅
Xato nomdagi maydonJim yangi kalitXatoXatoXato
O'zgaruvchan✅❌✅ (yoki frozen)✅
Runtime tip tekshiruvi❌❌❌✅
JSON'dan/gaTabiiyQo'ldaasdictmodel_validate_json / model_dump_json
QachonTez skript, dinamik kalitlarKichik o'zgarmas yozuvIchki ma'lumot klasslariTashqi ma'lumot: API, konfiguratsiya, LLM chiqishi

Qoida: ichki ma'lumot — dataclass; tashqaridan keladigan (ishonib bo'lmaydigan) ma'lumot — Pydantic (15-bob).

8. Abstrakt klasslar (ABC)#

Ota klass "shartnoma" belgilashi kerak bo'lsa — voris albatta qaysi metodlarni amalga oshirishi kerak:

from abc import ABC, abstractmethod
 
 
class Embedder(ABC):
    @abstractmethod
    def embed(self, texts: list[str]) -> list[list[float]]:
        """Matnlarni vektorlarga aylantiradi."""
 
    def embed_one(self, text: str) -> list[float]:        # oddiy metod — meros qilinadi
        return self.embed([text])[0]
 
 
class HashEmbedder(Embedder):
    """Test uchun oddiy embedder: so'zlar hashidan vektor."""
 
    def __init__(self, dim: int = 8) -> None:
        self.dim = dim
 
    def embed(self, texts: list[str]) -> list[list[float]]:
        out = []
        for t in texts:
            v = [0.0] * self.dim
            for w in t.lower().split():
                v[hash(w) % self.dim] += 1.0
            out.append(v)
        return out
 
 
Embedder()                      # TypeError: Can't instantiate abstract class Embedder ...
HashEmbedder().embed_one("salom dunyo")

12-bobda raise NotImplementedError ishlatgan edik — ABC'ning afzalligi: xato metod chaqirilganda emas, obyekt yaratilganda chiqadi.

9. Protocol — strukturaviy tiplash (duck typing)#

"Agar u o'rdakdek yursa va o'rdakdek g'aqillasa — u o'rdak". Python'da ko'pincha meros emas, kerakli metodlar borligi muhim. Protocol buni tiplar darajasida ifodalaydi:

from typing import Protocol
 
 
class SupportsEmbed(Protocol):
    def embed(self, texts: list[str]) -> list[list[float]]: ...
 
 
class OpenSourceEmbedder:                 # hech narsadan meros olmagan!
    def embed(self, texts: list[str]) -> list[list[float]]:
        return [[0.0] * 4 for _ in texts]
 
 
def build_index(embedder: SupportsEmbed, docs: list[str]) -> list[list[float]]:
    return embedder.embed(docs)
 
 
build_index(OpenSourceEmbedder(), ["a", "b"])   # ✅ mypy qabul qiladi — kerakli metod bor
ABCProtocol
Voris aniq meros olishi kerak (nominal)Mos metodlar bo'lishi kifoya (strukturaviy)
Umumiy kod (oddiy metodlar) berishi mumkinFaqat "shakl"ni tasvirlaydi
O'zingizning klasslar ierarxiyangiz uchunTashqi kutubxonalar klasslarini ham qamrab olish uchun

Zamonaviy Python'da Protocol tobora ko'proq ishlatilmoqda — ayniqsa "istalgan LLM klienti", "istalgan vektor bazasi" kabi almashtiriladigan komponentlar uchun. 15-bobda batafsil.

10. Enum — cheklangan qiymatlar to'plami#

Sehrli satrlar ("user", "assistant") o'rniga:

from enum import Enum, StrEnum, auto
 
 
class Split(Enum):
    TRAIN = "train"
    VAL = "val"
    TEST = "test"
 
 
class Role(StrEnum):           # Python 3.11+ — qiymatlar satr sifatida ham ishlaydi
    USER = auto()              # "user"
    ASSISTANT = auto()         # "assistant"
 
 
Split.TRAIN.value              # 'train'
Split("val")                   # Split.VAL — qiymatdan
Split["TEST"]                  # Split.TEST — nomdan
list(Split)                    # hamma a'zolar
Split.TRAIN == "train"         # False — oddiy Enum satrga teng emas
 
Role.USER == "user"            # True — StrEnum
{"role": Role.USER}            # JSON'ga "user" bo'lib yoziladi

Afzalligi: xato yozish imkonsiz (Split.TRIAN → darhol AttributeError, "trian" esa jim o'tib ketardi), muharrir avtomatik to'ldiradi, matchda ishlatish mumkin (case Split.TRAIN: — 4-bobdagi konstanta muammosi yo'q).

11. __slots__ — xotirani tejash#

Har bir oddiy obyekt atributlarini __dict__ lug'atida saqlaydi — bu qulay, lekin qimmat. __slots__ atributlar ro'yxatini qotiradi:

class PointSlots:
    __slots__ = ("x", "y")
    def __init__(self, x, y):
        self.x, self.y = x, y
 
p = PointSlots(1, 2)
p.z = 3            # AttributeError — yangi atribut qo'shib bo'lmaydi

Million kichik obyekt yaratilganda xotira 2–3 baravar kamayadi. Odatda @dataclass(slots=True) eng qulay yo'l. Lekin agar shuncha ko'p sonli obyekt bo'lsa — ehtimol sizga obyektlar emas, NumPy massivi yoki DataFrame kerak.

12. Amaliyot: model registri#

Hamma narsani birlashtiramiz — turli LLM provayderlarini bitta interfeys orqali boshqarish:

from dataclasses import dataclass
from enum import StrEnum
from typing import Protocol
 
 
class Provider(StrEnum):
    ANTHROPIC = "anthropic"
    LOCAL = "local"
 
 
@dataclass(frozen=True, slots=True)
class ModelSpec:
    name: str
    provider: Provider
    context_window: int
    input_price: float        # $ / 1M token
    output_price: float
 
    def cost(self, input_tokens: int, output_tokens: int) -> float:
        return (input_tokens * self.input_price + output_tokens * self.output_price) / 1_000_000
 
 
class ChatModel(Protocol):
    spec: ModelSpec
 
    def complete(self, prompt: str) -> str: ...
 
 
class EchoModel:
    """Test uchun: javob sifatida promptni qaytaradi."""
 
    def __init__(self) -> None:
        self.spec = ModelSpec("echo", Provider.LOCAL, 1_000, 0.0, 0.0)
 
    def complete(self, prompt: str) -> str:
        return prompt[::-1]
 
 
class Registry:
    def __init__(self) -> None:
        self._models: dict[str, ChatModel] = {}
 
    def register(self, model: ChatModel) -> None:
        if model.spec.name in self._models:
            raise ValueError(f"{model.spec.name!r} allaqachon ro'yxatda")
        self._models[model.spec.name] = model
 
    def __getitem__(self, name: str) -> ChatModel:
        try:
            return self._models[name]
        except KeyError:
            raise KeyError(f"{name!r} topilmadi. Mavjudlar: {sorted(self._models)}") from None
 
    def __contains__(self, name: str) -> bool:
        return name in self._models
 
    def __len__(self) -> int:
        return len(self._models)
 
    def __repr__(self) -> str:
        return f"Registry({sorted(self._models)})"
 
 
reg = Registry()
reg.register(EchoModel())
print(reg, len(reg), "echo" in reg)       # Registry(['echo']) 1 True
print(reg["echo"].complete("salom"))      # molas
print(ModelSpec("opus", Provider.ANTHROPIC, 1_000_000, 4.0, 20.0).cost(10_000, 1_000))   # 0.06

raise ... from None — asl KeyError zanjirini yashiradi, foydalanuvchi faqat tushunarli xabarni ko'radi.

Nima noto'g'ri ketishi mumkin#

XatoOqibatTuzatish
__repr__ yo'qLog va debug'da <object at 0x...>Har klassga __repr__ (yoki dataclass)
__eq__ bor, __hash__ yo'qObyekt set/dict'ga tushmaydiO'zgarmas bo'lsa __hash__ yoki frozen=True
Teng obyektlar — har xil hashLug'atda "yo'qolgan" kalitlarHash'ni __eq__dagi maydonlardan hisoblang
raise NotImplementedError operatordaTeskari operator sinab ko'rilmaydireturn NotImplemented
__rmul__ yo'q2 * v → TypeErrorTeskari metodni qo'shing
model.forward(x)Hook'lar ishlamaydimodel(x)
Dataclass'da tags: list = []ValueErrorfield(default_factory=list)
Tashqi JSON'ni dataclass'gaTiplar tekshirilmaydiPydantic
Sehrli satrlar ("trian")Jim xatoEnum
Split.TRAIN == "train"False.value yoki StrEnum
Amaliy topshiriq

1. Vector'ni to'ldiring

Vector klassiga - (ayirish va unar minus), / (songa bo'lish), normalized() (uzunligi 1 bo'lgan vektor), cosine(other) metodlarini qo'shing. Vector(1, 0).cosine(Vector(0, 1)) → 0.0, Vector(1, 1).cosine(Vector(2, 2)) → 1.0 (taxminan).

Maslahatni ko'rsatish

__sub__ ni __add__ dagidek yozing; __neg__ uchun -1 ga ko'paytiring; __truediv__ da k == 0 holatini tekshiring; normalized = self / abs(self).

Amaliy topshiriq

2. Dataset klassi

ListDataset(items, transform=None) klassini yozing: len(), ds[i], ds[2:5] (yangi ListDataset qaytarsin), for x in ds, x in ds ishlasin. Keyin split(ratio, seed) metodini qo'shing — ikkita ListDataset qaytarsin.

Maslahatni ko'rsatish

__init__ da ro'yxatni saqlang; __getitem__ slice'ni ham qo'llab-quvvatlasin: isinstance(idx, slice) bo'lsa yangi Dataset qaytaring.

Amaliy topshiriq

3. Konfiguratsiyani dataclass bilan

GenerationConfigni (12-bob mashqi) @dataclass(frozen=True) bilan qayta yozing: tekshiruvlar __post_init__da, to_api_params() None maydonlarsiz lug'at qaytarsin, from_dict(d) noma'lum kalit kelsa xato bersin. Property versiyasi bilan solishtiring — qaysi biri qisqaroq?

Maslahatni ko'rsatish

@dataclass(frozen=True, kw_only=True); __post_init__ da tekshiruv; from_dict classmethod'ida noma'lum kalitlarni dataclasses.fields() bilan aniqlang.

Amaliy topshiriq

4. Protocol bilan almashtiriladigan komponentlar

Retriever protokolini yozing (search(query: str, k: int) -> list[str]) va ikkita amalga oshirishni: KeywordRetriever (so'z mosligi soni bo'yicha) va RandomRetriever (seed bilan). answer(question, retriever) funksiyasi istalganini qabul qilsin. mypy bilan tekshiring (15-bobdan oldin ham uv add --dev mypy && uv run mypy fayl.py ishlaydi).

Yodda saqlang
  • 01Dunder metodlar klassingizni Python sintaksisiga ulaydi: len, [], for, +, ==, (), with.
  • 02__repr__ har doim yozing; __str__ — foydalanuvchi uchun chiroyli ko'rinish.
  • 03__eq__ yozsangiz __hash__ yo'qoladi; teng obyektlar teng hashga ega bo'lishi shart.
  • 04Operator metodlari noma'lum tip uchun NotImplemented qaytarsin; 2 * v uchun __rmul__.
  • 05__len__ + __getitem__ — dataset; __call__ — chaqiriladigan obyekt (model(x)).
  • 06@dataclass: __init__/__repr__/__eq__ avtomatik; field(default_factory=...), frozen, slots, __post_init__.
  • 07ABC — meros orqali shartnoma; Protocol — strukturaviy (duck typing) shartnoma.
  • 08Enum/StrEnum — sehrli satrlar o'rniga; __slots__ — ko'p kichik obyektlar uchun xotira.

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.

01Vector klassi

Vector(*values) klassi: __repr__ → Vector(1.0, 2.0); __len__; __getitem__; __eq__ va __hash__; v + w (uzunlik farqi → ValueError); v * 2 va 2 * v; v @ w — skalyar ko'paytma; abs(v) — uzunlik. Qiymatlarni float tuple sifatida saqlang.

02Dataset klassi

ListDataset(items, transform=None): len(ds), ds[i] (transform qo'llangan), ds[1:3] — yangi ListDataset, x in ds (asl elementlar bo'yicha). transform slice'dagi datasetga ham o'tsin.

03Frozen konfiguratsiya

@dataclass(frozen=True) bilan GenerationConfig: model: str, max_tokens: int = 1024, effort: str = "medium". __post_init__ da tekshiring: max_tokens 1..128000 oralig'ida, effort — low/medium/high/xhigh/max dan biri, aks holda ValueError. to_params() — lug'at qaytarsin.