Turlar va mypy: xatoni ishga tushirmasdan topish
Nima uchun tur belgilash
Python turlarni ishga tushish paytida tekshiradi. Ya'ni xato kod bajarilgandagina chiqadi — va agar o'sha tarmoq kamdan-kam bajarilsa, xato oylab yashirinib yotadi.
def ortacha(sonlar): return sum(sonlar) / len(sonlar) print("to'g'ri ishlatish:", ortacha([80, 90, 100])) try: ortacha(["80", "90"]) except TypeError as e: print("noto'g'ri ishlatish:", type(e).__name__, "-", e)
to'g'ri ishlatish: 90.0 noto'g'ri ishlatish: TypeError - unsupported operand type(s) for +: 'int' and 'str'
Bu xato faqat funksiya chaqirilganda chiqdi. Katta loyihada u foydalanuvchi qo'lida chiqadi.
Tur ko'rsatkichlari (type hints) — bu kodni ishga tushirmasdan tekshirish imkoni. Python ularni
o'zi tekshirmaydi; buni mypy kabi alohida asbob qiladi.
Sintaksis
from typing import Optional def salomlash(ism: str, marta: int = 1) -> str: return f"Salom, {ism}! " * marta def topish(ro_yxat: list[str], nishon: str) -> int | None: """Python 3.10 dan: `int | None`. Eskiroq versiyalarda `Optional[int]`.""" for i, x in enumerate(ro_yxat): if x == nishon: return i return None print(salomlash("Aziz", 2)) print("topildi:", topish(["a", "b", "c"], "b"), "| topilmadi:", topish(["a"], "z")) print("annotatsiyalar:", salomlash.__annotations__)
Salom, Aziz! Salom, Aziz!
topildi: 1 | topilmadi: None
annotatsiyalar: {'ism': <class 'str'>, 'marta': <class 'int'>, 'return': <class 'str'>}
Turlar — oddiy izoh emas, ular obyekt sifatida saqlanadi (__annotations__) va asboblar shuni o'qiydi.
| Yozuv | Ma'nosi |
|---|---|
int, str, float, bool |
Oddiy turlar |
list[str] |
Matnlar ro'yxati |
dict[str, int] |
Kalit matn, qiymat son |
tuple[int, str] |
Aniq ikki elementli kortej |
int \| None |
Son yoki None (3.10+) |
Sequence[int] |
Indekslanadigan har qanday to'plam (ro'yxat, kortej) |
Iterable[int] |
Aylanib chiqiladigan har narsa |
Callable[[int], str] |
Sondan matn qaytaruvchi funksiya |
mypy ni ishga tushiramiz
Loyihamizga turlar qo'shamiz:
shu faylni yaratingsrc/hisobot/tekshiruv.py
"""Qoidalar: qator to'g'rimi yoki yo'q.""" ENG_KATTA_BAHO = 100 def bahoni_tekshir(xom: str | None) -> int: """Matnni bahoga aylantiradi. Xato bo'lsa sabab bilan ValueError.""" if xom is None: raise ValueError("baho ko'rsatilmagan") try: baho = int(xom) except (TypeError, ValueError): raise ValueError(f"baho son emas: {xom!r}") from None if not 0 <= baho <= ENG_KATTA_BAHO: raise ValueError(f"baho 0..{ENG_KATTA_BAHO} oralig'ida bo'lishi kerak, berilgan: {baho}") return baho
shu faylni yaratingsrc/hisobot/modellar.py
"""Ma'lumot tuzilmalari.""" from dataclasses import dataclass, field from enum import Enum class Daraja(str, Enum): ALO = "a'lo" YAXSHI = "yaxshi" QONIQARLI = "qoniqarli" QONIQARSIZ = "qoniqarsiz" @classmethod def bahodan(cls, baho: int) -> "Daraja": if baho >= 90: return cls.ALO if baho >= 75: return cls.YAXSHI if baho >= 60: return cls.QONIQARLI return cls.QONIQARSIZ @dataclass(frozen=True, slots=True) class Talaba: ism: str guruh: str baho: int sana: str @property def daraja(self) -> Daraja: return Daraja.bahodan(self.baho) Xato = tuple[int, str, str] # (qator raqami, ism, sabab) @dataclass class Natija: talabalar: list[Talaba] = field(default_factory=list) xatolar: list[Xato] = field(default_factory=list) @property def jami(self) -> int: return len(self.talabalar) + len(self.xatolar)
shu faylni yaratingmypy.ini
[mypy] python_version = 3.10 files = src strict = True [mypy-tests.*] strict = False
.venv/bin/python3 -m pip install --quiet mypy python3 -m mypy --version
mypy 2.3.1 (compiled: yes)
Endi tekshiramiz:
python3 -m mypy src/hisobot/tekshiruv.py src/hisobot/modellar.py
src/hisobot/hisob.py:5: error: Function is missing a type annotation [no-untyped-def] src/hisobot/hisob.py:6: error: Need type annotation for "yigindi" (hint: "yigindi: dict[<type>, <type>] = ...") [var-annotated] src/hisobot/hisob.py:13: error: Function is missing a type annotation [no-untyped-def] src/hisobot/oqish.py:8: error: Function is missing a type annotation [no-untyped-def] src/hisobot/oqish.py:14: error: Function is missing a type annotation [no-untyped-def] Found 5 errors in 2 files (checked 2 source files)
Ikki faylni berdik, lekin mypy hisob.py va oqish.py haqida ham xato ko'rsatdi. Sabab: mypy
importlarni kuzatadi — tekshirilayotgan fayl boshqa modulni import qilsa, u ham tekshiriladi.
Bu foydali xulq: siz bitta faylni o'zgartirsangiz ham, u buzgan joylar ko'rinadi. Qolgan fayllarni bu bob davomida tuzatamiz.
strict nima talab qiladi
strict = True bir nechta qat'iy qoidani yoqadi. Eng muhimi: har funksiyada tur bo'lishi shart.
Buni ko'rsatish uchun turlarsiz fayl yozamiz:
shu faylni yaratingturlarsiz.py
def qosh(a, b): return a + b def ishlat(): return qosh("1", 2)
shu faylni yaratingoddiy_mypy.ini
[mypy] python_version = 3.10
Ikki sozlama bilan yonma-yon tekshiramiz. oddiy_mypy.ini da strict yo'q:
echo "=== strict BILAN ===" python3 -m mypy --strict turlarsiz.py echo "=== strict SIZ ===" python3 -m mypy --config-file=oddiy_mypy.ini turlarsiz.py
=== strict BILAN === turlarsiz.py:1: error: Function is missing a type annotation [no-untyped-def] turlarsiz.py:5: error: Function is missing a return type annotation [no-untyped-def] turlarsiz.py:6: error: Call to untyped function "qosh" in typed context [no-untyped-call] Found 3 errors in 1 file (checked 1 source file) === strict SIZ === Success: no issues found in 1 source file
--strict bilan uchta xato chiqdi: ikki funksiyada tur yo'q va turlanmagan funksiya chaqirilgan.
strict siz esa mypy "Success: no issues found" deydi. Lekin qosh("1", 2) haqiqatan
xato — str + int ishlamaydi. Turlar yozilmagani uchun mypy tekshiradigan narsa topmadi va
jim qoldi.
Turlarni qo'shamiz va xato darhol chiqadi:
shu faylni yaratingturli.py
def qosh(a: int, b: int) -> int: return a + b def ishlat() -> int: return qosh("1", 2)
python3 -m mypy turli.py
turli.py:6: error: Argument 1 to "qosh" has incompatible type "str"; expected "int" [arg-type] Found 1 error in 1 file (checked 1 source file)
Mana asosiy fikr: tur yozmasangiz, mypy sizga yordam bera olmaydi. U jim "hammasi yaxshi" deydi.
mypy haqiqiy xatoni topadi
Endi loyihamizga ataylab xato kiritamiz — real hayotda uchraydigan turini:
shu faylni yaratingsrc/hisobot/hisob.py
"""Statistika: kiritish/chiqarishsiz, faqat hisob.""" from collections import Counter from .modellar import Talaba def guruh_ortachasi(talabalar: list[Talaba]) -> dict[str, float]: yigindi: dict[str, tuple[int, int]] = {} for t in talabalar: jami, soni = yigindi.get(t.guruh, (0, 0)) yigindi[t.guruh] = (jami + t.baho, soni + 1) return {g: round(jami / soni, 2) for g, (jami, soni) in sorted(yigindi.items())} def daraja_taqsimoti(talabalar: list[Talaba]) -> dict[str, int]: return dict(Counter(t.daraja.value for t in talabalar).most_common()) def eng_yaxshi(talabalar: list[Talaba]) -> Talaba: """Eng yuqori bahoni qaytaradi.""" return max(talabalar, key=lambda t: t.baho, default=None)
python3 -m mypy src/hisobot/hisob.py
src/hisobot/oqish.py:8: error: Function is missing a type annotation [no-untyped-def] src/hisobot/oqish.py:14: error: Function is missing a type annotation [no-untyped-def] src/hisobot/hisob.py:21: error: Incompatible return value type (got "Talaba | None", expected "Talaba") [return-value] Found 3 errors in 2 files (checked 1 source file)
mypy eng_yaxshi dagi xatoni topdi: default=None berilgan, demak funksiya None qaytarishi mumkin,
lekin e'londa -> Talaba yozilgan. Bo'sh ro'yxat berilsa dastur yiqiladi.
Bu haqiqiy xato va u hech qachon ishga tushirilmagan. Tuzatamiz:
shu faylni yaratingsrc/hisobot/hisob.py
"""Statistika: kiritish/chiqarishsiz, faqat hisob.""" from collections import Counter from .modellar import Talaba def guruh_ortachasi(talabalar: list[Talaba]) -> dict[str, float]: yigindi: dict[str, tuple[int, int]] = {} for t in talabalar: jami, soni = yigindi.get(t.guruh, (0, 0)) yigindi[t.guruh] = (jami + t.baho, soni + 1) return {g: round(jami / soni, 2) for g, (jami, soni) in sorted(yigindi.items())} def daraja_taqsimoti(talabalar: list[Talaba]) -> dict[str, int]: return dict(Counter(t.daraja.value for t in talabalar).most_common()) def eng_yaxshi(talabalar: list[Talaba]) -> Talaba | None: """Eng yuqori bahoni qaytaradi. Ro'yxat bo'sh bo'lsa None.""" return max(talabalar, key=lambda t: t.baho, default=None)
python3 -m mypy src/hisobot/hisob.py
src/hisobot/oqish.py:8: error: Function is missing a type annotation [no-untyped-def] src/hisobot/oqish.py:14: error: Function is missing a type annotation [no-untyped-def] Found 2 errors in 1 file (checked 1 source file)
Endi tur e'loni haqiqatni aytadi: Talaba | None. Va bu chaqiruvchini None ni tekshirishga
majbur qiladi:
shu faylni yaratingfoydalanuvchi.py
from hisobot.hisob import eng_yaxshi from hisobot.modellar import Talaba t = [Talaba("Aziz", "A", 85, "2026-09-01")] print(eng_yaxshi(t).ism)
MYPYPATH=src python3 -m mypy --config-file=oddiy_mypy.ini --strict foydalanuvchi.py
src/hisobot/oqish.py:8: error: Function is missing a type annotation [no-untyped-def] src/hisobot/oqish.py:14: error: Function is missing a type annotation [no-untyped-def] foydalanuvchi.py:5: error: Item "None" of "Talaba | None" has no attribute "ism" [union-attr] Found 3 errors in 2 files (checked 1 source file)
mypy None ehtimolini eslatdi: Item "None" of "Talaba | None" has no attribute "ism".
To'g'ri yechim:
shu faylni yaratingfoydalanuvchi.py
from hisobot.hisob import eng_yaxshi from hisobot.modellar import Talaba t = [Talaba("Aziz", "A", 85, "2026-09-01")] eng = eng_yaxshi(t) if eng is None: print("talaba yo'q") else: print(eng.ism)
MYPYPATH=src python3 -m mypy --config-file=oddiy_mypy.ini --strict foydalanuvchi.py .venv/bin/python3 foydalanuvchi.py
src/hisobot/oqish.py:8: error: Function is missing a type annotation [no-untyped-def] src/hisobot/oqish.py:14: error: Function is missing a type annotation [no-untyped-def] Found 2 errors in 1 file (checked 1 source file) Aziz
Bu naql "tekshirishni unutish" xatolarining katta qismini yo'q qiladi.
Butun loyihani tekshiramiz
shu faylni yaratingsrc/hisobot/oqish.py
"""CSV o'qish va qatorlarni tekshirib modelga aylantirish.""" import csv from pathlib import Path from .modellar import Natija, Talaba from .tekshiruv import bahoni_tekshir def oqi(yol: str | Path) -> list[dict[str, str]]: """Faqat o'qiydi: lug'atlar ro'yxati.""" with open(yol, encoding="utf-8") as f: return list(csv.DictReader(f)) def tozala(qatorlar: list[dict[str, str]]) -> Natija: """Xom lug'atlarni Talaba obyektlariga aylantiradi.""" natija = Natija() for i, q in enumerate(qatorlar, start=2): try: natija.talabalar.append(Talaba( ism=q.get("ism", "").strip(), guruh=q.get("guruh", "").strip(), baho=bahoni_tekshir(q.get("baho")), sana=q.get("sana", "").strip(), )) except ValueError as e: natija.xatolar.append((i, q.get("ism", "?"), str(e))) return natija
shu faylni yaratingsrc/hisobot/__main__.py
"""python3 -m hisobot bilan ishga tushadi.""" import sys from .hisob import daraja_taqsimoti, guruh_ortachasi from .oqish import oqi, tozala def main(argv: list[str] | None = None) -> int: argv = sys.argv[1:] if argv is None else argv if not argv: print("ishlatish: python3 -m hisobot <fayl.csv>") return 2 n = tozala(oqi(argv[0])) print(f"jami: {n.jami}, to'g'ri: {len(n.talabalar)}, xato: {len(n.xatolar)}") for g, o in guruh_ortachasi(n.talabalar).items(): print(f" {g}: {o}") print("darajalar:", daraja_taqsimoti(n.talabalar)) for qator, ism, sabab in n.xatolar: print(f" {qator}-qator ({ism}): {sabab}") return 0 if not n.xatolar else 1 if __name__ == "__main__": sys.exit(main())
python3 -m mypy .venv/bin/python3 -m hisobot sinov_malumot.csv
Success: no issues found in 6 source files
jami: 8, to'g'ri: 6, xato: 2
A: 90.67
B: 83.0
C: 61.0
darajalar: {'yaxshi': 3, "a'lo": 2, 'qoniqarli': 1}
5-qator (Elyor): baho son emas: ''
8-qator (Hasan): baho son emas: 'abc'
Butun paket toza: mypy hech qanday xato topmadi, dastur esa ishlaydi.
Turlar qachon yolg'on gapiradi
Tur e'loni — va'da, kafolat emas. Ishga tushish paytida hech kim tekshirmaydi:
def faqat_son(x: int) -> int: return x * 2 print("mypy bunga qarshi bo'lardi, lekin Python bajaradi:", faqat_son("salom"))
mypy bunga qarshi bo'lardi, lekin Python bajaradi: salomsalom
Shuning uchun tashqi ma'lumotni turga ishonib qabul qilmang. Fayl, API yoki foydalanuvchidan
kelgan narsa haqiqatan tekshirilishi kerak — 11-bobda pydantic bilan qilamiz.
:::gotcha Any — tekshiruvni o'chiradi
Any turi "har narsa bo'lishi mumkin" degani va mypy u bilan bog'liq hamma narsani tekshirmay
qo'yadi. U ba'zan kerak, lekin ko'pincha "hozir vaqtim yo'q" degani. mypy --strict da
disallow_any_explicit bilan uni butunlay taqiqlash mumkin.
:::
Amaliy tavsiyalar
| Holat | Tavsiya |
|---|---|
| Yangi loyiha | Birinchi kundan strict = True |
| Mavjud loyiha | Modul-modul yoqing; [mypy-eski.*] ignore_errors = True |
| Tashqi kutubxona turlari yo'q | pip install types-requests yoki ignore_missing_imports |
| Tez prototip | Turlarsiz yozing, lekin strict ni keyin yoqing |
| Kutubxona yozayapsiz | Turlar majburiy — foydalanuvchilaringiz shunga tayanadi |
Xulosa
| Tushuncha | Asosiy fikr |
|---|---|
| Tur ko'rsatkichi | Python tekshirmaydi; mypy tekshiradi |
int \| None |
3.10+ yozuvi; eskisi Optional[int] |
--strict siz |
Turlarsiz funksiyalar umuman tekshirilmaydi — mypy jim "Success" deydi |
| Import kuzatish | Bitta fayl berilsa ham, u import qilgan modullar tekshiriladi |
MYPYPATH |
src/ tartibida paketni topishi uchun kerak |
| Haqiqiy foyda | default=None kabi xatolarni ishga tushirmasdan topadi |
X \| None |
Chaqiruvchini tekshirishga majbur qiladi |
| Tashqi ma'lumot | Tur — va'da, kafolat emas; haqiqatan tekshiring |
Any |
Tekshiruvni o'chiradi; ehtiyot bo'ling |
Talaba ishi
mypy.inidastrict = Falseqiling va butun loyihani qayta tekshiring. Nechta xato yo'qoldi? Ular haqiqatan muammo emasmidi?bahoni_tekshirga-> into'rniga-> int | Noneyozing varaiseo'rnigareturn Noneqiling. Chaqiruvchi kodda mypy nima deydi?Talaba.sananistrdandatetime.datega o'zgartiring. mypy qaysi fayllarda xato ko'rsatadi?typing.Protocolhaqida o'qing vaguruh_ortachasiniTalabao'rniga "guruhvabahomaydonlari bor har qanday obyekt" qabul qiladigan qiling.
Bu bobni bepul o'qidingiz.
Kitob o'rgatadi, lekin sizning kodingiz nega ishlamaganini ayta olmaydi. Kursda uy vazifangiz tekshiriladi.