IlmHamroh
Python kursi/Standart kutubxona16/16-dars24 daqiqa
Mundarija (23)

15.16-dars: logging

15-QISM — STANDART KUTUBXONA · 16-dars


1. Kirish va motivatsiya

Kichik skriptda print yetadi. Lekin serverda ishlayotgan dasturda savollar boshqa: kechasi soat 3 da nima bo'ldi? Qaysi so'rovda xato chiqdi? Qaysi foydalanuvchi uchun? Nega bu xabar faqat ishlab chiqarishda ko'rinadi, ishlab chiqishda esa yo'q?

print bu savollarga javob bermaydi: darajasi yo'q (xato va oddiy xabar bir xil), manbasi yo'q (qaysi modul), vaqti yo'q, o'chirib bo'lmaydi va faqat stdout ga boradi (15.11-dars: diagnostika stderr ga). logging — standart kutubxonadagi to'liq jurnal tizimi.

Real vaziyat. Jamoa to'lov xizmatiga batafsil jurnal qo'shdi:

python
logger.debug(f"to'lov so'rovi: {sorov}")

Ishlab chiqarishda DEBUG o'chiq edi — lekin f-string baribir har so'rovda hisoblanardi va sorov ning __repr__ i bazaga murojaat qilardi: javob vaqti 40% oshdi. Keyinroq kimdir sozlamani dictConfig ga ko'chirdi va eski modullarning jurnallari butunlay yo'qoldi — disable_existing_loggers sukut bo'yicha True. Uchinchi muammo: xato xabarlari ikki marta yozilardi va jurnal hajmi ikki barobar edi. Karta raqamlari ham to'liq holda jurnalga tushardi.

Bu darsda logging arxitekturasini (logger, handler, formatter, filter) va shunday tuzoqlarni o'rganamiz. Bu dars 15-qismni yakunlaydi.

Bu darsda:

  • Nega print emas; darajalar
  • Arxitektura: Logger → Handler → Formatter, Filter
  • Logger ierarxiyasi va getLogger(__name__), propagatsiya
  • basicConfig bir marta ishlaydi; force=True
  • Dangasa formatlash: %s va f-string
  • exception(), exc_info, stack_info
  • extra, LoggerAdapter, filtrlar — kontekst va maxfiy ma'lumotni yashirish
  • dictConfig va disable_existing_loggers
  • Handlerlar: fayl, aylanma fayl, QueueHandler
  • Kutubxonalarda: NullHandler
  • Amaliy: tuzilgan (JSON) jurnal va so'rov identifikatori
  • 15-qism yakuni

2. Nazariya — chuqur tushuntirish

2.1. Darajalar

Daraja Son Qachon
DEBUG 10 Ishlab chiqishda batafsil tafsilot
INFO 20 Muhim normal voqealar: ishga tushdi, buyurtma yaratildi
WARNING 30 Kutilmagan, lekin ish davom etadi: qayta urinish, eskirgan API
ERROR 40 Amal bajarilmadi
CRITICAL 50 Dastur ishlay olmaydi

Logger darajasi va handler darajasi — ikki alohida filtr: xabar ikkalasidan o'tishi kerak.

Sozlanmagan dasturda (basicConfig yo'q) faqat WARNING va undan yuqorisi chiqadi — logging.lastResort handler orqali stderr ga, formatsiz.

2.2. Arxitektura

logger.info("...")          →  LogRecord  →  Logger darajasi va filtrlari
                                               ↓
                                           Handler lar (har biri: daraja, filtr, Formatter)
                                               ↓
                                           ota logger lar handlerlari (propagate=True bo'lsa)
Komponent Vazifasi Misollar
Logger Xabar manbai, nomli, ierarxik logging.getLogger(__name__)
Handler Qayerga yozish StreamHandler, FileHandler, RotatingFileHandler, QueueHandler
Formatter Qanday ko'rinishda "%(asctime)s %(levelname)s %(name)s: %(message)s"
Filter O'tkazish/o'zgartirish Maxfiy ma'lumotni yashirish, kontekst qo'shish
LogRecord Bitta xabar name, levelname, msg, args, exc_info, created, ...

2.3. Ierarxiya va propagatsiya

python
logger = logging.getLogger(__name__)     # ⭐ "ilova.db.sorovlar"
  • Nomlar nuqta bilan ierarxiya hosil qiladi: ilova.db — ilova ning bolasi, hammasi — root ning avlodi.
  • Darajasi berilmagan logger ota darajasini oladi (getEffectiveLevel()).
  • Xabar o'z handlerlaridan keyin ota logger handlerlariga ham boradi (propagate=True).

Ikki marta yozish: handler ham ilova ga, ham root ga qo'shilsa — har xabar ikki marta chiqadi. Qoida: handlerlar faqat root da (yoki ilovaning eng yuqori loggerida) va propagate=False faqat aniq sabab bilan.

Modul ichida handler qo'shmang — faqat getLogger(__name__). Handler va formatlarni dasturning kirish nuqtasi sozlaydi.

2.4. basicConfig

python
logging.basicConfig(level=logging.INFO, format="%(levelname)s %(name)s: %(message)s")
Fakt Tafsilot
Root da handler bo'lsa Hech narsa qilmaydi — jim
force=True Mavjud root handlerlarini olib tashlab, qayta sozlaydi
Qachon chaqirish Dastur boshida, bir marta, main() ichida
Modul importida chaqirish Boshqa dasturning sozlamasini buzadi

2.5. Dangasa formatlash

python
logger.debug("to'lov: %s", sorov)      # ✅ DEBUG o'chiq bo'lsa — str(sorov) chaqirilmaydi
logger.debug(f"to'lov: {sorov}")       # ❌ har doim hisoblanadi
if logger.isEnabledFor(logging.DEBUG): # juda qimmat hisob uchun
    logger.debug("statistika: %s", ogir_hisob())
Usul O'chiq darajada narx Boshqa farq
%s argumentlar Deyarli nol Bir xil xabar shabloni — jurnal tizimlarida guruhlash oson
f-string To'liq hisob Har xabar noyob satr

2.6. Xatolar va kontekst

Vosita Nima beradi
logger.exception("...") ERROR + traceback — faqat except ichida
logger.error("...", exc_info=True) Istalgan darajada traceback
stack_info=True Joriy chaqiruv steki (xatosiz)
extra={"sorov_id": ...} Yozuvga maydon qo'shish (formatda %(sorov_id)s)
LoggerAdapter(logger, {"sorov_id": ...}) Kontekstni har chaqiruvda takrorlamaslik
Filter Har yozuvni o'zgartirish yoki rad etish

Formatda %(sorov_id)s bo'lsa-yu, yozuvda extra bo'lmasa — logging istisno ko'tarmaydi, stderr ga "--- Logging error ---" yozadi va xabar yo'qoladi. Yechim: filtr orqali sukut qiymat qo'shish.

Maxfiy ma'lumot (parol, token, karta raqami) jurnalga tushmasligi uchun — handler darajasidagi filtr va repr larni ehtiyotkor yozish.

2.7. dictConfig

python
logging.config.dictConfig({
    "version": 1,
    "disable_existing_loggers": False,       # ⭐ doim aniq yozing
    "formatters": {"oddiy": {"format": "%(levelname)s %(name)s: %(message)s"}},
    "handlers": {"konsol": {"class": "logging.StreamHandler", "formatter": "oddiy", "stream": "ext://sys.stderr"}},
    "root": {"handlers": ["konsol"], "level": "INFO"},
})

disable_existing_loggers sukut bo'yicha True: dictConfig chaqirilishidan oldin yaratilgan loggerlar (odatda — import qilingan modullarning getLogger(__name__) i) o'chiriladi, ularning xabarlari jim yo'qoladi.

3.12+: logging.getHandlerByName("konsol"), logging.getHandlerNames().

2.8. Handlerlar

Handler Qachon
StreamHandler() stderr (sukut) — konteyner va 12-faktor ilovalar uchun
FileHandler(yol, encoding="utf-8") Faylga
RotatingFileHandler(yol, maxBytes, backupCount) Hajm bo'yicha aylanish
TimedRotatingFileHandler(yol, when="midnight") Vaqt bo'yicha aylanish
QueueHandler + QueueListener Sekin handlerlarni (tarmoq, fayl) alohida ipga ko'chirish
NullHandler Kutubxonalarda: "sozlanmagan" ogohlantirishining oldini olish
handlers.SysLogHandler, SMTPHandler, HTTPHandler Tashqi tizimlar

Kutubxona qoidasi: kutubxona hech qachon basicConfig chaqirmaydi va handler qo'shmaydi — faqat getLogger(__name__) va kerak bo'lsa addHandler(NullHandler()). Ilova qanday yozishni o'zi hal qiladi.

Konteyner va bulutda: faylga emas, stderr ga, bir qatorli JSON formatda yozing — jurnal tizimi (Loki, ELK, CloudWatch) uni yig'adi va maydonlar bo'yicha qidiradi.


3. Tez ma'lumotnoma

python
import logging

logger = logging.getLogger(__name__)              # modulda — faqat shu

def main() -> int:
    logging.basicConfig(level=logging.INFO,
                        format="%(asctime)s %(levelname)s %(name)s: %(message)s")
    logger.info("ishga tushdi: port=%s", port)
    try:
        ...
    except Exception:
        logger.exception("buyurtma bajarilmadi: id=%s", buyurtma_id)
        return 1
    return 0

log = logging.LoggerAdapter(logger, {"sorov_id": sid})
logging.config.dictConfig({..., "disable_existing_loggers": False})

Qoidalar

modulda: getLogger(__name__), handler yo'q
sozlash — kirish nuqtasida, bir marta
%s argumentlar, f-string emas
except ichida — logger.exception
dictConfig — disable_existing_loggers: False
handler — root da; ikki marta chiqsa, propagatsiyani tekshiring
maxfiy ma'lumot — filtr bilan yashiring

4. Batafsil misollar

Misol 1 — Darajalar, ierarxiya va propagatsiya

python
"""Sozlanmagan holat (lastResort); logger va handler darajalari; getLogger ierarxiyasi; getEffectiveLevel; ikki marta yozish va uni tuzatish; basicConfig va force."""

import contextlib
import io
import logging


def handler(nishon: str, oqim: io.StringIO, daraja: int = logging.NOTSET) -> logging.Handler:
    h = logging.StreamHandler(oqim)
    h.setLevel(daraja)
    h.setFormatter(logging.Formatter(f"{nishon} %(levelname)s %(name)s: %(message)s"))
    return h


def main() -> None:
    print("=== 1. Sozlanmagan dastur ===")
    xato = io.StringIO()
    with contextlib.redirect_stderr(xato):
        log = logging.getLogger("sozlanmagan")
        log.debug("debug"); log.info("info"); log.warning("warning"); log.error("error")
    print(f"  stderr: {xato.getvalue().splitlines()}  ← faqat WARNING+, formatsiz (lastResort)")

    print("\n=== 2. Logger va handler darajalari ===")
    oqim = io.StringIO()
    root = logging.getLogger()
    root.handlers.clear()
    root.setLevel(logging.DEBUG)
    root.addHandler(handler("[konsol]", oqim, logging.INFO))
    db = logging.getLogger("ilova.db")
    db.debug("SQL: SELECT 1")
    db.info("ulanish ochildi")
    logging.getLogger("ilova.tolov").setLevel(logging.ERROR)
    logging.getLogger("ilova.tolov.karta").warning("urinish 2/3")
    logging.getLogger("ilova.tolov.karta").error("rad etildi")
    for qator in oqim.getvalue().splitlines():
        print(f"  {qator}")
    karta = logging.getLogger("ilova.tolov.karta")
    print(f"  ilova.tolov.karta: o'z darajasi={karta.level}, samarali={logging.getLevelName(karta.getEffectiveLevel())}, ota={karta.parent.name}")

    print("\n=== 3. ⚠️ Ikki marta yozish ===")
    oqim = io.StringIO()
    root.handlers.clear()
    root.addHandler(handler("[root]", oqim))
    ilova = logging.getLogger("ilova")
    ilova.addHandler(handler("[ilova]", oqim))
    logging.getLogger("ilova.api").warning("sekin javob: 2.3 s")
    print(f"  {oqim.getvalue().splitlines()}")
    ilova.handlers.clear()
    oqim.truncate(0); oqim.seek(0)
    logging.getLogger("ilova.api").warning("sekin javob: 2.3 s")
    print(f"  handler faqat root da: {oqim.getvalue().splitlines()}")

    print("\n=== 4. ⚠️ basicConfig bir marta ===")
    root.handlers.clear()
    birinchi, ikkinchi = io.StringIO(), io.StringIO()
    logging.basicConfig(stream=birinchi, level=logging.INFO, format="1: %(message)s")
    logging.basicConfig(stream=ikkinchi, level=logging.DEBUG, format="2: %(message)s")
    logging.getLogger("x").info("salom")
    print(f"  ikkinchi basicConfig jim e'tiborsiz: birinchi={birinchi.getvalue().strip()!r}, ikkinchi={ikkinchi.getvalue()!r}")
    logging.basicConfig(stream=ikkinchi, level=logging.DEBUG, format="2: %(message)s", force=True)
    logging.getLogger("x").debug("endi ishladi")
    print(f"  force=True: ikkinchi={ikkinchi.getvalue().strip()!r}")
    root.handlers.clear()


if __name__ == "__main__":
    main()

Natijaning muhim qismi:

text
=== 1. Sozlanmagan dastur ===
  stderr: ['warning', 'error']  ← faqat WARNING+, formatsiz (lastResort)

=== 2. Logger va handler darajalari ===
  [konsol] INFO ilova.db: ulanish ochildi
  [konsol] ERROR ilova.tolov.karta: rad etildi
  ilova.tolov.karta: o'z darajasi=0, samarali=ERROR, ota=ilova.tolov

=== 3. ⚠️ Ikki marta yozish ===
  ['[ilova] WARNING ilova.api: sekin javob: 2.3 s', '[root] WARNING ilova.api: sekin javob: 2.3 s']
  handler faqat root da: ['[root] WARNING ilova.api: sekin javob: 2.3 s']

=== 4. ⚠️ basicConfig bir marta ===
  ikkinchi basicConfig jim e'tiborsiz: birinchi='1: salom', ikkinchi=''
  force=True: ikkinchi='2: endi ishladi'

Nima ko'rsatdi: 2.1, 2.2, 2.3, 2.4-bo'limlar.

Misol 2 — Formatlash, xatolar va kontekst

python
"""Dangasa %s va f-string narxi; isEnabledFor; exception va exc_info; extra va yetishmayotgan maydon xatosi; LoggerAdapter; maxfiy ma'lumotni yashiruvchi filtr."""

import contextlib
import io
import logging
import re


class Sorov:
    def __init__(self) -> None:
        self.repr_chaqiruvlari = 0

    def __repr__(self) -> str:
        self.repr_chaqiruvlari += 1               # masalan, bazaga murojaat
        return "Sorov(id=42)"


class KontekstFiltri(logging.Filter):
    def filter(self, yozuv: logging.LogRecord) -> bool:
        if not hasattr(yozuv, "sorov_id"):
            yozuv.sorov_id = "-"
        return True


class MaxfiyFiltr(logging.Filter):
    NAQSHLAR = [
        (re.compile(r"(?<![0-9])([0-9]{4})[0-9 ]{8,11}([0-9]{4})(?![0-9])"), r"\1 **** **** \2"),
        (re.compile(r"(parol|token)=\S+", re.IGNORECASE), r"\1=***"),
    ]

    def filter(self, yozuv: logging.LogRecord) -> bool:
        matn = yozuv.getMessage()
        for naqsh, almashtirma in self.NAQSHLAR:
            matn = naqsh.sub(almashtirma, matn)
        yozuv.msg, yozuv.args = matn, None
        return True


def logger_yarat(nom: str, oqim: io.StringIO, fmt: str, daraja: int = logging.DEBUG) -> logging.Logger:
    log = logging.getLogger(nom)
    log.handlers.clear()
    log.propagate = False
    log.setLevel(daraja)
    h = logging.StreamHandler(oqim)
    h.setFormatter(logging.Formatter(fmt))
    log.addHandler(h)
    return log


def main() -> None:
    print("=== 1. ⚠️ Dangasa formatlash ===")
    log = logger_yarat("tolov", io.StringIO(), "%(message)s", daraja=logging.INFO)
    sorov = Sorov()
    for _ in range(1000):
        log.debug("so'rov: %r", sorov)
    print(f"  %r argument, DEBUG o'chiq: repr chaqiruvlari = {sorov.repr_chaqiruvlari}")
    for _ in range(1000):
        log.debug(f"so'rov: {sorov!r}")
    print(f"  f-string, DEBUG o'chiq:    repr chaqiruvlari = {sorov.repr_chaqiruvlari}")
    print(f"  isEnabledFor(DEBUG): {log.isEnabledFor(logging.DEBUG)}")

    print("\n=== 2. exception ===")
    oqim = io.StringIO()
    log = logger_yarat("hisob", oqim, "%(levelname)s %(message)s")
    try:
        {"narx": 100}["chegirma"]
    except KeyError:
        log.exception("buyurtma hisoblanmadi: id=%s", 1234)
    qatorlar = oqim.getvalue().splitlines()
    print(f"  birinchi qator: {qatorlar[0]!r}")
    print(f"  traceback bor: {qatorlar[1] == 'Traceback (most recent call last):'}, oxirgi: {qatorlar[-1]!r}")

    print("\n=== 3. ⚠️ extra va yetishmayotgan maydon ===")
    oqim = io.StringIO()
    log = logger_yarat("api", oqim, "%(levelname)s [%(sorov_id)s] %(message)s")
    log.info("buyurtma yaratildi", extra={"sorov_id": "r-7f3a"})
    xato = io.StringIO()
    with contextlib.redirect_stderr(xato):
        log.info("sorov_id siz xabar")
    print(f"  jurnal: {oqim.getvalue().splitlines()}")
    print(f"  stderr: {xato.getvalue().splitlines()[0]!r} — xabar yo'qoldi")
    log.handlers[0].addFilter(KontekstFiltri())
    log.info("filtr bilan")
    print(f"  filtr sukut qo'ydi: {oqim.getvalue().splitlines()[-1]!r}")

    print("\n=== 4. LoggerAdapter ===")
    adapter = logging.LoggerAdapter(log, {"sorov_id": "r-9b21"})
    adapter.info("to'lov boshlandi")
    adapter.warning("bank javobi sekin")
    print(f"  {oqim.getvalue().splitlines()[-2:]}")

    print("\n=== 5. Maxfiy ma'lumotni yashirish ===")
    oqim = io.StringIO()
    log = logger_yarat("xavfsiz", oqim, "%(message)s")
    log.handlers[0].addFilter(MaxfiyFiltr())
    log.info("karta %s bilan to'lov", "8600 1234 5678 9012")
    log.warning("ulanish: host=db parol=Maxfiy123 token=sk_live_abc")
    for qator in oqim.getvalue().splitlines():
        print(f"  {qator}")


if __name__ == "__main__":
    main()

Natijaning muhim qismi:

text
=== 1. ⚠️ Dangasa formatlash ===
  %r argument, DEBUG o'chiq: repr chaqiruvlari = 0
  f-string, DEBUG o'chiq:    repr chaqiruvlari = 1000
  isEnabledFor(DEBUG): False

=== 2. exception ===
  birinchi qator: 'ERROR buyurtma hisoblanmadi: id=1234'
  traceback bor: True, oxirgi: "KeyError: 'chegirma'"

=== 3. ⚠️ extra va yetishmayotgan maydon ===
  jurnal: ['INFO [r-7f3a] buyurtma yaratildi']
  stderr: '--- Logging error ---' — xabar yo'qoldi
  filtr sukut qo'ydi: 'INFO [-] filtr bilan'

=== 4. LoggerAdapter ===
  ["INFO [r-9b21] to'lov boshlandi", 'WARNING [r-9b21] bank javobi sekin']

=== 5. Maxfiy ma'lumotni yashirish ===
  karta 8600 **** **** 9012 bilan to'lov
  ulanish: host=db parol=*** token=***

Nima ko'rsatdi: 2.5, 2.6-bo'limlar.

Misol 3 — Konfiguratsiya va handlerlar

python
"""dictConfig va disable_existing_loggers tuzog'i; getHandlerByName; RotatingFileHandler; QueueHandler va QueueListener; kutubxonada NullHandler."""

import io
import logging
import logging.config
import logging.handlers
import queue
import tempfile
import time
from pathlib import Path


def konfiguratsiya(oqim: io.StringIO, disable: bool | None) -> dict:
    sozlama = {
        "version": 1,
        "formatters": {"oddiy": {"format": "%(levelname)s %(name)s: %(message)s"}},
        "handlers": {"konsol": {"class": "logging.StreamHandler", "formatter": "oddiy", "stream": oqim}},
        "root": {"handlers": ["konsol"], "level": "INFO"},
    }
    if disable is not None:
        sozlama["disable_existing_loggers"] = disable
    return sozlama


class SekinHandler(logging.Handler):
    def __init__(self) -> None:
        super().__init__()
        self.yozilganlar: list[str] = []

    def emit(self, yozuv: logging.LogRecord) -> None:
        time.sleep(0.02)                          # tarmoqqa yuborish kabi
        self.yozilganlar.append(yozuv.getMessage())


def main() -> None:
    print("=== 1. ⚠️ dictConfig va avval yaratilgan loggerlar ===")
    for disable in (None, False):
        eski = logging.getLogger(f"eski_modul_{disable}")      # import paytida yaratilgan kabi
        oqim = io.StringIO()
        logging.config.dictConfig(konfiguratsiya(oqim, disable))
        eski.warning("muhim ogohlantirish")
        logging.getLogger("yangi").warning("yangi logger")
        nom = "sukut (True)" if disable is None else "False"
        print(f"  disable_existing_loggers={nom:12} eski.disabled={eski.disabled!s:5} jurnal={oqim.getvalue().splitlines()}")
    print(f"  getHandlerByName('konsol'): {type(logging.getHandlerByName('konsol')).__name__}")

    print("\n=== 2. RotatingFileHandler ===")
    with tempfile.TemporaryDirectory() as katalog:
        yol = Path(katalog) / "ilova.log"
        h = logging.handlers.RotatingFileHandler(yol, maxBytes=200, backupCount=2, encoding="utf-8")
        h.setFormatter(logging.Formatter("%(message)s"))
        log = logging.getLogger("aylanma")
        log.propagate = False
        log.addHandler(h)
        log.setLevel(logging.INFO)
        for i in range(20):
            log.info("buyurtma #%03d qayta ishlandi — oʻzbekcha matn", i)
        h.close()
        fayllar = sorted(p.name for p in Path(katalog).iterdir())
        print(f"  fayllar: {fayllar}")
        print(f"  har biri 200 baytdan oshmaydi: {all(p.stat().st_size <= 200 for p in Path(katalog).iterdir())}")
        print(f"  eng yangi yozuv asosiy faylda: {'#019' in yol.read_text(encoding='utf-8')}")
        print(f"  eng eski yozuvlar o'chdi (backupCount=2): {not any('#000' in p.read_text(encoding='utf-8') for p in Path(katalog).iterdir())}")

    print("\n=== 3. QueueHandler — sekin handler asosiy ipni to'xtatmasin ===")
    sekin = SekinHandler()
    to_g_ridan = logging.getLogger("togridan")
    to_g_ridan.propagate = False
    to_g_ridan.addHandler(sekin)
    boshlanish = time.perf_counter()
    for i in range(10):
        to_g_ridan.warning("xabar %d", i)
    togridan_vaqt = time.perf_counter() - boshlanish

    navbat: queue.Queue = queue.Queue()
    sekin2 = SekinHandler()
    tinglovchi = logging.handlers.QueueListener(navbat, sekin2)
    tinglovchi.start()
    navbatli = logging.getLogger("navbatli")
    navbatli.propagate = False
    navbatli.addHandler(logging.handlers.QueueHandler(navbat))
    boshlanish = time.perf_counter()
    for i in range(10):
        navbatli.warning("xabar %d", i)
    navbat_vaqt = time.perf_counter() - boshlanish
    tinglovchi.stop()
    print(f"  to'g'ridan: 10 xabar 0.15 s dan ko'p: {togridan_vaqt > 0.15}")
    print(f"  navbat orqali: 10 barobardan ko'p tez: {togridan_vaqt / navbat_vaqt > 10}")
    print(f"  hammasi yozildi va tartib saqlandi: {sekin2.yozilganlar == [f'xabar {i}' for i in range(10)]}")

    print("\n=== 4. Kutubxonada NullHandler ===")
    kutubxona = logging.getLogger("mening_kutubxonam")
    kutubxona.addHandler(logging.NullHandler())
    print(f"  kutubxona faqat NullHandler qo'shadi: {[type(h).__name__ for h in kutubxona.handlers]}")
    print("  ⭐ basicConfig, format, fayl — ilovaning ishi")


if __name__ == "__main__":
    main()

Natijaning muhim qismi:

text
=== 1. ⚠️ dictConfig va avval yaratilgan loggerlar ===
  disable_existing_loggers=sukut (True) eski.disabled=True  jurnal=['WARNING yangi: yangi logger']
  disable_existing_loggers=False        eski.disabled=False jurnal=['WARNING eski_modul_False: muhim ogohlantirish', 'WARNING yangi: yangi logger']
  getHandlerByName('konsol'): StreamHandler

=== 2. RotatingFileHandler ===
  fayllar: ['ilova.log', 'ilova.log.1', 'ilova.log.2']
  har biri 200 baytdan oshmaydi: True
  eng yangi yozuv asosiy faylda: True
  eng eski yozuvlar o'chdi (backupCount=2): True

=== 3. QueueHandler — sekin handler asosiy ipni to'xtatmasin ===
  to'g'ridan: 10 xabar 0.15 s dan ko'p: True
  navbat orqali: 10 barobardan ko'p tez: True
  hammasi yozildi va tartib saqlandi: True

=== 4. Kutubxonada NullHandler ===
  kutubxona faqat NullHandler qo'shadi: ['NullHandler']
  ⭐ basicConfig, format, fayl — ilovaning ishi

Nima ko'rsatdi: 2.7, 2.8-bo'limlar.

Misol 4 — Amaliy: tuzilgan JSON jurnal va so'rov identifikatori

Kirishdagi vaziyatni to'g'ri hal qilamiz. Veb-xizmat uchun jurnal: stderr ga bir qatorli JSON, har yozuvda sorov_id (contextvars orqali — asyncio vazifalari orasida aralashmaydi), maxfiy maydonlar yashiriladi, xatolar traceback bilan, sozlama dictConfig orqali (disable_existing_loggers: False), darajani LOG_DARAJASI muhit o'zgaruvchisi belgilaydi. Bir nechta bir vaqtdagi so'rov simulyatsiya qilinadi, keyin jurnal JSON sifatida tahlil qilinadi. Vaqt sinov uchun qat'iy qilingan.

python
"""JSON formatter; contextvars bilan so'rov identifikatori; asyncio vazifalarida kontekst; maxfiy maydonlar; exception maydoni; dictConfig; jurnalni tahlil qilish."""

import asyncio
import contextvars
import io
import json
import logging
import logging.config
from collections import Counter

SOROV_ID: contextvars.ContextVar[str] = contextvars.ContextVar("sorov_id", default="-")
MAXFIY = {"parol", "token", "karta"}
QATIY_VAQT = 1_789_635_600.0


class KontekstFiltri(logging.Filter):
    def filter(self, yozuv: logging.LogRecord) -> bool:
        yozuv.sorov_id = SOROV_ID.get()
        yozuv.created = QATIY_VAQT                   # faqat misol uchun — natija takrorlansin
        return True


class JsonFormatter(logging.Formatter):
    def format(self, yozuv: logging.LogRecord) -> str:
        malumot = {
            "vaqt": self.formatTime(yozuv, "%Y-%m-%dT%H:%M:%S"),
            "daraja": yozuv.levelname,
            "logger": yozuv.name,
            "xabar": yozuv.getMessage(),
            "sorov_id": getattr(yozuv, "sorov_id", "-"),
        }
        for kalit, qiymat in getattr(yozuv, "maydonlar", {}).items():
            malumot[kalit] = "***" if kalit in MAXFIY else qiymat
        if yozuv.exc_info:
            malumot["xato"] = self.formatException(yozuv.exc_info).splitlines()[-1]
        return json.dumps(malumot, ensure_ascii=False)


def sozla(oqim: io.StringIO, daraja: str) -> None:
    JsonFormatter.converter = __import__("time").gmtime
    logging.config.dictConfig({
        "version": 1,
        "disable_existing_loggers": False,
        "filters": {"kontekst": {"()": KontekstFiltri}},
        "formatters": {"json": {"()": JsonFormatter}},
        "handlers": {"stderr": {"class": "logging.StreamHandler", "stream": oqim,
                                "formatter": "json", "filters": ["kontekst"]}},
        "root": {"handlers": ["stderr"], "level": daraja},
    })


log = logging.getLogger("dokon.buyurtma")


async def buyurtma(sorov_id: str, mahsulot: str, soni: int, karta: str) -> None:
    SOROV_ID.set(sorov_id)
    log.info("buyurtma qabul qilindi", extra={"maydonlar": {"mahsulot": mahsulot, "soni": soni, "karta": karta}})
    await asyncio.sleep(0.01 * soni)
    log.debug("ombor tekshirildi")
    try:
        if soni > 5:
            raise ValueError(f"omborda {mahsulot} yetarli emas")
        log.info("buyurtma yaratildi", extra={"maydonlar": {"summa": soni * 45_000}})
    except ValueError:
        log.exception("buyurtma rad etildi")


async def xizmat() -> None:
    await asyncio.gather(
        buyurtma("r-001", "daftar", 3, "8600123456789012"),
        buyurtma("r-002", "kitob", 9, "8600987654321098"),
        buyurtma("r-003", "ruchka", 1, "9860111122223333"),
    )


def main() -> None:
    muhit = {"LOG_DARAJASI": "INFO"}
    oqim = io.StringIO()
    sozla(oqim, muhit["LOG_DARAJASI"])
    asyncio.run(xizmat())
    yozuvlar = [json.loads(q) for q in oqim.getvalue().splitlines()]

    print("=== 1. Jurnal (har qator — JSON) ===")
    for yozuv in sorted(yozuvlar, key=lambda y: (y["sorov_id"], y["xabar"])):
        print(f"  {json.dumps(yozuv, ensure_ascii=False)}")

    print("\n=== 2. Tahlil ===")
    print(f"  darajalar: {dict(Counter(y['daraja'] for y in yozuvlar))}")
    print(f"  DEBUG yozilmadi (LOG_DARAJASI=INFO): {all(y['daraja'] != 'DEBUG' for y in yozuvlar)}")
    print(f"  har so'rov o'z yozuvlariga ega: {dict(Counter(y['sorov_id'] for y in yozuvlar))}")
    xatolar = [y for y in yozuvlar if y["daraja"] == "ERROR"]
    print(f"  xatolar: {[(y['sorov_id'], y['xato']) for y in xatolar]}")
    print(f"  karta raqami hech qayerda yo'q: {not any('8600' in json.dumps(y) for y in yozuvlar)}")
    print(f"  jami summa: {sum(y.get('summa', 0) for y in yozuvlar):,} so'm".replace(",", " "))

    print("\n=== 3. Daraja muhitdan ===")
    oqim2 = io.StringIO()
    sozla(oqim2, "DEBUG")
    asyncio.run(xizmat())
    debug = [json.loads(q) for q in oqim2.getvalue().splitlines() if json.loads(q)["daraja"] == "DEBUG"]
    print(f"  LOG_DARAJASI=DEBUG: dokon.buyurtma DEBUG yozuvlari {sum(1 for y in debug if y['logger'] == 'dokon.buyurtma')} ta")
    print(f"  ⚠️ boshqa kutubxonalar ham DEBUG yozadi: {sorted({y['logger'] for y in debug} - {'dokon.buyurtma'})}")


if __name__ == "__main__":
    main()

Natijaning muhim qismi:

text
=== 1. Jurnal (har qator — JSON) ===
  {"vaqt": "2026-09-17T09:00:00", "daraja": "INFO", "logger": "dokon.buyurtma", "xabar": "buyurtma qabul qilindi", "sorov_id": "r-001", "mahsulot": "daftar", "soni": 3, "karta": "***"}
  {"vaqt": "2026-09-17T09:00:00", "daraja": "INFO", "logger": "dokon.buyurtma", "xabar": "buyurtma yaratildi", "sorov_id": "r-001", "summa": 135000}
  {"vaqt": "2026-09-17T09:00:00", "daraja": "INFO", "logger": "dokon.buyurtma", "xabar": "buyurtma qabul qilindi", "sorov_id": "r-002", "mahsulot": "kitob", "soni": 9, "karta": "***"}
  {"vaqt": "2026-09-17T09:00:00", "daraja": "ERROR", "logger": "dokon.buyurtma", "xabar": "buyurtma rad etildi", "sorov_id": "r-002", "xato": "ValueError: omborda kitob yetarli emas"}
  {"vaqt": "2026-09-17T09:00:00", "daraja": "INFO", "logger": "dokon.buyurtma", "xabar": "buyurtma qabul qilindi", "sorov_id": "r-003", "mahsulot": "ruchka", "soni": 1, "karta": "***"}
  {"vaqt": "2026-09-17T09:00:00", "daraja": "INFO", "logger": "dokon.buyurtma", "xabar": "buyurtma yaratildi", "sorov_id": "r-003", "summa": 45000}

=== 2. Tahlil ===
  darajalar: {'INFO': 5, 'ERROR': 1}
  DEBUG yozilmadi (LOG_DARAJASI=INFO): True
  har so'rov o'z yozuvlariga ega: {'r-001': 2, 'r-002': 2, 'r-003': 2}
  xatolar: [('r-002', 'ValueError: omborda kitob yetarli emas')]
  karta raqami hech qayerda yo'q: True
  jami summa: 180 000 so'm

=== 3. Daraja muhitdan ===
  LOG_DARAJASI=DEBUG: dokon.buyurtma DEBUG yozuvlari 3 ta
  ⚠️ boshqa kutubxonalar ham DEBUG yozadi: ['asyncio']

Nima ko'rsatdi: 2.3, 2.5, 2.6, 2.7, 2.8-bo'limlar.


5. To'g'ri va noto'g'ri tushunishlar

Noto'g'ri fikr To'g'risi
"logger.info sozlamasiz ham chiqadi" Faqat WARNING+ (lastResort)
"basicConfig ni qayta chaqirsam sozlama yangilanadi" Jim e'tiborsiz — force=True
"f-string va %s bir xil" f-string o'chiq darajada ham hisoblanadi
"Handler qo'shgan joyimdagina yoziladi" Propagatsiya — ota handlerlarga ham
"dictConfig faqat yangi sozlamani qo'shadi" Sukut bo'yicha mavjud loggerlarni o'chiradi
"Formatdagi maydon yetishmasa istisno bo'ladi" "--- Logging error ---" va xabar yo'qoladi
"Kutubxona basicConfig qilishi mumkin" Faqat NullHandler; sozlash — ilova ishi
"Jurnal faylga yozilishi kerak" Konteynerda — stderr + JSON

6. Keng tarqalgan xatolar va yechimlari

1. print bilan diagnostika

python
print("xato:", e)                             # ❌ daraja, manba, vaqt yo'q
logger.exception("buyurtma bajarilmadi")      # ✅

2. f-string

python
logger.debug(f"so'rov: {sorov!r}")            # ❌
logger.debug("so'rov: %r", sorov)             # ✅

3. Modulda sozlash

python
# ilova/db.py
logging.basicConfig(level=logging.DEBUG)      # ❌ import qilgan dasturni buzadi
logger = logging.getLogger(__name__)          # ✅ faqat shu

4. Ikki marta yozish

python
logging.getLogger("ilova").addHandler(h)
logging.basicConfig()                         # ⚠️ root da ham handler

5. dictConfig sukuti

python
logging.config.dictConfig({"version": 1, ...})            # ⚠️ eski loggerlar o'chadi
logging.config.dictConfig({"version": 1, "disable_existing_loggers": False, ...})   # ✅

6. Traceback yo'qotish

python
except Exception as e:
    logger.error(f"xato: {e}")                # ❌ qayerda — noma'lum
    logger.exception("xato")                  # ✅

7. Maxfiy ma'lumot

python
logger.info("login: %s parol=%s", foydalanuvchi, parol)   # ❌

8. root loggerga to'g'ridan-to'g'ri yozish

python
logging.info("...")                           # ⚠️ manba noma'lum, root sozlamasiga bog'liq
logger = logging.getLogger(__name__); logger.info("...")  # ✅

7. Integratsiya — bu bilim qayerda kerak bo'ladi

  • 14.6-dars (o'tilgan): asyncio — contextvars vazifalar orasida
  • 15.11-dars (o'tilgan): stdout/stderr, muhit o'zgaruvchilari
  • 15.15-dars (o'tilgan): -v bayroqlarini jurnal darajasiga bog'lash
  • 17-qism: testlarda jurnalni tekshirish (caplog)
  • 28.11–28.12-darslar: markazlashgan loglar va xatolarni kuzatish (Sentry)
  • 20.7-dars: bog'liqliklar — so'rov identifikatori va middleware
  • 28.10-dars: monitoring va metrikalar — kuzatuvchanlik

8. Eng yaxshi amaliyotlar

  1. Har modulda logger = logging.getLogger(__name__).

  2. Sozlash — faqat kirish nuqtasida, bir marta.

  3. %s argumentlar; qimmat hisob — isEnabledFor.

  4. Xatolar — logger.exception.

  5. dictConfig da disable_existing_loggers: False.

  6. Konteynerda — stderr va JSON.

  7. So'rov identifikatori — contextvars va filtr.

  8. Maxfiy ma'lumotni filtr bilan yashiring va testlang.


9. Amaliy topshiriq

Vazifa 1: Natijani bashorat qiling

python
import logging
1.  print(logging.getLevelName(logging.WARNING))
2.  print(logging.DEBUG < logging.INFO < logging.WARNING)
3.  print(logging.getLogger("a.b").parent.name)
4.  print(logging.getLogger("x") is logging.getLogger("x"))
5.  print(logging.getLogger().name)
6.  l = logging.getLogger("yangi_q"); print(l.level)
7.  print(logging.getLevelNamesMapping()["ERROR"])
8.  print(logging.getLogger("p.q").propagate)
9.  print(isinstance(logging.NullHandler(), logging.Handler))
10. l = logging.getLogger("z_q"); l.setLevel(logging.ERROR)
    print(l.isEnabledFor(logging.WARNING))
11. print(logging.getLevelName(25))
12. print(logging.LoggerAdapter(logging.getLogger("m"), {"k": 1}).extra)
Javoblar
  1. WARNING
  2. True
  3. root — a loggeri hali yaratilmagan; ota — eng yaqin mavjud logger (a keyinroq yaratilsa, bog'lanish yangilanadi)
  4. True
  5. root
  6. 0 — NOTSET, ota darajasi ishlatiladi
  7. 40
  8. True
  9. True
  10. False
  11. Level 25
  12. {'k': 1}

Vazifa 2: Xatolarni tuzating

python
1.  # kutubxona/__init__.py
    import logging
    logging.basicConfig(level=logging.DEBUG)
    logger = logging.getLogger("kutubxona")

2.  def ishla(buyurtma):
        logger.debug(f"buyurtma: {buyurtma.toliq_malumot()}")

3.  try:
        tolov()
    except Exception as e:
        logger.error("xato: " + str(e))

4.  logging.config.dictConfig({"version": 1, "root": {"level": "INFO", "handlers": ["h"]},
                               "handlers": {"h": {"class": "logging.StreamHandler"}}})

5.  logger.info("foydalanuvchi kirdi: %s, token=%s", ism, token)
Javoblar
python
1.  # kutubxona/__init__.py
    import logging
    logger = logging.getLogger(__name__)
    logger.addHandler(logging.NullHandler())

2.  def ishla(buyurtma):
        if logger.isEnabledFor(logging.DEBUG):
            logger.debug("buyurtma: %s", buyurtma.toliq_malumot())

3.  try:
        tolov()
    except Exception:
        logger.exception("to'lov bajarilmadi")

4.  logging.config.dictConfig({"version": 1, "disable_existing_loggers": False,
                               "root": {"level": "INFO", "handlers": ["h"]},
                               "handlers": {"h": {"class": "logging.StreamHandler"}}})

5.  logger.info("foydalanuvchi kirdi: %s", ism)     # token umuman yozilmaydi
    # yoki MaxfiyFiltr bilan yashirish

Vazifa 3: CLI va jurnal

15.15 dagi vazifalar menejeriga jurnal qo'shing:

  1. -v — INFO, -vv — DEBUG, sukut — WARNING
  2. --log-fayl — qo'shimcha ravishda RotatingFileHandler
  3. Foydalanuvchi xatolari — WARNING, kutilmagan xatolar — logger.exception va kod 1
  4. stdout natija uchun toza qolsin
  5. caplog siz (17-qismgacha) — StringIO handler bilan testlang

Vazifa 4: Jurnal tahlilchisi

Misol 4 dagi JSON jurnal (kamida 1000 qator, generator bilan) uchun:

  1. Darajalar va loggerlar bo'yicha statistika
  2. Har sorov_id uchun yozuvlar zanjiri va davomiyligi
  3. Eng ko'p uchragan xato turlari
  4. Buzilgan (JSON bo'lmagan) qatorlarni hisoblab, o'tkazib yuboring
  5. argparse bilan filtrlar: --daraja, --logger, --sorov

Vazifa 5: Kontekst bilan jurnal

  1. contextvars asosida with jurnal_konteksti(foydalanuvchi_id=..., sorov_id=...) kontekst menejeri
  2. Ichma-ich kontekstlar birlashsin, chiqishda tiklansin
  3. asyncio.gather dagi vazifalar bir-biriga aralashmasligini testlang
  4. Iplarda (ThreadPoolExecutor) kontekstni contextvars.copy_context() bilan uzating

Vazifa 6: Jurnal darajasini ishlab turgan dasturda o'zgartirish

  1. Dastur har 1 soniyada ish bajaradi va jurnalga yozadi
  2. SIGUSR1 (Linux) yoki maxsus fayl paydo bo'lganda (log_daraja.txt) daraja o'zgarsin
  3. Faqat ma'lum logger darajasini o'zgartirish (ilova.db → DEBUG)
  4. Qayta ishga tushirmasdan ishlashini ko'rsating

Vazifa 7: O'ylash

Python'ning logging moduli 2002-yilda Java'ning log4j dizaynidan ilhomlanib yaratilgan: ierarxik loggerlar, handlerlar, formatterlar. Ko'pchilik uni "murakkab" deb biladi va loguru kabi soddaroq kutubxonalarni tanlaydi. Zamonaviy kuzatuvchanlik (OpenTelemetry, tuzilgan jurnallar) davrida bu arxitektura qanchalik o'rinli va qanday kelishuvlar bor?

Javob

Qisqa javob: logging murakkabligi — ajratish (decoupling) narxi: kutubxonalar faqat "nima bo'ldi"ni aytadi, ilova esa "qayerga va qanday yozish"ni hal qiladi. Bu minglab kutubxonali ekotizimda juda muhim va shuning uchun arxitektura hali ham o'rinli. Kamchiliklari — noqulay sozlash API si va tuzilgan jurnallar uchun o'rnatilgan qo'llab-quvvatlashning yo'qligi; ular JSON formatterlar, structlog va OpenTelemetry integratsiyasi bilan to'ldiriladi.

1. Arxitekturaning kuchi

Xususiyat Nima beradi
Ierarxik nomlar urllib3 ni WARNING ga, o'z ilovangizni DEBUG ga — alohida
Kutubxona va ilova ajratilgan Kutubxona chiqish joyini bilmaydi va bilishi shart emas
Handlerlar Bir xabar — konsol, fayl, tashqi tizim
Standart Har bir Python kutubxonasi bir xil mexanizmdan foydalanadi
Kechiktirilgan formatlash O'chiq darajada deyarli bepul

2. Kamchiliklari

Kamchilik Oqibat
Sozlash API si murakkab basicConfig tuzoqlari, dictConfig sukutlari
Global holat Testlarda va kutubxonalarda chalkashlik
Tuzilgan maydonlar extra noqulay, formatda maydon yetishmasa xato
Kontekst So'rov identifikatori uchun qo'lda filtr va contextvars
% formatlash Zamonaviy {} sintaksisi bilan nomuvofiqlik

3. Muqobillar va to'ldiruvchilar

Vosita Yondashuv
loguru Bitta tayyor logger, oson sozlash, chiroyli chiqish — kichik ilovalar uchun qulay
structlog Tuzilgan jurnal birinchi o'rinda, kontekst bog'lash; logging ustida ishlay oladi
python-json-logger logging uchun JSON formatter
OpenTelemetry Jurnal, metrika va tracing ni birlashtiradi; logging handler orqali ulanadi

Muhim nuqta: ko'p muqobillar logging bilan birga ishlaydi — chunki uchinchi tomon kutubxonalari baribir standart logging ga yozadi.

4. Zamonaviy kuzatuvchanlik talablari

  1. Tuzilgan jurnal: matn emas, maydonlar (JSON) — qidirish va agregatsiya
  2. Korrelyatsiya: trace_id/span_id/sorov_id — bir so'rovning butun yo'lini ko'rish
  3. Namunalash va narx: har DEBUG ni saqlash qimmat
  4. Uch ustun: jurnallar, metrikalar, tracing — biri ikkinchisini almashtirmaydi

5. Amaliy tavsiya

Vaziyat Tanlov
Kutubxona Faqat logging.getLogger(__name__) + NullHandler
Kichik skript basicConfig yoki loguru
Xizmat, konteyner logging + JSON formatter + contextvars (Misol 4) yoki structlog
Taqsimlangan tizim Yuqoridagi + OpenTelemetry

6. Xulosa

  1. logging murakkabligi — kutubxonalar va ilovalarni ajratish narxi
  2. Bu ajratish ekotizim uchun zarur, shuning uchun arxitektura eskirmagan
  3. Tuzilgan jurnal va kontekst — qo'shimcha qatlamlar bilan hal qilinadi
  4. Muqobil tanlasangiz ham, u standart logging bilan hamkorlik qilishi kerak

Nimani mustahkamlaydi: 2.1–2.8-bo'limlar.


Xulosa

Bu darsda logging arxitekturasini va uni ishlab chiqarishda to'g'ri ishlatishni o'rgandik.

Eng muhim uch fikr:

  1. Arxitektura: logger — manba, handler — joy, formatter — ko'rinish. Har modulda getLogger(__name__), sozlash esa faqat kirish nuqtasida. Loggerlar ierarxik: darajasi yo'q logger otasinikini oladi, xabarlar ota handlerlariga ham boradi — handler ham bola, ham root loggerda bo'lsa, har xabar ikki marta yoziladi. Sozlanmagan dastur faqat WARNING+ ni formatsiz chiqaradi, basicConfig esa ikkinchi marta jim e'tiborsiz qoladi (force=True).

  2. Jim tuzoqlar. f-string o'chiq darajada ham hisoblanadi — %s argumentlar esa yo'q (misolda 1000 ta chaqiruv farqi). Formatdagi maydon yozuvda bo'lmasa, xabar "--- Logging error ---" bilan yo'qoladi. dictConfig sukut bo'yicha avval yaratilgan loggerlarni o'chiradi — disable_existing_loggers: False ni doim yozing. Kutubxona esa faqat NullHandler qo'shadi.

  3. Ishlab chiqarish jurnali — tuzilgan va xavfsiz. logger.exception traceback bilan, extra/LoggerAdapter/filtr — kontekst uchun, contextvars — asyncio vazifalari orasida aralashmaydigan so'rov identifikatori uchun. Konteynerda stderr ga bir qatorli JSON, maxfiy maydonlar filtr bilan yashiriladi, sekin handlerlar QueueHandler orqali alohida ipga, fayllar esa RotatingFileHandler bilan aylanadi.

15-qism yakuni

Dars Mavzu Asosiy g'oya
15.1 Muntazam ifodalar: asoslar fullmatch validatsiyada; \d, \w — Unicode; re da \p{L} yo'q
15.2 Guruh va almashtirish Nomli guruhlar; findall guruhlarga bog'liq; count/flags nomi bilan
15.3 Amaliy naqshlar Lookaround; chegara tuzoqlari; ReDoS va atomik guruhlar; tokenizator
15.4 datetime timedelta — fizik davomiylik; oy qo'shish; ISO 8601; utcnow eskirgan
15.5 Vaqt mintaqalari Siljish ≠ mintaqa; yozgi vaqtda bo'shliq va fold; UTC da saqlash
15.6 Counter, defaultdict Sanash va guruhlash; update qo'shadi; o'qish kalit qo'shadi
15.7 deque, OrderedDict Chetlar O(1); maxlen; move_to_end va LRU
15.8 itertools: asosiy Dangasa va bir martalik; groupby faqat ketma-ket; batched
15.9 Kombinatorika Avval sanang; portlovchi o'sish; kirish oldindan o'qiladi
15.10 functools Kesh va konkurentlik; singledispatch noaniqligi; vositalar birgalikda
15.11 os va sys Chiqish kodlari va oqimlar; muhit — faqat satrlar; atomar yozish
15.12 pathlib Yo'l — obyekt; resolve + is_relative_to; 3.14 copy/move
15.13 shutil copy2; himoyalangan rmtree; arxiv xavfsizligi
15.14 subprocess Ro'yxat, check, timeout; shell=True in'ektsiyasi; deadlock
15.15 argparse type=bool tuzog'i; ichki buyruqlar; main(argv) -> int
15.16 logging Ierarxiya va propagatsiya; dangasa formatlash; JSON va kontekst

15-qism bilan kurs 4-bosqich — "Kutubxona va professional vositalar" ning birinchi qismi tugadi. Endi siz matn, vaqt, kolleksiyalar, iteratorlar, fayl tizimi, jarayonlar, CLI va jurnallar bilan professional darajada ishlay olasiz — va har birining jim tuzoqlarini bilasiz.

Keyingi qism — ma'lumot formatlari: matn va binar fayllar, json, csv, pickle va uning xavfi, XML, YAML va TOML, sqlite3 va katta fayllarni oqim bilan o'qish.

Ulashish:Telegram'da

Izohlar (0)

Izoh yozish uchun kiring.

  • Hozircha izoh yo'q. Birinchi bo'ling!
15.16-dars: logging — IlmHamroh