Asosiy qismga o'tish

Modul, paket va import tizimi

3-bob 21 kod bloki 1736 so'z

Import aslida nima qiladi

import hisobot yozganingizda Python uch ish qiladi: faylni topadi, uni bajaradi, natijani keshlaydi. Uchalasi ham muammo manbai bo'lishi mumkin, shuning uchun har birini ko'rib chiqamiz.

python4 qator
import sys, json
print("keshlangan modullar soni:", len(sys.modules))
print("misol:", [m for m in list(sys.modules) if m.startswith("json")][:3])
print("json qayerdan yuklandi:", sys.modules["json"].__file__)
Chiqish
keshlangan modullar soni: 818
misol: ['json.scanner', 'json.decoder', 'json.encoder']
json qayerdan yuklandi: /usr/lib/python3.10/json/__init__.py

sys.modules — keshi. Bir modul bir marta bajariladi, keyin shu keshdan olinadi.

shu faylni yaratingsanagich.py

python2 qator
print("  [sanagich moduli bajarilmoqda]")
QIYMAT = 42
python7 qator
python3 -c "
import sanagich
import sanagich
import sanagich
print('uch marta import qilindi, lekin:')
print('QIYMAT =', sanagich.QIYMAT)
"
Chiqish (qaytish kodi 0, 18 ms)
  [sanagich moduli bajarilmoqda]
uch marta import qilindi, lekin:
QIYMAT = 42

Xabar bir marta chiqdi. Bu muhim xulq: modul darajasidagi kod (fayl ichidagi, funksiyadan tashqaridagi kod) faqat birinchi importda ishlaydi.

Python modulni qayerdan qidiradi

python3 qator
import sys
for i, yol in enumerate(sys.path[:6]):
    print(f"  {i}. {yol or '(joriy papka)'}")
Chiqish
  0. /mnt/c/Users/Admin/Desktop/Course Material/Python_Muhandisligi/ish
  1. /mnt/c/Users/Admin/Desktop/Course Material/Python_Muhandisligi/ish
  2. /mnt/c/Users/Admin/Desktop/Course Material/Python_Muhandisligi/book_src
  3. /usr/lib/python310.zip
  4. /usr/lib/python3.10
  5. /usr/lib/python3.10/lib-dynload

Tartib muhim: Python yuqoridan pastga qaraydi va birinchi topilganini oladi. Shuning uchun o'z faylingizni json.py deb nomlasangiz, standart json o'rniga sizniki yuklanadi.

Buni ko'rsatamiz:

shu faylni yaratingtuzoq/json.py

python1 qator
XABAR = "bu standart kutubxona EMAS, bu mening faylim"
bash9 qator
cd tuzoq && python3 -c "
import json
print('json.__file__ =', json.__file__)
print(getattr(json, 'XABAR', 'standart json'))
try:
    json.dumps({'a': 1})
except AttributeError as e:
    print('XATO:', e)
"
Chiqish (qaytish kodi 0, 19 ms)
json.__file__ = /mnt/c/Users/Admin/Desktop/Course Material/Python_Muhandisligi/ish/tuzoq/json.py
bu standart kutubxona EMAS, bu mening faylim
XATO: module 'json' has no attribute 'dumps'

Standart json "yo'qoldi". Bu xato haqiqiy loyihalarda tez-tez uchraydi va topish qiyin, chunki xabar mutlaqo boshqa joyda chiqadi.

:::gotcha Fayl nomlari Hech qachon standart kutubxona nomlarini ishlatmang: json.py, csv.py, time.py, random.py, types.py, string.py, email.py, logging.py, test.py. Tekshirish oson: python3 -c "import X" — xato bermasa, bu nom band. :::

Absolyut va nisbiy import

Paket ichida ikki usul bor:

shu faylni yaratingsrc/hisobot/tekshiruv.py

python14 qator
"""Qoidalar: qator to'g'rimi yoki yo'q."""

ENG_KATTA_BAHO = 100


def bahoni_tekshir(xom):
    """Matnni bahoga aylantiradi. Xato bo'lsa sabab bilan ValueError."""
    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/oqish.py

python21 qator
"""CSV o'qish va qatorlarni tekshirish."""
import csv

from hisobot.tekshiruv import bahoni_tekshir        # absolyut import
from .tekshiruv import ENG_KATTA_BAHO               # nisbiy import — xuddi shu modul


def oqi(yol):
    with open(yol, encoding="utf-8") as f:
        return list(csv.DictReader(f))


def tozala(qatorlar):
    """(to'g'ri qatorlar, xatolar) qaytaradi."""
    yaxshi, xatolar = [], []
    for i, q in enumerate(qatorlar, start=2):
        try:
            yaxshi.append({**q, "baho": bahoni_tekshir(q.get("baho"))})
        except ValueError as e:
            xatolar.append((i, q.get("ism", "?"), str(e)))
    return yaxshi, xatolar
python9 qator
.venv/bin/python3 -c "
from hisobot.oqish import oqi, tozala
from hisobot.tekshiruv import ENG_KATTA_BAHO
print('eng katta baho:', ENG_KATTA_BAHO)
yaxshi, xatolar = tozala(oqi('sinov_malumot.csv'))
print('to\'g\'ri:', len(yaxshi), '| xatolar:', len(xatolar))
for x in xatolar:
    print('  ', x)
"
Chiqish (qaytish kodi 0, 68 ms)
eng katta baho: 100
to'g'ri: 6 | xatolar: 2
   (5, 'Elyor', "baho son emas: ''")
   (8, 'Hasan', "baho son emas: 'abc'")

Ikkala import usuli ham ishladi. Farqi:

Usul Yozuv Qachon
Absolyut from hisobot.tekshiruv import X Odatiy tanlov — aniq va ko'chirishga chidamli
Nisbiy from .tekshiruv import X Paket ichida, paket nomi o'zgarishi mumkin bo'lsa

Nisbiy import faqat paket ichida ishlaydi. To'g'ridan-to'g'ri ishga tushirilgan faylda u xato beradi:

matn1 qator
.venv/bin/python3 src/hisobot/oqish.py
Chiqish (qaytish kodi 1, 67 ms)
Traceback (most recent call last):
  File "/mnt/c/Users/Admin/Desktop/Course Material/Python_Muhandisligi/ish/src/hisobot/oqish.py", line 5, in <module>
    from .tekshiruv import ENG_KATTA_BAHO               # nisbiy import — xuddi shu modul
ImportError: attempted relative import with no known parent package

Qaytish kodi 1. Sabab: fayl to'g'ridan-to'g'ri ishga tushirilganda u paket a'zosi emas, shunchaki skript — demak "nisbiy" nimaga nisbatan ekani noma'lum.

To'g'ri usul — -m bayrog'i bilan modul sifatida chaqirish:

shu faylni yaratingsrc/hisobot/__main__.py

python21 qator
"""python3 -m hisobot bilan ishga tushadi."""
import sys

from .oqish import oqi, tozala
from .hisob import guruh_ortachasi


def main(argv=None):
    argv = sys.argv[1:] if argv is None else argv
    if not argv:
        print("ishlatish: python3 -m hisobot <fayl.csv>")
        return 2
    yaxshi, xatolar = tozala(oqi(argv[0]))
    print(f"to'g'ri: {len(yaxshi)}, xato: {len(xatolar)}")
    for g, o in guruh_ortachasi(yaxshi).items():
        print(f"  {g}: {o}")
    return 0 if not xatolar else 1


if __name__ == "__main__":
    sys.exit(main())
matn2 qator
.venv/bin/python3 -m hisobot sinov_malumot.csv
.venv/bin/python3 -m hisobot
Chiqish (qaytish kodi 2, 184 ms)
to'g'ri: 6, xato: 2
  A: 90.67
  B: 83.0
  C: 61.0
ishlatish: python3 -m hisobot <fayl.csv>

Ikkinchi chaqiruv qaytish kodi 2 berdi — argument berilmagani uchun. Bu tasodifiy emas: buyruq qatori dasturlari muvaffaqiyatda 0, xatoda noldan farqli kod qaytaradi. 15-bobda buni kengaytiramiz.

Aylanma import (circular import)

Ikki modul bir-birini import qilsa nima bo'ladi?

shu faylni yaratingaylana_a.py

python7 qator
import aylana_b

NOM = "A"


def a_dan():
    return f"A -> {aylana_b.NOM}"

shu faylni yaratingaylana_b.py

python7 qator
import aylana_a

NOM = "B"


def b_dan():
    return f"B -> {aylana_a.NOM}"
python1 qator
python3 -c "import aylana_a; print(aylana_a.a_dan())"
Chiqish (qaytish kodi 0, 22 ms)
A -> B

Bu ishladi, chunki import aylana_b moduli obyekt sifatida oladi va aylana_b.NOM ga faqat funksiya chaqirilganda murojaat qiladi — o'shanda modul to'liq yuklangan bo'ladi.

Endi from ... import ... bilan sinaymiz:

shu faylni yaratingaylana_c.py

python3 qator
from aylana_d import NOM_D

NOM = "C"

shu faylni yaratingaylana_d.py

python3 qator
from aylana_c import NOM

NOM_D = "D"
matn1 qator
python3 -c "import aylana_c"
Chiqish (qaytish kodi 1, 82 ms)
Traceback (most recent call last):
  File "<string>", line 1, in <module>
  File "/mnt/c/Users/Admin/Desktop/Course Material/Python_Muhandisligi/ish/aylana_c.py", line 1, in <module>
    from aylana_d import NOM_D
  File "/mnt/c/Users/Admin/Desktop/Course Material/Python_Muhandisligi/ish/aylana_d.py", line 1, in <module>
    from aylana_c import NOM
ImportError: cannot import name 'NOM' from partially initialized module 'aylana_c' (most likely due to a circular import) (/mnt/c/Users/Admin/Desktop/Course Material/Python_Muhandisligi/ish/aylana_c.py)

Bu yiqildi. ImportError — aylana_c hali NOM ni belgilamagan paytda aylana_d uni so'radi.

Farqni tushunish muhim: import X kechiktiradi, from X import Y esa darhol qiymatni talab qiladi.

Aylanma importning haqiqiy yechimi — uni yo'q qilish. Odatda u dizayn muammosini bildiradi: ikki modul bir-biriga juda bog'lanib qolgan. Umumiy qismni uchinchi modulga chiqaring.

__init__.py nima qiladi

shu faylni yaratingsrc/hisobot/__init__.py

python8 qator
"""CSV ma'lumotini tekshirib hisobot chiqaradigan asbob."""

from .hisob import guruh_ortachasi
from .oqish import oqi, tozala
from .tekshiruv import bahoni_tekshir

__version__ = "0.3.0"
__all__ = ["guruh_ortachasi", "oqi", "tozala", "bahoni_tekshir", "__version__"]
python6 qator
.venv/bin/python3 -c "
import hisobot
print('versiya:', hisobot.__version__)
print('ochiq nomlar:', hisobot.__all__)
print('to\'g\'ridan-to\'g\'ri ishlaydi:', hisobot.bahoni_tekshir('85'))
"
Chiqish (qaytish kodi 0, 352 ms)
versiya: 0.3.0
ochiq nomlar: ['guruh_ortachasi', 'oqi', 'tozala', 'bahoni_tekshir', '__version__']
to'g'ridan-to'g'ri ishlaydi: 85

Endi foydalanuvchi from hisobot.tekshiruv import bahoni_tekshir o'rniga from hisobot import bahoni_tekshir yoza oladi. __init__.py — paketning ommaviy yuzi.

__all__ esa ikki ish qiladi: from hisobot import * nimani olishini belgilaydi va o'quvchiga "mana bular rasmiy API" deb aytadi.

:::tip __init__.py ni yengil saqlang Unga faqat import va versiya yozing, mantiq yozmang. Sababi: import hisobot har safar __init__.py ni to'liq bajaradi. Unda og'ir ish bo'lsa (fayl o'qish, tarmoqqa chiqish), har import sekinlashadi. :::

Buni o'lchaymiz:

python16 qator
import subprocess, time, statistics
ISH_S = str(ISH)

def olch(kod, n=5):
    vaqtlar = []
    for _ in range(n):
        t0 = time.perf_counter()
        subprocess.run([f"{ISH_S}/.venv/bin/python3", "-c", kod], cwd=ISH_S, capture_output=True)
        vaqtlar.append((time.perf_counter() - t0) * 1000)
    return round(statistics.median(vaqtlar), 1)

bosh = olch("pass", n=9)
bilan = olch("import hisobot", n=9)
print(f"bo'sh Python ishga tushishi : {bosh} ms")
print(f"import hisobot bilan        : {bilan} ms")
print(f"paketning o'z narxi         : {round(bilan - bosh, 1)} ms")
Chiqish
bo'sh Python ishga tushishi : 39.7 ms
import hisobot bilan        : 59.0 ms
paketning o'z narxi         : 19.3 ms

Xulosa

Tushuncha Asosiy fikr
sys.modules Modul bir marta bajariladi, keyin keshdan olinadi
sys.path Yuqoridan pastga qidiriladi; joriy papka birinchi
Nom to'qnashuvi json.py deb nomlamang — standart kutubxonani berkitadi
Absolyut import Odatiy tanlov: from hisobot.tekshiruv import X
Nisbiy import Faqat paket ichida; to'g'ridan-to'g'ri ishga tushirilsa xato
python3 -m paket __main__.py ni ishga tushiradi, nisbiy importlar ishlaydi
Aylanma import import X chidaydi, from X import Y yiqiladi; ildizi — dizayn
__init__.py Paketning ommaviy yuzi; yengil saqlang

Talaba ishi

  1. sys.path ning birinchi elementini o'zgartirib ko'ring (sys.path.insert). Qanday holatda bu kerak bo'ladi va nega undan qochish kerak?
  2. aylana_c/aylana_d ni tuzating: umumiy qismni aylana_umumiy.py ga chiqaring.
  3. __init__.py ga time.sleep(0.5) qo'shing va yuqoridagi o'lchovni qayta ishga tushiring. import hisobot qancha sekinlashdi?
  4. python3 -m hisobot ga mavjud bo'lmagan fayl nomini bering. Qanday xato chiqadi va foydalanuvchi uchun tushunarlimi? (8-bobda buni tuzatamiz.)

Bu bobni bepul o'qidingiz.

Kitob o'rgatadi, lekin sizning kodingiz nega ishlamaganini ayta olmaydi. Kursda uy vazifangiz tekshiriladi.

Telegramda yozish Narxlar