Asosiy qismga o'tish

Turlar va mypy: xatoni ishga tushirmasdan topish

5-bob 24 kod bloki 2666 so'z

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.

python8 qator
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)
Chiqish
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

python15 qator
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__)
Chiqish
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

python16 qator
"""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

python45 qator
"""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

python7 qator
[mypy]
python_version = 3.10
files = src
strict = True

[mypy-tests.*]
strict = False
matn2 qator
.venv/bin/python3 -m pip install --quiet mypy
python3 -m mypy --version
Chiqish (qaytish kodi 0, 6189 ms)
mypy 2.3.1 (compiled: yes)

Endi tekshiramiz:

matn1 qator
python3 -m mypy src/hisobot/tekshiruv.py src/hisobot/modellar.py
Chiqish (qaytish kodi 1, 1411 ms)
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

python6 qator
def qosh(a, b):
    return a + b


def ishlat():
    return qosh("1", 2)

shu faylni yaratingoddiy_mypy.ini

python2 qator
[mypy]
python_version = 3.10

Ikki sozlama bilan yonma-yon tekshiramiz. oddiy_mypy.ini da strict yo'q:

python4 qator
echo "=== strict BILAN ==="
python3 -m mypy --strict turlarsiz.py
echo "=== strict SIZ ==="
python3 -m mypy --config-file=oddiy_mypy.ini turlarsiz.py
Chiqish (qaytish kodi 0, 2176 ms)
=== 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

python6 qator
def qosh(a: int, b: int) -> int:
    return a + b


def ishlat() -> int:
    return qosh("1", 2)
matn1 qator
python3 -m mypy turli.py
Chiqish (qaytish kodi 1, 1279 ms)
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

python21 qator
"""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)
matn1 qator
python3 -m mypy src/hisobot/hisob.py
Chiqish (qaytish kodi 1, 946 ms)
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

python21 qator
"""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)
matn1 qator
python3 -m mypy src/hisobot/hisob.py
Chiqish (qaytish kodi 1, 456 ms)
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

python5 qator
from hisobot.hisob import eng_yaxshi
from hisobot.modellar import Talaba

t = [Talaba("Aziz", "A", 85, "2026-09-01")]
print(eng_yaxshi(t).ism)
python1 qator
MYPYPATH=src python3 -m mypy --config-file=oddiy_mypy.ini --strict foydalanuvchi.py
Chiqish (qaytish kodi 1, 468 ms)
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

python9 qator
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)
python2 qator
MYPYPATH=src python3 -m mypy --config-file=oddiy_mypy.ini --strict foydalanuvchi.py
.venv/bin/python3 foydalanuvchi.py
Chiqish (qaytish kodi 0, 1004 ms)
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

python28 qator
"""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

python24 qator
"""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())
matn2 qator
python3 -m mypy
.venv/bin/python3 -m hisobot sinov_malumot.csv
Chiqish (qaytish kodi 1, 1089 ms)
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:

python4 qator
def faqat_son(x: int) -> int:
    return x * 2

print("mypy bunga qarshi bo'lardi, lekin Python bajaradi:", faqat_son("salom"))
Chiqish
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

  1. mypy.ini da strict = False qiling va butun loyihani qayta tekshiring. Nechta xato yo'qoldi? Ular haqiqatan muammo emasmidi?
  2. bahoni_tekshir ga -> int o'rniga -> int | None yozing va raise o'rniga return None qiling. Chaqiruvchi kodda mypy nima deydi?
  3. Talaba.sana ni str dan datetime.date ga o'zgartiring. mypy qaysi fayllarda xato ko'rsatadi?
  4. typing.Protocol haqida o'qing va guruh_ortachasi ni Talaba o'rniga "guruh va baho maydonlari 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.

Telegramda yozish Narxlar