Mundarija (21)
- 1. Kirish va motivatsiya
- 2. Nazariya — chuqur tushuntirish
- 2.1. Protocol nima
- 2.2. Nominal va strukturaviy
- 2.3. @runtime_checkable va chegaralari
- 2.4. Ma'lumot atributlari
- 2.5. Chaqiriladigan va generik protokollar
- 2.6. Standart protokollar
- 2.7. Qachon Protocol, qachon ABC
- 3. Tez ma'lumotnoma
- 4. Batafsil misollar
- Misol 1 — Protokol va meros
- Misol 2 — @runtime_checkable va uning chegaralari
- Misol 3 — Generik va chaqiriladigan protokollar
- Misol 4 — Amaliy: ulanadigan saqlash qatlami
- 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.5-dars: Protocol — strukturaviy turlash
13-QISM — TURLAR TIZIMI · 5-dars
1. Kirish va motivatsiya
13.1-darsda duck typing ni ko'rdik: "write metodi bo'lsa yetarli". Lekin buni turlar tilida qanday yozish kerak?
Real vaziyat. Loyihada hisobotlarni turli joyga yozadigan kod bor: faylga, S3 ga, xotiradagi buferga, testda esa soxta (mock) obyektga. Birinchi yechim — umumiy ota klass:
class Yozuvchi(ABC):
@abstractmethod
def write(self, matn: str) -> int: ...Muammo: io.StringIO va open() qaytargan fayl obyekti bu klassdan meros olmaydi — ular allaqachon yozilgan. Tashqi kutubxonaning klassini ham o'zgartirib bo'lmaydi. Natijada isinstance tekshiruvlari va Any lar paydo bo'ldi, tur xavfsizligi yo'qoldi.
Protocol shu muammoni hal qiladi: "menga write(str) -> int metodi bo'lgan har qanday obyekt mos" — merossiz. io.StringIO, o'z klassingiz, mock — hammasi avtomatik mos keladi va tekshiruvchi buni tekshiradi.
Bu darsda:
-
Protocol: strukturaviy turlash — "shakl mos bo'lsa yetarli" - Nominal (
ABC) va strukturaviy yondashuv farqi @runtime_checkableva uning chegaralari- Ma'lumot atributlari bo'lgan protokollar
- Chaqiriladigan protokollar (
__call__) va generik protokollar - Standart protokollar:
Sized,Iterable,SupportsIndexva boshqalar - Amaliy: ulanadigan (pluggable) saqlash qatlami
2. Nazariya — chuqur tushuntirish
2.1. Protocol nima
from typing import Protocol
class Yozuvchi(Protocol):
def write(self, matn: str) -> int: ...
def hisobot_yoz(chiqish: Yozuvchi, matn: str) -> int:
return chiqish.write(matn)Yozuvchidan meros olish shart emas:write(str) -> intmetodi bo'lgan har qanday obyekt mosio.StringIO, fayl obyekti, o'z klassingiz, test uchun soxta obyekt — hammasi ishlaydi- Tekshiruvchi mos kelishini imzo bo'yicha tekshiradi
Bu — "duck typing ni tekshiruvchiga tushuntirish" usuli.
2.2. Nominal va strukturaviy
ABC (nominal) |
Protocol (strukturaviy) |
|
|---|---|---|
| Mos kelish sharti | Meros olish kerak | Shakl mos bo'lsa yetarli |
| Tashqi klasslar | Ro'yxatdan o'tkazish kerak (register) |
Avtomatik |
| Ish vaqtida tekshiruv | isinstance — har doim |
Faqat @runtime_checkable bilan, cheklangan |
| Sukut implementatsiya | Metodlarda kod bo'lishi mumkin | Odatda yo'q (lekin mumkin) |
| Qachon | Sizning ierarxiyangiz, umumiy xulq | Interfeys, tashqi obyektlar, testlar |
Ikkalasi birga ham ishlatiladi: Protocol — chegaralarda (funksiya parametrlari), ABC — o'z ierarxiyangizda.
2.3. @runtime_checkable va chegaralari
@runtime_checkable
class Yopiladigan(Protocol):
def close(self) -> None: ...
isinstance(fayl, Yopiladigan) # ✅ ishlaydiUchta muhim cheklov:
- Imzo tekshirilmaydi — faqat nom bor-yo'qligi:
def close(self, majburiy: int)ham "mos" deb hisoblanadi @runtime_checkablebo'lmasa —isinstanceTypeErrorberadiissubclassfaqat metodli protokollar uchun ishlaydi (ma'lumot atributlari bo'lsa —TypeError)
Ya'ni ish vaqtidagi tekshiruv — "taxminiy"; haqiqiy kafolat statik tekshiruvchidan keladi.
2.4. Ma'lumot atributlari
@runtime_checkable
class Nomli(Protocol):
nom: str- Nusxada
nomatributi bo'lsa — mos (isinstanceham ishlaydi) - Klass darajasida
nom: stre'loni yetarli emas — qiymat kerak Protocolichida__init__yozish — odatda keraksiz (protokol — interfeys, konstruktor emas)
2.5. Chaqiriladigan va generik protokollar
Chaqiriladigan protokol — murakkab imzoli funksiyalar uchun (13.2-darsdagi Callable cheklovini yengadi):
class Jonatuvchi(Protocol):
def __call__(self, xabar: str, *, qayta_urinish: int = 0) -> bool: ...Generik protokol:
class Ombor[T](Protocol):
def ol(self, kalit: str) -> T | None: ...
def qoy(self, kalit: str, qiymat: T) -> None: ...Endi Ombor[Foydalanuvchi] va Ombor[Mahsulot] alohida tekshiriladi.
2.6. Standart protokollar
| Protokol | Talab | Qayerdan |
|---|---|---|
Sized |
__len__ |
collections.abc |
Iterable[T], Iterator[T] |
__iter__, __next__ |
collections.abc |
Container[T] |
__contains__ |
collections.abc |
Hashable |
__hash__ |
collections.abc |
SupportsIndex, SupportsFloat, SupportsInt |
__index__, __float__, __int__ |
typing |
SupportsRead, SupportsWrite |
read, write |
_typeshed (faqat tekshiruv paytida) |
Ko'p hollarda o'z protokolingizni yozishdan oldin standartini qidiring.
2.7. Qachon Protocol, qachon ABC
| Vaziyat | Tanlov |
|---|---|
| Tashqi kutubxona obyektlarini qabul qilish | Protocol |
| Test uchun soxta obyektlar | Protocol |
| "Faqat shu metod kerak" | Protocol |
| Umumiy xulq va sukut implementatsiya | ABC |
Ierarxiya va super() chaqiruvlari |
ABC |
| Plagin tizimi, ro'yxatdan o'tkazish | Ikkalasi (Protocol + reestr) |
3. Tez ma'lumotnoma
from typing import Protocol, runtime_checkable
class Yozuvchi(Protocol):
def write(self, matn: str) -> int: ...
@runtime_checkable
class Yopiladigan(Protocol):
def close(self) -> None: ...
class Ombor[T](Protocol): # generik
def ol(self, kalit: str) -> T | None: ...
class Jonatuvchi(Protocol): # chaqiriladigan
def __call__(self, xabar: str, *, qayta: int = 0) -> bool: ...Qoidalar
meros shart emas — shakl yetarli
isinstance faqat @runtime_checkable bilan va faqat nomlarni tekshiradi
issubclass — faqat metodli protokollar uchun
standart protokollar: Sized, Iterable, SupportsIndex, Hashable4. Batafsil misollar
Misol 1 — Protokol va meros
"""Bitta funksiya — turli manbalar; ABC bilan solishtirish; tashqi klasslar avtomatik mos keladi; testda soxta obyekt."""
import io
import typing
from abc import ABC, abstractmethod
class Yozuvchi(typing.Protocol):
def write(self, matn: str) -> int: ...
def hisobot_yoz(chiqish: Yozuvchi, qatorlar: list[str]) -> int:
"""Chiqish qayerdaligi muhim emas — write(str) -> int bo'lsa yetarli."""
jami = 0
for qator in qatorlar:
jami += chiqish.write(qator + "\n")
return jami
class BuferYozuvchi:
"""Protokoldan meros OLMAYDI — lekin mos keladi."""
def __init__(self) -> None:
self.qatorlar: list[str] = []
def write(self, matn: str) -> int:
self.qatorlar.append(matn)
return len(matn)
class SoxtaYozuvchi:
"""Test uchun: nima yozilganini eslaydi va xato simulyatsiya qiladi."""
def __init__(self, xato_qatorda: int | None = None) -> None:
self.yozilgan: list[str] = []
self.xato_qatorda = xato_qatorda
def write(self, matn: str) -> int:
if self.xato_qatorda == len(self.yozilgan):
raise OSError("disk to'ldi")
self.yozilgan.append(matn)
return len(matn)
QATORLAR = ["sana,summa", "2026-09-16,250000", "2026-09-16,120000"]
print("=== 1. Turli manbalar, bitta funksiya ===")
bufer = io.StringIO()
print(f" io.StringIO: {hisobot_yoz(bufer, QATORLAR)} bayt, natija: {bufer.getvalue().splitlines()}")
oz_yozuvchi = BuferYozuvchi()
print(f" BuferYozuvchi: {hisobot_yoz(oz_yozuvchi, QATORLAR)} bayt, qatorlar: {len(oz_yozuvchi.qatorlar)}")
soxta = SoxtaYozuvchi()
print(f" SoxtaYozuvchi: {hisobot_yoz(soxta, QATORLAR)} bayt")
print("\n=== 2. Testda xatoni simulyatsiya qilish ===")
buzuq = SoxtaYozuvchi(xato_qatorda=2)
try:
hisobot_yoz(buzuq, QATORLAR)
except OSError as xato:
print(f" OSError: {xato}; xatogacha yozilgan qatorlar: {len(buzuq.yozilgan)}")
print("\n=== 3. ABC bilan bo'lsa nima bo'lardi ===")
class AbstraktYozuvchi(ABC):
@abstractmethod
def write(self, matn: str) -> int: ...
class ABCdanMeros(AbstraktYozuvchi):
def __init__(self) -> None:
self.qatorlar: list[str] = []
def write(self, matn: str) -> int:
self.qatorlar.append(matn)
return len(matn)
print(f" ABC vorisi mos keladi: {isinstance(ABCdanMeros(), AbstraktYozuvchi)}")
print(f" io.StringIO ABC ga mos kelmaydi: {isinstance(io.StringIO(), AbstraktYozuvchi)}")
print(f" BuferYozuvchi ham: {isinstance(BuferYozuvchi(), AbstraktYozuvchi)}")
print(" ⭐ ABC — meros talab qiladi; tashqi klasslarni o'zgartirib bo'lmaydi")
try:
AbstraktYozuvchi()
except TypeError as xato:
print(f" abstrakt klassni yaratib bo'lmaydi: TypeError: {str(xato)[:60]}...")
print("\n=== 4. Protokol — faqat annotatsiya ===")
print(f" Yozuvchi klassmi: {isinstance(Yozuvchi, type)}, Protocol vorisimi: {typing.Protocol in Yozuvchi.__mro__}")
print(f" hisobot_yoz izohi: {typing.get_type_hints(hisobot_yoz)['chiqish'].__name__}")
print(" ⭐ ish vaqtida hech qanday tekshiruv yo'q — kafolatni tekshiruvchi beradi")Natijaning muhim qismi:
=== 1. Turli manbalar, bitta funksiya ===
io.StringIO: 47 bayt, natija: ['sana,summa', '2026-09-16,250000', '2026-09-16,120000']
BuferYozuvchi: 47 bayt, qatorlar: 3
SoxtaYozuvchi: 47 bayt
=== 2. Testda xatoni simulyatsiya qilish ===
OSError: disk to'ldi; xatogacha yozilgan qatorlar: 2
=== 3. ABC bilan bo'lsa nima bo'lardi ===
ABC vorisi mos keladi: True
io.StringIO ABC ga mos kelmaydi: False
BuferYozuvchi ham: False
⭐ ABC — meros talab qiladi; tashqi klasslarni o'zgartirib bo'lmaydi
abstrakt klassni yaratib bo'lmaydi: TypeError: Can't instantiate abstract class AbstraktYozuvchi without an...
=== 4. Protokol — faqat annotatsiya ===
Yozuvchi klassmi: True, Protocol vorisimi: True
hisobot_yoz izohi: Yozuvchi
⭐ ish vaqtida hech qanday tekshiruv yo'q — kafolatni tekshiruvchi beradiNima ko'rsatdi: 2.1, 2.2-bo'limlar.
Misol 2 — @runtime_checkable va uning chegaralari
"""isinstance bilan protokol; imzo tekshirilmasligi; ma'lumot atributlari; issubclass cheklovi; xavfsizroq tekshiruv."""
import inspect
import io
import typing
@typing.runtime_checkable
class Yopiladigan(typing.Protocol):
def close(self) -> None: ...
@typing.runtime_checkable
class Nomli(typing.Protocol):
nom: str
class Tekshirilmaydigan(typing.Protocol):
def ishla(self) -> None: ...
class Ulanish:
def close(self) -> None:
pass
class NotogriImzo:
def close(self, majburiy_argument: int) -> None:
pass
class Mahsulot:
def __init__(self, nom: str) -> None:
self.nom = nom
class NomsizKlass:
nom: str # faqat annotatsiya, qiymat yo'q
print("=== 1. isinstance protokol bilan ===")
for tavsif, obj in [("Ulanish()", Ulanish()), ("io.StringIO()", io.StringIO()), ("'matn'", "matn")]:
print(f" {tavsif:16} Yopiladigan: {isinstance(obj, Yopiladigan)}")
print("\n=== 2. ⚠️ Imzo tekshirilmaydi ===")
print(f" NotogriImzo().close(majburiy_argument) — isinstance: {isinstance(NotogriImzo(), Yopiladigan)}")
try:
NotogriImzo().close() # type: ignore[call-arg]
except TypeError as xato:
print(f" lekin chaqirilganda: TypeError: {xato}")
print(" ⭐ ish vaqtidagi tekshiruv faqat nom bor-yo'qligini ko'radi")
print("\n=== 3. Xavfsizroq tekshiruv: imzoni solishtirish ===")
def imzo_mos(obj: object, protokol: type, metod: str) -> bool:
kutilgan = inspect.signature(getattr(protokol, metod))
haqiqiy = inspect.signature(getattr(obj, metod))
kutilgan_parametrlar = [p for n, p in kutilgan.parameters.items() if n != "self"]
return [p.name for p in haqiqiy.parameters.values()] == [p.name for p in kutilgan_parametrlar]
for tavsif, obj in [("Ulanish()", Ulanish()), ("NotogriImzo()", NotogriImzo())]:
print(f" {tavsif:16} isinstance: {isinstance(obj, Yopiladigan)}, imzo ham mos: {imzo_mos(obj, Yopiladigan, 'close')}")
print("\n=== 4. Ma'lumot atributlari ===")
print(f" Mahsulot('Klaviatura') Nomli: {isinstance(Mahsulot('Klaviatura'), Nomli)}")
print(f" NomsizKlass() (faqat annotatsiya): {isinstance(NomsizKlass(), Nomli)}")
print(f" Ulanish() Nomli: {isinstance(Ulanish(), Nomli)}")
print("\n=== 5. Cheklovlar ===")
try:
isinstance(Ulanish(), Tekshirilmaydigan)
except TypeError as xato:
print(f" runtime_checkable siz: TypeError: {str(xato)[:64]}...")
print(f" issubclass metodli protokol bilan: {issubclass(Ulanish, Yopiladigan)}")
try:
issubclass(Mahsulot, Nomli)
except TypeError as xato:
print(f" issubclass ma'lumot atributli protokol bilan: TypeError: {str(xato)[:52]}...")Natijaning muhim qismi:
=== 1. isinstance protokol bilan ===
Ulanish() Yopiladigan: True
io.StringIO() Yopiladigan: True
'matn' Yopiladigan: False
=== 2. ⚠️ Imzo tekshirilmaydi ===
NotogriImzo().close(majburiy_argument) — isinstance: True
lekin chaqirilganda: TypeError: NotogriImzo.close() missing 1 required positional argument: 'majburiy_argument'
⭐ ish vaqtidagi tekshiruv faqat nom bor-yo'qligini ko'radi
=== 3. Xavfsizroq tekshiruv: imzoni solishtirish ===
Ulanish() isinstance: True, imzo ham mos: True
NotogriImzo() isinstance: True, imzo ham mos: False
=== 4. Ma'lumot atributlari ===
Mahsulot('Klaviatura') Nomli: True
NomsizKlass() (faqat annotatsiya): False
Ulanish() Nomli: False
=== 5. Cheklovlar ===
runtime_checkable siz: TypeError: Instance and class checks can only be used with @runtime_checkab...
issubclass metodli protokol bilan: True
issubclass ma'lumot atributli protokol bilan: TypeError: Protocols with non-method members don't support issu...Nima ko'rsatdi: 2.3, 2.4-bo'limlar.
Misol 3 — Generik va chaqiriladigan protokollar
"""Generik Ombor[T] protokoli; ikki implementatsiya; chaqiriladigan protokol bilan murakkab imzo; standart protokollar."""
import typing
from collections.abc import Hashable, Iterable, Sized
from dataclasses import dataclass
@dataclass(frozen=True)
class Foydalanuvchi:
id: int
ism: str
class Ombor[T](typing.Protocol):
def ol(self, kalit: str) -> T | None: ...
def qoy(self, kalit: str, qiymat: T) -> None: ...
def __len__(self) -> int: ...
class XotiraOmbori[T]:
def __init__(self) -> None:
self._yozuvlar: dict[str, T] = {}
def ol(self, kalit: str) -> T | None:
return self._yozuvlar.get(kalit)
def qoy(self, kalit: str, qiymat: T) -> None:
self._yozuvlar[kalit] = qiymat
def __len__(self) -> int:
return len(self._yozuvlar)
class JurnalliOmbor[T]:
"""Har amalni yozib boradi — protokolga mos, lekin merossiz."""
def __init__(self, asos: Ombor[T]) -> None:
self._asos = asos
self.jurnal: list[str] = []
def ol(self, kalit: str) -> T | None:
self.jurnal.append(f"ol({kalit})")
return self._asos.ol(kalit)
def qoy(self, kalit: str, qiymat: T) -> None:
self.jurnal.append(f"qoy({kalit})")
self._asos.qoy(kalit, qiymat)
def __len__(self) -> int:
return len(self._asos)
def yukla_yoki_yarat(ombor: Ombor[Foydalanuvchi], kalit: str, yarat: typing.Callable[[], Foydalanuvchi]) -> Foydalanuvchi:
mavjud = ombor.ol(kalit)
if mavjud is not None:
return mavjud
yangi = yarat()
ombor.qoy(kalit, yangi)
return yangi
print("=== 1. Generik protokol ===")
xotira: XotiraOmbori[Foydalanuvchi] = XotiraOmbori()
jurnalli = JurnalliOmbor(xotira)
aziz = yukla_yoki_yarat(jurnalli, "u1", lambda: Foydalanuvchi(1, "Aziz"))
yana = yukla_yoki_yarat(jurnalli, "u1", lambda: Foydalanuvchi(1, "Aziz"))
print(f" birinchi chaqiruv: {aziz}")
print(f" ikkinchi chaqiruv o'shanimi: {aziz is yana}, ombor hajmi: {len(jurnalli)}")
print(f" jurnal: {jurnalli.jurnal}")
print(f" Ombor tur parametrlari: {Ombor.__type_params__}")
print("\n=== 2. Chaqiriladigan protokol ===")
class Jonatuvchi(typing.Protocol):
def __call__(self, xabar: str, *, qayta_urinish: int = 0) -> bool: ...
def xabarlarni_yubor(jonat: Jonatuvchi, xabarlar: list[str]) -> int:
return sum(jonat(x, qayta_urinish=1) for x in xabarlar)
yuborilganlar: list[tuple[str, int]] = []
def sms_yubor(xabar: str, *, qayta_urinish: int = 0) -> bool:
yuborilganlar.append((xabar, qayta_urinish))
return len(xabar) < 20
print(f" yuborilgan: {xabarlarni_yubor(sms_yubor, ['salom', 'juda uzun xabar matni bu yerda'])} ta muvaffaqiyatli")
print(f" qayd: {yuborilganlar}")
print(f" __call__ izohi: {typing.get_type_hints(Jonatuvchi.__call__)}")
print("\n=== 3. Standart protokollar ===")
namunalar: list[object] = [[1, 2, 3], "matn", 42, {"a": 1}, (x for x in range(3))]
print(f" {'obyekt':22} {'Sized':>6} {'Iterable':>9} {'Hashable':>9} {'SupportsIndex':>14}")
for obj in namunalar:
tavsif = type(obj).__name__ + ("" if isinstance(obj, (int, str)) else f" ({len(obj) if isinstance(obj, Sized) else '?'})")
print(f" {tavsif:22} {isinstance(obj, Sized)!s:>6} {isinstance(obj, Iterable)!s:>9} "
f"{isinstance(obj, Hashable)!s:>9} {isinstance(obj, typing.SupportsIndex)!s:>14}")
def indeksla(ketma_ketlik: typing.Sequence[str], indeks: typing.SupportsIndex) -> str:
return ketma_ketlik[indeks.__index__()]
print(f" SupportsIndex bilan: {indeksla(['a', 'b', 'c'], 1)}, bool ham mos: {indeksla(['a', 'b', 'c'], True)}")Natijaning muhim qismi:
=== 1. Generik protokol ===
birinchi chaqiruv: Foydalanuvchi(id=1, ism='Aziz')
ikkinchi chaqiruv o'shanimi: True, ombor hajmi: 1
jurnal: ['ol(u1)', 'qoy(u1)', 'ol(u1)']
Ombor tur parametrlari: (T,)
=== 2. Chaqiriladigan protokol ===
yuborilgan: 1 ta muvaffaqiyatli
qayd: [('salom', 1), ('juda uzun xabar matni bu yerda', 1)]
__call__ izohi: {'xabar': <class 'str'>, 'qayta_urinish': <class 'int'>, 'return': <class 'bool'>}
=== 3. Standart protokollar ===
obyekt Sized Iterable Hashable SupportsIndex
list (3) True True False False
str True True True False
int False False True True
dict (1) True True False False
generator (?) False True True False
SupportsIndex bilan: b, bool ham mos: bNima ko'rsatdi: 2.5, 2.6-bo'limlar.
Misol 4 — Amaliy: ulanadigan saqlash qatlami
Hisobotlarni turli joyga saqlash kerak: xotira (test), fayl tizimi, "bulut" (simulyatsiya). Protokol interfeysni belgilaydi, implementatsiyalar merossiz yoziladi, tanlov esa sozlamaga qarab qilinadi. Qo'shimcha: implementatsiyalarni ro'yxatdan o'tkazuvchi reestr va imzolarni tekshiruvchi.
"""Protokol interfeys; uch implementatsiya; reestr; imzolarni tekshirish; bir xil kod har qanday backend bilan."""
import inspect
import pathlib
import tempfile
import typing
class Saqlagich(typing.Protocol):
"""Hisobotlarni saqlash interfeysi."""
nom: str
def saqla(self, kalit: str, mazmun: str) -> int: ...
def oqi(self, kalit: str) -> str | None: ...
def royxat(self) -> list[str]: ...
class XotiraSaqlagich:
nom = "xotira"
def __init__(self) -> None:
self._yozuvlar: dict[str, str] = {}
def saqla(self, kalit: str, mazmun: str) -> int:
self._yozuvlar[kalit] = mazmun
return len(mazmun)
def oqi(self, kalit: str) -> str | None:
return self._yozuvlar.get(kalit)
def royxat(self) -> list[str]:
return sorted(self._yozuvlar)
class FaylSaqlagich:
nom = "fayl"
def __init__(self, papka: pathlib.Path) -> None:
self.papka = papka
papka.mkdir(parents=True, exist_ok=True)
def saqla(self, kalit: str, mazmun: str) -> int:
yol = self.papka / f"{kalit}.txt"
yol.write_text(mazmun, encoding="utf-8")
return len(mazmun)
def oqi(self, kalit: str) -> str | None:
yol = self.papka / f"{kalit}.txt"
return yol.read_text(encoding="utf-8") if yol.exists() else None
def royxat(self) -> list[str]:
return sorted(y.stem for y in self.papka.glob("*.txt"))
class BulutSaqlagich:
"""Tarmoq simulyatsiyasi: kechikish va urinishlar hisobi."""
nom = "bulut"
def __init__(self) -> None:
self._obyektlar: dict[str, str] = {}
self.sorovlar = 0
def saqla(self, kalit: str, mazmun: str) -> int:
self.sorovlar += 1
self._obyektlar[f"hisobotlar/{kalit}"] = mazmun
return len(mazmun)
def oqi(self, kalit: str) -> str | None:
self.sorovlar += 1
return self._obyektlar.get(f"hisobotlar/{kalit}")
def royxat(self) -> list[str]:
self.sorovlar += 1
return sorted(k.split("/", 1)[1] for k in self._obyektlar)
def hisobot_saqla(saqlagich: Saqlagich, sana: str, qatorlar: list[str]) -> str:
"""Saqlagich turidan qat'i nazar bir xil kod."""
mazmun = "\n".join(qatorlar)
hajm = saqlagich.saqla(sana, mazmun)
return f"{saqlagich.nom}: {sana} ({hajm} bayt)"
def protokolga_mosmi(klass: type, protokol: type) -> tuple[bool, list[str]]:
"""Statik tekshiruvchi qiladigan ishning soddalashtirilgan varianti."""
kamchiliklar = []
for nom, kutilgan in inspect.getmembers(protokol, inspect.isfunction):
if nom.startswith("_"):
continue
haqiqiy = getattr(klass, nom, None)
if haqiqiy is None:
kamchiliklar.append(f"{nom}: metod yo'q")
continue
kutilgan_imzo = [p for p in inspect.signature(kutilgan).parameters if p != "self"]
haqiqiy_imzo = [p for p in inspect.signature(haqiqiy).parameters if p != "self"]
if kutilgan_imzo != haqiqiy_imzo:
kamchiliklar.append(f"{nom}: {haqiqiy_imzo} ≠ {kutilgan_imzo}")
for nom in getattr(protokol, "__annotations__", {}):
if not hasattr(klass, nom):
kamchiliklar.append(f"{nom}: atribut yo'q")
return not kamchiliklar, kamchiliklar
QATORLAR = ["sana,summa", "2026-09-16,250000", "2026-09-16,120000"]
papka = pathlib.Path(tempfile.mkdtemp()) / "hisobotlar"
print("=== 1. Bir xil kod, uch saqlagich ===")
saqlagichlar: list[Saqlagich] = [XotiraSaqlagich(), FaylSaqlagich(papka), BulutSaqlagich()]
for s in saqlagichlar:
print(f" {hisobot_saqla(s, '2026-09-16', QATORLAR)}")
print("\n=== 2. Saqlangan ma'lumotni o'qish ===")
for s in saqlagichlar:
mazmun = s.oqi("2026-09-16")
print(f" {s.nom:8} ro'yxat: {s.royxat()}, birinchi qator: {mazmun.splitlines()[0] if mazmun else None!r}")
bulut = saqlagichlar[2]
print(f" bulut so'rovlari: {bulut.sorovlar}") # type: ignore[attr-defined]
print("\n=== 3. Moslikni tekshirish ===")
class YarimSaqlagich:
nom = "yarim"
def saqla(self, kalit: str) -> int: # ⚠️ mazmun argumenti yo'q
return 0
class NomsizSaqlagich:
def saqla(self, kalit: str, mazmun: str) -> int:
return 0
def oqi(self, kalit: str) -> str | None:
return None
def royxat(self) -> list[str]:
return []
for klass in (XotiraSaqlagich, FaylSaqlagich, BulutSaqlagich, YarimSaqlagich, NomsizSaqlagich):
mos, kamchiliklar = protokolga_mosmi(klass, Saqlagich)
belgi = "✅" if mos else "❌"
print(f" {belgi} {klass.__name__:18} {'' if mos else '; '.join(kamchiliklar)}")
print(" ⭐ statik tekshiruvchi bu xatolarni kodni ishga tushirmasdan topadi")Natijaning muhim qismi:
=== 1. Bir xil kod, uch saqlagich ===
xotira: 2026-09-16 (46 bayt)
fayl: 2026-09-16 (46 bayt)
bulut: 2026-09-16 (46 bayt)
=== 2. Saqlangan ma'lumotni o'qish ===
xotira ro'yxat: ['2026-09-16'], birinchi qator: 'sana,summa'
fayl ro'yxat: ['2026-09-16'], birinchi qator: 'sana,summa'
bulut ro'yxat: ['2026-09-16'], birinchi qator: 'sana,summa'
bulut so'rovlari: 3
=== 3. Moslikni tekshirish ===
✅ XotiraSaqlagich
✅ FaylSaqlagich
✅ BulutSaqlagich
❌ YarimSaqlagich oqi: metod yo'q; royxat: metod yo'q; saqla: ['kalit'] ≠ ['kalit', 'mazmun']
❌ NomsizSaqlagich nom: atribut yo'q
⭐ statik tekshiruvchi bu xatolarni kodni ishga tushirmasdan topadiNima ko'rsatdi: 2.1, 2.3, 2.7-bo'limlar.
5. To'g'ri va noto'g'ri tushunishlar
| Noto'g'ri fikr | To'g'risi |
|---|---|
"Protocol dan meros olish kerak" |
Shart emas — shakl mos bo'lsa yetarli |
"isinstance protokol bilan har doim ishlaydi" |
Faqat @runtime_checkable bilan |
"isinstance imzoni tekshiradi" |
Faqat nom bor-yo'qligini |
"Protocol — ABC ning yangi nomi" |
Turli mexanizm: strukturaviy va nominal |
| "Protokolda kod yozib bo'lmaydi" | Mumkin (sukut implementatsiya), lekin kamdan-kam |
| "Har interfeys uchun protokol yozish kerak" | Avval standart protokollarni qarang |
| "Protokol ish vaqtida himoya beradi" | Kafolat — tekshiruvchidan |
| "Protokol va ABC ni birga ishlatib bo'lmaydi" | Ko'p loyihalarda ikkalasi ham bor |
6. Keng tarqalgan xatolar va yechimlari
1. Tashqi obyektlar uchun ABC talab qilish
def yoz(chiqish: AbstraktYozuvchi) -> None: ... # ❌ io.StringIO mos kelmaydi
def yoz(chiqish: Yozuvchi) -> None: ... # ✅ Protocol2. isinstance ga ishonib, imzoni tekshirmaslik
if isinstance(obj, Yopiladigan):
obj.close() # ⚠️ close(majburiy) bo'lsa — TypeError Statik tekshiruv yoki inspect.signature bilan qo'shimcha tekshiruv.
3. @runtime_checkable ni unutish
isinstance(x, MeningProtokolim) # ❌ TypeError4. Protokolda ortiqcha metodlar
class Ombor(Protocol):
def ol(self, k: str) -> str: ...
def qoy(self, k: str, v: str) -> None: ...
def tozala(self) -> None: ...
def zaxira(self) -> bytes: ... # ⚠️ hamma implementatsiyaga kerakmi?Kichik protokollar (interfeysni ajratish tamoyili).
5. Protokolni konstruktor sifatida ishlatish
def yarat(turi: type[Saqlagich]) -> Saqlagich:
return turi() # ⚠️ protokolda __init__ shartnomasi yo'qFabrika funksiyasi yoki reestr.
6. Ma'lumot atributini klass annotatsiyasi bilan chalkashtirish
class Nomli(Protocol):
nom: str
class K:
nom: str # ⚠️ qiymat yo'q — isinstance False
class K:
def __init__(self): self.nom = "a" # ✅7. Callable bilan murakkab imzoni ifodalash
jonat: Callable[..., bool] # ⚠️ kalitli argumentlar yo'qoldi
class Jonatuvchi(Protocol): # ✅
def __call__(self, xabar: str, *, qayta: int = 0) -> bool: ...8. Generik protokolni parametrsiz ishlatish
def f(ombor: Ombor) -> None: ... # ⚠️ T noma'lum
def f(ombor: Ombor[Foydalanuvchi]) -> None: ... # ✅7. Integratsiya — bu bilim qayerda kerak bo'ladi
- 8.18-dars (o'tilgan): abstrakt klasslar (
ABC) - 10.15-dars (o'tilgan): ma'lumot modeli — dunder protokollari
- 13.2-dars (o'tilgan):
Callableva kolleksiya turlari - 13.4-dars (o'tilgan): generiklar — generik protokollar uchun
- 13.9-dars: turlar bilan loyiha dizayni — portlar va adapterlar
- 17-qism: testlar — soxta obyektlar protokolga mos keladi
- 20–23-qismlar: servis qatlamlari, repozitoriylar, tashqi API mijozlari
- 27-qism: arxitektura — bog'liqliklarni teskari aylantirish (DIP)
8. Eng yaxshi amaliyotlar
Chegaralarda
Protocol, ierarxiyadaABC.Protokollarni kichik ushlang — "faqat kerakli metodlar".
Standart protokollarni qidiring (
Sized,Iterable,SupportsIndex).@runtime_checkableni faqat kerak bo'lganda va uning cheklovlarini bilib qo'shing.Murakkab imzolar uchun chaqiriladigan protokol.
Generik protokollarni parametr bilan ishlating.
Test obyektlari protokolga mos bo'lsin —
Mocko'rniga oddiy klass.Statik tekshiruvchiga tayaning — ish vaqtidagi tekshiruv taxminiy.
9. Amaliy topshiriq
Vazifa 1: Natijani bashorat qiling
import typing
@typing.runtime_checkable
class Yopiladigan(typing.Protocol):
def close(self) -> None: ...
class Tekshirilmas(typing.Protocol):
def ishla(self) -> None: ...
class A:
def close(self) -> None: ...
class B:
def close(self, x: int) -> None: ...
1. print(isinstance(A(), Yopiladigan))
2. print(isinstance(B(), Yopiladigan))
3. print(isinstance("matn", Yopiladigan))
4. try:
isinstance(A(), Tekshirilmas)
except TypeError:
print("TypeError")
5. print(issubclass(A, Yopiladigan))
6. @typing.runtime_checkable
class Nomli(typing.Protocol):
nom: str
class K:
def __init__(self):
self.nom = "x"
print(isinstance(K(), Nomli))
7. class L:
nom: str
print(isinstance(L(), Nomli))
8. try:
issubclass(K, Nomli)
except TypeError:
print("TypeError")
9. import io
print(isinstance(io.StringIO(), Yopiladigan))
10. from collections.abc import Sized, Iterable
print(isinstance([1], Sized), isinstance(5, Sized))
11. print(isinstance(5, typing.SupportsIndex), isinstance(5.0, typing.SupportsIndex))
12. class Ombor[T](typing.Protocol):
def ol(self) -> T: ...
print(Ombor.__type_params__)Javoblar
TrueTrue— imzo tekshirilmaydiFalseTypeErrorTrueTrueFalse— faqat annotatsiya, qiymat yo'qTypeError— ma'lumot atributli protokol bilanissubclassishlamaydiTrueTrue FalseTrue False—floatda__index__yo'q(T,)
Vazifa 2: Xatolarni tuzating
1. class Yozuvchi(ABC):
@abstractmethod
def write(self, matn: str) -> int: ...
def hisobot(chiqish: Yozuvchi) -> None: # io.StringIO uzatiladi
...
2. class Ombor(Protocol):
def ol(self, kalit: str) -> str: ...
def ishla(ombor: Ombor) -> None:
if isinstance(ombor, Ombor):
...
3. def yubor(jonatuvchi: Callable[..., bool], xabar: str) -> bool:
return jonatuvchi(xabar, qayta_urinish=2)
4. class Saqlagich(Protocol):
def saqla(self, k: str, v: str) -> None: ...
def oqi(self, k: str) -> str: ...
def zaxirala(self) -> bytes: ...
def tikla(self, z: bytes) -> None: ...
def migratsiya(self) -> None: ...
5. @runtime_checkable
class Ishlovchi(Protocol):
def ishla(self) -> None: ...
def bajar(obj: object) -> None:
if isinstance(obj, Ishlovchi):
obj.ishla() # imzo mos kelmasligi mumkinJavoblar
1. class Yozuvchi(Protocol): # meros talab qilinmaydi
def write(self, matn: str) -> int: ...
2. def ishla(ombor: Ombor) -> None:
... # isinstance shart emas — tekshiruvchi bajaradi
# kerak bo'lsa: @runtime_checkable va cheklovlarini bilib
3. class Jonatuvchi(Protocol):
def __call__(self, xabar: str, *, qayta_urinish: int = 0) -> bool: ...
def yubor(jonatuvchi: Jonatuvchi, xabar: str) -> bool:
return jonatuvchi(xabar, qayta_urinish=2)
4. class Saqlagich(Protocol): # kichik interfeys
def saqla(self, k: str, v: str) -> None: ...
def oqi(self, k: str) -> str: ...
class Zaxiralanadigan(Protocol): # alohida protokol
def zaxirala(self) -> bytes: ...
def tikla(self, z: bytes) -> None: ...
5. def bajar(obj: Ishlovchi) -> None: # turni imzoda talab qiling
obj.ishla()
# ish vaqtida kerak bo'lsa: try/except TypeError yoki imzoni tekshirishVazifa 3: Protokollar kutubxonasi
Loyihangiz uchun protokollar to'plamini yozing:
Saqlagich,Jurnalchi,Soat(vaqt manbai),Xabarnoma(yuborish)- Har biri uchun kamida ikki implementatsiya: real va test uchun
Soatprotokoli bilan testlarda vaqtni boshqaring (time.timeo'rniga)- Barcha funksiyalar protokollarni qabul qilsin, konkret klasslarni emas
- Implementatsiyalarni almashtirib, testlar o'zgarmasligini ko'rsating
Vazifa 4: Moslik tekshiruvchisi
Misol 4 dagi protokolga_mosmi ni rivojlantiring:
- Argument turlarini ham solishtirsin (
inspect.signatureannotatsiyalari) - Qaytish turini tekshirsin
- Kalitli va sukut qiymatli argumentlarni hisobga olsin
*args/**kwargsbilan ishlasin- Natijani
mypyxulosasi bilan solishtiring (13.7-darsdan keyin)
Vazifa 5: Standart protokollar bilan tanishuv
collections.abcvatypingdagi barcha protokollarni ro'yxatga oling- Har biri qaysi dunder metodlarni talab qilishini aniqlang (
__abstractmethods__) - Standart turlar (
list,dict,str,int, generator, fayl) qaysi protokollarga mos kelishini jadval qiling - O'z klassingizni
Sequenceprotokoliga to'liq mos qilib yozing va tekshiring
Vazifa 6: Plagin tizimi
Protocol asosida plagin tizimi yozing:
Plaginprotokoli:nom,versiya,ishga_tushir(kontekst)- Plaginlarni papkadan yuklovchi reestr (
importlib) - Yuklashda protokolga moslikni tekshirish va mos kelmaganini rad etish
- Uchta namuna plagin yozing va ularni ishga tushiring
- Xatolarni izolyatsiya qiling: bitta plagin yiqilsa, qolgani ishlasin
Vazifa 7: O'ylash
Go'da interfeyslar strukturaviy: tip interfeysdagi metodlarga ega bo'lsa, u avtomatik mos keladi (Python Protocol kabi); Java va C# da interfeyslar nominal: implements yozish shart; TypeScript'da esa strukturaviy turlash butun tilning asosi. Bu farqlar arxitekturaga qanday ta'sir qiladi va Python ikkala modelni ham qo'llab-quvvatlagani yaxshimi?
Javob
Qisqa javob: nominal interfeyslar niyatni aniq bildiradi ("men bu shartnomani bajaraman"), strukturaviy interfeyslar esa bog'liqlikni kamaytiradi (implementatsiya interfeys haqida bilmasligi mumkin). Python ikkalasini ham berib, chegaralarda strukturaviy, ierarxiyalarda nominal yondashuvni tanlash imkonini yaratadi.
1. Modellar
| Til | Model | Xususiyat |
|---|---|---|
| Go | Strukturaviy | Interfeys iste'molchi tomonda e'lon qilinadi ("accept interfaces, return structs") |
| TypeScript | Strukturaviy | Butun tur tizimi shunday |
| Java, C# | Nominal | implements/: yozilishi shart |
| Rust | Nominal (trait) + impl |
Tashqi tur uchun ham impl yozish mumkin |
| Python | Ikkalasi | Protocol (strukturaviy) va ABC (nominal) |
2. Strukturaviy yondashuvning kuchi
def hisobot(chiqish: Yozuvchi) -> None: ...io.StringIO, fayl, tarmoq soketi, test obyekti — hech biri sizning kodingiz haqida bilmaydi- Interfeys iste'molchi tomonda e'lon qilinadi — Go falsafasi: "kichik interfeyslar, iste'molchi tomonda"
- Bog'liqlik yo'nalishi teskari aylantiriladi (DIP, 27-qism)
3. Nominal yondashuvning kuchi
- Niyat aniq: klass nima ekanini nomidan bilish mumkin
- Sukut implementatsiya va umumiy xulqni ota klass beradi
isinstanceishonchli ishlaydi — ish vaqtidagi dispatch uchun- Tasodifiy moslik yo'q: bir xil nomli metod tasodifan mos kelib qolmaydi
4. Tasodifiy moslik muammosi
class Otish(Protocol):
def otish(self) -> None: ... # "o'q uzish"
class Eshik:
def otish(self) -> None: ... # "o'tish"? — tasodifan mos keldiNominal tizimda bunday chalkashlik bo'lmaydi. Amalda protokol nomlari va metod nomlarini aniq tanlash bilan hal qilinadi.
5. Python uchun xulosa
- Chegarada (funksiya parametrlari, portlar) —
Protocol - Ichkarida (o'z ierarxiyangiz, umumiy kod) —
ABCyoki oddiy meros - Interfeysni iste'molchi yonida e'lon qiling — u faqat kerakli metodlarni talab qilsin
- Ikkala modelni birga ishlatish — cheklov emas, imkoniyat: Go va Java tajribasining eng yaxshi qismlari
Nimani mustahkamlaydi: 2.1–2.7-bo'limlar.
Xulosa
Bu darsda strukturaviy turlashni o'rgandik.
Eng muhim uch fikr:
Protocol— duck typing ning turlangan ko'rinishi. Funksiya "mengawrite(str) -> intkerak" deb aytadi vaio.StringIO, o'z klassingiz yoki test obyekti — hammasi merossiz mos keladi.ABCesa meros talab qiladi, shuning uchun tashqi kutubxona obyektlari bilan ishlamaydi. Amaliy taqsimot: chegaralardaProtocol, o'z ierarxiyangizdaABC.Ish vaqtidagi tekshiruv cheklangan.
@runtime_checkablebo'lmasaisinstanceTypeErrorberadi; bo'lsa ham u faqat nomlar borligini tekshiradi —close(self, majburiy)ham "mos" hisoblanadi.issubclassesa ma'lumot atributli protokollar bilan umuman ishlamaydi. Haqiqiy kafolat statik tekshiruvchidan keladi.Protokollar generik va chaqiriladigan bo'lishi mumkin.
class Ombor[T](Protocol)— har model uchun alohida tekshiriladigan interfeys;def __call__(self, xabar: str, *, qayta: int = 0) -> bool—Callable[..., bool]yo'qotadigan imzoni saqlaydi. Yangi protokol yozishdan oldin standartlarini qarang:Sized,Iterable,Hashable,SupportsIndex.
Keyingi darsda lug'at va kortejlarni turlangan qilishni o'rganamiz: TypedDict bilan JSON tuzilmalari, NamedTuple, dataclass bilan solishtirish va qaysi birini qachon tanlash.
Izohlar (0)
Izoh yozish uchun kiring.
- Hozircha izoh yo'q. Birinchi bo'ling!