IlmHamroh
Python kursi/Malumot formatlari4/8-dars22 daqiqa
Mundarija (22)

16.4-dars: pickle va xavfsizlik

16-QISM — MA'LUMOT FORMATLARI · 4-dars


1. Kirish va motivatsiya

JSON va CSV — matn formatlari: ular faqat oddiy turlarni (son, satr, ro'yxat, lug'at) tashiydi va boshqa tillar ham o'qiy oladi. Ba'zan esa Python obyektining o'zini — ML modeli, murakkab sinf namunasi, kesh holati, o'zaro bog'langan tuzilmasi — to'g'ridan-to'g'ri saqlash kerak bo'ladi. pickle shu ishni qiladi: istalgan Python obyektini baytlarga seriyalaydi va qaytadan tiklaydi.

Bu qulaylikning narxi bor. pickle shunchaki ma'lumotni emas, obyektni qanday qayta qurish ko'rsatmasini saqlaydi — va bu ko'rsatma yuklashda kod bajaradi.

Real vaziyat. Bir jamoa ML modelini .pkl fayl sifatida saqlagan va uni foydalanuvchilar yuklaydigan API ga qo'ygan: "modelingizni yuklang, biz bashorat qilamiz". Bir tadqiqotchi buni xavfsizlik auditida ko'rdi va oddiy .pkl yubordi — uning ichida model emas, __reduce__ orqali yozilgan bitta buyruq bor edi. pickle.load chaqirilishi bilanoq bu buyruq serverda ishga tushdi. Bu o'ylab topilgan hikoya emas — bu pickle ning hujjatlashtirilgan, ma'lum va bugungacha faol xavfsizlik xususiyati:

Ogohlantirish: pickle moduli xavfsiz emas. Faqat ishonchli ma'lumotni oching. Ishonchsiz manbadan kelgan pickle ni yuklash ixtiyoriy kod bajarilishiga olib keladi.

Bu darsda pickle ni to'liq o'rganamiz — nima uchun kuchli, nima uchun xavfli, qachon ishlatish mumkin va qanday himoyalanish kerak.

Bu darsda:

  • dumps/loads, dump/load va protokollar
  • Nima pickle qilinadi, nima yo'q (lambda, ochiq fayl, mahalliy sinf)
  • Nima uchun pickle.load = kod bajarish (__reduce__)
  • Xavfsizlik: hech qachon ishonchsiz pickle
  • Himoya: find_class bilan cheklangan Unpickler, HMAC imzo
  • __getstate__/__setstate__, persistent_id
  • Versiyalash mo'rtligi — sinf o'zgarsa
  • pickle vs JSON: qachon qaysi biri
  • Amaliy: xavfsiz kesh qatlami

2. Nazariya — chuqur tushuntirish

2.1. Asosiy API

Funksiya Nima qiladi
pickle.dumps(obyekt) Obyekt → bytes
pickle.loads(baytlar) bytes → obyekt
pickle.dump(obyekt, fayl) Binar faylga ("wb")
pickle.load(fayl) Binar fayldan ("rb")

Natija — binar: faylni "wb"/"rb" rejimida oching, encoding bermang.

2.2. Protokollar

pickle baytlar formati vaqt o'tishi bilan rivojlangan.

Protokol Kiritilgan Xususiyat
0 eng eski ASCII, o'qish mumkin, katta
1–2 binar, eski sinflar
3 3.0 bytes
4 3.4 katta obyektlar, __qualname__
5 3.8 bufer, nol-nusxa katta ma'lumot
  • pickle.HIGHEST_PROTOCOL — 5 3.14-bob, pickle.DEFAULT_PROTOCOL — 5.
  • Yangi protokol kichikroq va tezroq; eski protokol eski Python bilan mos.
  • Yuqori protokol bilan yozilgan faylni past versiyali Python o'qiy olmaydi.

2.3. Nima pickle qilinadi

Ishlaydi Ishlamaydi
int, float, str, bytes, bool, None lambda va ichki funksiyalar
list, dict, tuple, set, frozenset mahalliy (<locals>) sinflar
Modul darajasidagi sinf namunalari ochiq fayl, soket, ulanish
datetime, Decimal, ko'pchilik stdlib generator, threading.Lock
O'zaro va rekursiv havolalar ba'zi C-kengaytma obyektlari

Sinf namunasi uchun pickle faqat __dict__ ni saqlaydi, kodni emas: yuklovchi tomonda aynan o'sha nomli sinf import qilinadigan bo'lishi kerak. PicklingError — yozib bo'lmasa; TypeError — masalan ochiq fayl.

JSON qila olmaydigan narsa: o'zaro havolalar (bir obyekt ikki joyda — bittaligicha saqlanadi) va rekursiv tuzilmalar pickle da ishlaydi.

2.4. Nima uchun pickle.load kod bajaradi

Obyektni qayta qurish uchun pickle uning __reduce__ metodini chaqiradi. U chaqiriladigan obyekt va argumentlar juftini qaytaradi; yuklashda pickle o'sha chaqiruvni bajaradi. Oddiy obyektlar uchun bu SomeClass(...). Lekin hujumkor __reduce__ ni istalgan chaqiruvga — masalan os.system("...") ga — sozlashi mumkin. Natijada:

pickle.loads(zararli_baytlar)   →   ishonchsiz kod DARHOL bajariladi

Bu loyihalangan xususiyat, xatolik emas: pickle obyektlarni qayta qurish uchun chaqiruvlarni saqlashi kerak. Shuning uchun:

Pickle ni faqat o'zingiz yaratgan yoki imzolagan manbadan yuklang. Foydalanuvchi yuklamasi, tarmoq, umumiy papka — hech qachon.

2.5. Himoya usullari

1. Cheklangan Unpickler (find_class). pickle yuklashda har bir sinf/funksiyani find_class(modul, nom) orqali qidiradi. Uni bekor qilib faqat oq ro'yxatdagilarni ruxsat etish mumkin:

python
class XavfsizUnpickler(pickle.Unpickler):
    RUXSAT = {("builtins", "list"), ("builtins", "dict")}
    def find_class(self, modul, nom):
        if (modul, nom) in self.RUXSAT:
            return super().find_class(modul, nom)
        raise pickle.UnpicklingError(f"taqiqlangan: {modul}.{nom}")

Bu yechim ham mo'rt: builtins.eval, builtins.exec, os.system kabilarni ro'yxatga qo'shmaslik kerak, va oq ro'yxat baribir hujum yuzasini qoldiradi. Eng yaxshi himoya — ishonchsiz pickle ni umuman yuklamaslik.

2. HMAC imzo. O'zingiz yozgan pickle ni maxfiy kalit bilan imzolang; yuklashdan oldin imzoni tekshiring. Buzilgan yoki soxta ma'lumot yuklanmaydi. Bu faqat sizniki ekanini tasdiqlaydi — begona pickle ni xavfsiz qilmaydi.

2.6. __getstate__ va __setstate__

Obyekt saqlab bo'lmaydigan qismga ega bo'lsa (soket, ochiq fayl, kesh) — nimani saqlashni boshqaring:

Metod Vazifasi
__getstate__() Saqlanadigan holatni qaytaradi (masalan soketsiz __dict__)
__setstate__(holat) Yuklashda holatdan qayta quradi (masalan ulanishni yangidan ochadi)

persistent_id/persistent_load — tashqi obyektlarni (masalan bazadagi yozuv) qiymati bilan emas, havola (ID) bilan saqlash.

2.7. Versiyalash mo'rtligi

pickle sinf holatini saqlaydi, ta'rifini emas. Sinf keyin o'zgarsa:

O'zgarish Yuklashda
Sinf ko'chirildi/nomlandi AttributeError/ModuleNotFoundError
Maydon o'chirildi Eski __dict__ da ortiqcha kalit
Maydon qo'shildi Eski pickle da yo'q — __setstate__ da sukut bering
Kutubxona versiyasi o'zgardi Ko'pincha umuman yuklanmaydi

Shuning uchun pickle uzoq muddatli yoki versiyalararo saqlashga yaramaydi. ML modelini oylar keyin boshqa muhitda yuklash — ko'p muammoning manbai.

2.8. pickle vs JSON

Mezon pickle JSON
Turlar Deyarli har qanday Python obyekt Oddiy turlar
Xavfsizlik Kod bajaradi Xavfsiz
Tillararo Faqat Python Universal
O'qiladi Binar Matn
O'zaro/rekursiv havola
Hajm/tezlik Ixcham, tez Kattaroq
Uzoq saqlash Mo'rt Barqaror

Qoida: ma'lumot almashish, saqlash, sozlama — JSON. Faqat Python ichidagi vaqtinchalik, ishonchli holat (kesh, multiprocessing, joblib model) — pickle.

multiprocessing (14-qism) ichida argumentlarni pickle bilan uzatadi — shuning uchun lambda va mahalliy funksiyalar jarayonlar orasida ishlamaydi.


3. Tez ma'lumotnoma

python
import pickle

with open("holat.pkl", "wb") as f:
    pickle.dump(obyekt, f, protocol=pickle.HIGHEST_PROTOCOL)
with open("holat.pkl", "rb") as f:            # ⚠️ faqat ishonchli fayl
    obyekt = pickle.load(f)

baytlar = pickle.dumps(obyekt)
obyekt = pickle.loads(baytlar)

Qoidalar

ishonchsiz pickle — HECH QACHON load
binar rejim: "wb" / "rb"
uzoq saqlash / almashinuv — JSON, pickle emas
o'zingiznikini imzolang (HMAC)
saqlab bo'lmaydigan qism — __getstate__/__setstate__
faqat Python-ichi, vaqtinchalik holat uchun

4. Batafsil misollar

Misol 1 — Asoslar: protokollar, turlar, havolalar

python
"""dumps/loads; protokol va hajm; nima pickle bo'ladi, nima yo'q; o'zaro va rekursiv havolalar; JSON bilan solishtirish; sinf namunasi."""

import json
import pickle
from datetime import datetime


class Nuqta:
    def __init__(self, x: int, y: int) -> None:
        self.x, self.y = x, y

    def __repr__(self) -> str:
        return f"Nuqta({self.x}, {self.y})"


def main() -> None:
    print("=== 1. Aylanib qaytish ===")
    obyekt = {"son": 42, "matn": "salom", "kortej": (1, 2), "toplam": {3, 1, 2},
              "sana": datetime(2026, 9, 17, 9, 30), "ichki": {"a": [1, {"b": 2}]}, "yoq": None}
    qaytgan = pickle.loads(pickle.dumps(obyekt))
    print(f"  teng: {qaytgan == obyekt}")
    print(f"  set turi saqlandi: {type(qaytgan['toplam']).__name__}, kortej: {type(qaytgan['kortej']).__name__}")

    print("\n=== 2. Protokollar va hajm ===")
    for protokol in (0, 2, 5):
        baytlar = pickle.dumps(obyekt, protocol=protokol)
        print(f"  protokol {protokol}: {len(baytlar):3} bayt, boshi: {baytlar[:4]!r}")
    print(f"  DEFAULT={pickle.DEFAULT_PROTOCOL}, HIGHEST={pickle.HIGHEST_PROTOCOL}")

    print("\n=== 3. Nima pickle bo'lmaydi ===")
    def ichki_funksiya() -> None:
        pass
    for nom, qiymat in [("lambda", lambda x: x), ("ichki funksiya", ichki_funksiya)]:
        try:
            pickle.dumps(qiymat)
        except pickle.PicklingError as xato:
            print(f"  {nom}: PicklingError: {str(xato).split(':')[0]}")
    try:
        pickle.dumps(open(__file__, encoding="utf-8"))
    except TypeError as xato:
        print(f"  ochiq fayl: TypeError: {xato}")

    print("\n=== 4. ⭐ O'zaro va rekursiv havolalar ===")
    umumiy = [1, 2, 3]
    juft = [umumiy, umumiy]
    natija = pickle.loads(pickle.dumps(juft))
    print(f"  bir xil obyekt bittaligicha saqlandi: {natija[0] is natija[1]}")
    halqa: list = []
    halqa.append(halqa)
    tiklangan = pickle.loads(pickle.dumps(halqa))
    print(f"  rekursiv tuzilma: {tiklangan[0] is tiklangan}")
    try:
        json.dumps(halqa)
    except ValueError as xato:
        print(f"  JSON rekursivni qila olmaydi: {type(xato).__name__}: {xato}")

    print("\n=== 5. pickle vs JSON hajmi ===")
    malumot = {"foydalanuvchilar": [{"id": i, "faol": i % 2 == 0} for i in range(50)]}
    print(f"  json:   {len(json.dumps(malumot).encode()):4} bayt")
    print(f"  pickle: {len(pickle.dumps(malumot)):4} bayt")


if __name__ == "__main__":
    main()

Natijaning muhim qismi:

text
=== 1. Aylanib qaytish ===
  teng: True
  set turi saqlandi: set, kortej: tuple

=== 2. Protokollar va hajm ===
  protokol 0: 310 bayt, boshi: b'(dp0'
  protokol 2: 261 bayt, boshi: b'\x80\x02}q'
  protokol 5: 158 bayt, boshi: b'\x80\x05\x95\x93'
  DEFAULT=5, HIGHEST=5

=== 3. Nima pickle bo'lmaydi ===
  lambda: PicklingError: Can't pickle local object <function main.<locals>.<lambda> at 0x000001C40CE0BC10>
  ichki funksiya: PicklingError: Can't pickle local object <function main.<locals>.ichki_funksiya at 0x000001C40CE0BB60>
  ochiq fayl: TypeError: cannot pickle 'TextIOWrapper' instances

=== 4. ⭐ O'zaro va rekursiv havolalar ===
  bir xil obyekt bittaligicha saqlandi: True
  rekursiv tuzilma: True
  JSON rekursivni qila olmaydi: ValueError: Circular reference detected

=== 5. pickle vs JSON hajmi ===
  json:   1337 bayt
  pickle:  596 bayt

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

Misol 2 — Xavfsizlik: kod bajarish va himoya

python
"""__reduce__ orqali pickle.load kod bajaradi (zararsiz namoyish); find_class bilan cheklangan Unpickler; xavfsiz data baribir yuklanadi; HMAC imzo bilan yaxlitlik."""

import hashlib
import hmac
import io
import pickle

IMZO_KALITI = b"faqat-server-biladigan-maxfiy-kalit"


class Zararsiz_Namoyish:
    """__reduce__ yuklashda print chaqiradi — haqiqiy hujumda bu os.system bo'lardi."""

    def __reduce__(self):
        return (print, ("      [!] pickle.load ushbu kodni BAJARDI — haqiqiy hujumda bu buyruq bo'lardi",))


class XavfsizUnpickler(pickle.Unpickler):
    RUXSAT = {("builtins", "list"), ("builtins", "dict"), ("builtins", "set"),
              ("builtins", "tuple"), ("builtins", "str"), ("builtins", "int")}

    def find_class(self, modul: str, nom: str):
        if (modul, nom) in self.RUXSAT:
            return super().find_class(modul, nom)
        raise pickle.UnpicklingError(f"taqiqlangan turdagi obyekt: {modul}.{nom}")


def imzolab_saqla(obyekt: object) -> bytes:
    data = pickle.dumps(obyekt)
    imzo = hmac.new(IMZO_KALITI, data, hashlib.sha256).digest()
    return imzo + data


def tekshirib_yukla(blob: bytes) -> object:
    imzo, data = blob[:32], blob[32:]
    kutilgan = hmac.new(IMZO_KALITI, data, hashlib.sha256).digest()
    if not hmac.compare_digest(imzo, kutilgan):
        raise ValueError("imzo mos emas — ma'lumot buzilgan yoki soxta")
    return pickle.loads(data)


def main() -> None:
    print("=== 1. ⚠️ pickle.load kod bajaradi ===")
    zararli = pickle.dumps(Zararsiz_Namoyish())
    print("  quyidagi qator zararli pickle ni yuklaydi:")
    pickle.loads(zararli)
    print("  ↑ ma'lumot deb o'ylagan narsangiz kod ekan")

    print("\n=== 2. Cheklangan Unpickler ===")
    try:
        XavfsizUnpickler(io.BytesIO(zararli)).load()
    except pickle.UnpicklingError as xato:
        print(f"  zararli bloklandi: {xato}")
    xavfsiz_data = pickle.dumps({"foydalanuvchi": "aziz", "rollar": ["oquvchi"]})
    print(f"  xavfsiz data yuklanadi: {XavfsizUnpickler(io.BytesIO(xavfsiz_data)).load()}")
    print("  ⚠️ oq ro'yxat ham to'liq yechim emas — eng yaxshisi ishonchsizni umuman yuklamaslik")

    print("\n=== 3. HMAC imzo ===")
    blob = imzolab_saqla({"model": "v3", "koeffitsiyentlar": [0.1, 0.5, 0.9]})
    print(f"  imzolangan: {len(blob)} bayt (32 imzo + {len(blob) - 32} data)")
    print(f"  to'g'ri imzo yuklandi: {tekshirib_yukla(blob)}")
    buzilgan = blob[:-1] + bytes([blob[-1] ^ 0x01])
    try:
        tekshirib_yukla(buzilgan)
    except ValueError as xato:
        print(f"  bitta bayt o'zgardi: ValueError: {xato}")
    soxta = imzolab_saqla({"model": "soxta"})
    soxta = bytes(32) + soxta[32:]        # imzoni nolga almashtirish urinishi
    try:
        tekshirib_yukla(soxta)
    except ValueError as xato:
        print(f"  soxta imzo: ValueError: {xato}")


if __name__ == "__main__":
    main()

Natijaning muhim qismi:

text
=== 1. ⚠️ pickle.load kod bajaradi ===
  quyidagi qator zararli pickle ni yuklaydi:
      [!] pickle.load ushbu kodni BAJARDI — haqiqiy hujumda bu buyruq bo'lardi
  ↑ ma'lumot deb o'ylagan narsangiz kod ekan

=== 2. Cheklangan Unpickler ===
  zararli bloklandi: taqiqlangan turdagi obyekt: builtins.print
  xavfsiz data yuklanadi: {'foydalanuvchi': 'aziz', 'rollar': ['oquvchi']}
  ⚠️ oq ro'yxat ham to'liq yechim emas — eng yaxshisi ishonchsizni umuman yuklamaslik

=== 3. HMAC imzo ===
  imzolangan: 111 bayt (32 imzo + 79 data)
  to'g'ri imzo yuklandi: {'model': 'v3', 'koeffitsiyentlar': [0.1, 0.5, 0.9]}
  bitta bayt o'zgardi: ValueError: imzo mos emas — ma'lumot buzilgan yoki soxta
  soxta imzo: ValueError: imzo mos emas — ma'lumot buzilgan yoki soxta

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

Misol 3 — Holatni boshqarish va versiyalash

python
"""__getstate__/__setstate__: saqlab bo'lmaydigan qismni chetlab o'tish va sukut qiymatlar; persistent_id bilan tashqi havola; sinf o'zgarganda mo'rtlik."""

import io
import pickle


class Ulanish:
    """Soketni saqlab bo'lmaydi — holatdan chiqaramiz, yuklashda qayta ochamiz."""

    def __init__(self, host: str) -> None:
        self.host = host
        self.urinishlar = 0
        self._soket = f"<{host} ga ochiq soket>"      # saqlab bo'lmaydi

    def __getstate__(self) -> dict:
        holat = self.__dict__.copy()
        del holat["_soket"]
        return holat

    def __setstate__(self, holat: dict) -> None:
        self.__dict__.update(holat)
        self.__dict__.setdefault("qayta_ulanish", True)   # yangi maydon uchun sukut
        self._soket = None                                # yuklashda yopiq


class DBYozuv:
    def __init__(self, jadval: str, id: int) -> None:
        self.jadval, self.id = jadval, id


class RefPickler(pickle.Pickler):
    def persistent_id(self, obyekt: object):
        if isinstance(obyekt, DBYozuv):
            return ("db", obyekt.jadval, obyekt.id)       # qiymat emas, havola saqlanadi
        return None


class RefUnpickler(pickle.Unpickler):
    def persistent_load(self, pid: tuple):
        tur, jadval, id = pid
        if tur == "db":
            return f"<{jadval}#{id} bazadan yuklab olindi>"
        raise pickle.UnpicklingError(f"noma'lum havola: {pid}")


class Foydalanuvchi:
    """Yangi versiyada 'til' maydoni qo'shildi; eski pickle da u yo'q."""

    def __init__(self, ism: str) -> None:
        self.ism = ism
        self.til = "uz"

    def __setstate__(self, holat: dict) -> None:
        self.__dict__.update(holat)
        self.__dict__.setdefault("til", "uz")     # eski pickle uchun sukut


def main() -> None:
    print("=== 1. __getstate__ / __setstate__ ===")
    ulanish = Ulanish("db.example")
    ulanish.urinishlar = 3
    tiklangan = pickle.loads(pickle.dumps(ulanish))
    print(f"  host: {tiklangan.host}, urinishlar: {tiklangan.urinishlar}")
    print(f"  soket saqlanmadi: {tiklangan._soket}")
    print(f"  yangi maydon sukut bilan tiklandi: {tiklangan.qayta_ulanish}")

    print("\n=== 2. persistent_id — tashqi havola ===")
    bufer = io.BytesIO()
    RefPickler(bufer).dump({"buyurtma": 100, "mijoz": DBYozuv("foydalanuvchilar", 7),
                            "mahsulot": DBYozuv("mahsulotlar", 42)})
    natija = RefUnpickler(io.BytesIO(bufer.getvalue())).load()
    print(f"  {natija}")

    print("\n=== 3. ⚠️ Versiyalash mo'rtligi ===")
    # Eski pickle da 'til' maydoni yo'q edi — __setstate__ sukut beradi
    qurilgan = Foydalanuvchi.__new__(Foydalanuvchi)
    qurilgan.__setstate__({"ism": "Aziz"})            # eski holatda 'til' yo'q
    print(f"  eski holatdan tiklandi: ism={qurilgan.ism}, til={qurilgan.til} (sukut qo'yildi)")
    # Sinf nomi topilmasa — yuklash buziladi
    saqlangan = pickle.dumps(Foydalanuvchi("Bek"))
    buzuq = saqlangan.replace(b"Foydalanuvchi", b"Boshqasinf")   # sinf nomini o'zgartirish
    try:
        pickle.loads(buzuq)
    except (AttributeError, pickle.UnpicklingError, KeyError, EOFError, ValueError) as xato:
        print(f"  sinf nomi o'zgarsa: {type(xato).__name__}")
    print("  ⭐ pickle sinf HOLATINI saqlaydi, ta'rifini emas — versiyalararo saqlashga yaramaydi")


if __name__ == "__main__":
    main()

Natijaning muhim qismi:

text
=== 1. __getstate__ / __setstate__ ===
  host: db.example, urinishlar: 3
  soket saqlanmadi: None
  yangi maydon sukut bilan tiklandi: True

=== 2. persistent_id — tashqi havola ===
  {'buyurtma': 100, 'mijoz': '<foydalanuvchilar#7 bazadan yuklab olindi>', 'mahsulot': '<mahsulotlar#42 bazadan yuklab olindi>'}

=== 3. ⚠️ Versiyalash mo'rtligi ===
  eski holatdan tiklandi: ism=Aziz, til=uz (sukut qo'yildi)
  sinf nomi o'zgarsa: UnpicklingError
  ⭐ pickle sinf HOLATINI saqlaydi, ta'rifini emas — versiyalararo saqlashga yaramaydi

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

Misol 4 — Amaliy: xavfsiz kesh qatlami

Qimmat hisob-kitob (masalan tashqi hisobot yaratish) natijasini diskka keshlaymiz. Kesh faqat o'zimiz yozgan ma'lumot bo'lgani uchun pickle mos — lekin uni ham to'g'ri qilish kerak: HMAC imzo bilan yaxlitlikni tekshirish, atomar yozish, versiya belgisini tekshirish, muddat (TTL). Bu — pickle ni ishonchli kontekstda to'g'ri ishlatish namunasi.

python
"""Diskdagi pickle kesh: HMAC imzo, versiya belgisi, TTL, atomar yozish, buzilgan/soxta/eskirgan keshni rad etib qayta hisoblash."""

import hashlib
import hmac
import os
import pickle
import tempfile
import time
from dataclasses import dataclass
from pathlib import Path

KESH_KALITI = b"server-maxfiy-kalit"
KESH_VERSIYASI = 2


@dataclass
class Hisobot:
    nom: str
    qatorlar: int
    yaratilgan: float


class Kesh:
    def __init__(self, katalog: Path, ttl: float) -> None:
        self.katalog = katalog
        self.ttl = ttl
        self.hisoblashlar = 0

    def _yol(self, kalit: str) -> Path:
        xesh = hashlib.sha256(kalit.encode()).hexdigest()[:16]
        return self.katalog / f"{xesh}.pkl"

    def _yoz(self, yol: Path, qiymat: object) -> None:
        paket = {"versiya": KESH_VERSIYASI, "vaqt": time.time(), "qiymat": qiymat}
        data = pickle.dumps(paket, protocol=pickle.HIGHEST_PROTOCOL)
        imzo = hmac.new(KESH_KALITI, data, hashlib.sha256).digest()
        vaqtinchalik = yol.with_suffix(".tmp")
        vaqtinchalik.write_bytes(imzo + data)
        os.replace(vaqtinchalik, yol)                 # atomar almashtirish

    def _oqi(self, yol: Path) -> object:
        blob = yol.read_bytes()
        imzo, data = blob[:32], blob[32:]
        if not hmac.compare_digest(imzo, hmac.new(KESH_KALITI, data, hashlib.sha256).digest()):
            raise ValueError("imzo mos emas")
        paket = pickle.loads(data)
        if paket["versiya"] != KESH_VERSIYASI:
            raise ValueError(f"versiya {paket['versiya']} ≠ {KESH_VERSIYASI}")
        if time.time() - paket["vaqt"] > self.ttl:
            raise ValueError("muddati o'tgan")
        return paket["qiymat"]

    def ol(self, kalit: str, hisobla):
        yol = self._yol(kalit)
        if yol.exists():
            try:
                return self._oqi(yol), "keshdan"
            except ValueError as xato:
                sabab = str(xato)
        else:
            sabab = "yo'q"
        qiymat = hisobla()
        self.hisoblashlar += 1
        self._yoz(yol, qiymat)
        return qiymat, f"qayta hisoblandi ({sabab})"


def main() -> None:
    with tempfile.TemporaryDirectory() as katalog:
        kesh = Kesh(Path(katalog), ttl=60)

        def qimmat_hisob() -> Hisobot:
            return Hisobot("oylik", qatorlar=10_000, yaratilgan=0.0)

        print("=== 1. Birinchi va ikkinchi murojaat ===")
        q1, holat1 = kesh.ol("oylik-2026-09", qimmat_hisob)
        q2, holat2 = kesh.ol("oylik-2026-09", qimmat_hisob)
        print(f"  1: {holat1}, 2: {holat2}")
        print(f"  qiymat bir xil: {q1 == q2}, jami hisoblashlar: {kesh.hisoblashlar}")

        print("\n=== 2. Buzilgan kesh fayli ===")
        yol = kesh._yol("oylik-2026-09")
        blob = bytearray(yol.read_bytes())
        blob[40] ^= 0x01                              # bitta baytni buzish
        yol.write_bytes(blob)
        q3, holat3 = kesh.ol("oylik-2026-09", qimmat_hisob)
        print(f"  3: {holat3} (imzo mos kelmadi → qayta hisoblandi)")

        print("\n=== 3. Soxta kesh (imzosiz hujum) ===")
        soxta = pickle.dumps({"versiya": KESH_VERSIYASI, "vaqt": time.time(), "qiymat": "soxta"})
        yol.write_bytes(bytes(32) + soxta)            # nol imzo bilan
        q4, holat4 = kesh.ol("oylik-2026-09", qimmat_hisob)
        print(f"  4: {holat4}, qiymat soxta emas: {q4 != 'soxta'}")

        print("\n=== 4. Eskirgan versiya ===")
        eski = pickle.dumps({"versiya": 1, "vaqt": time.time(), "qiymat": "eski format"})
        imzo = hmac.new(KESH_KALITI, eski, hashlib.sha256).digest()
        yol.write_bytes(imzo + eski)
        q5, holat5 = kesh.ol("oylik-2026-09", qimmat_hisob)
        print(f"  5: {holat5}")
        print(f"  jami hisoblashlar: {kesh.hisoblashlar} (har xato → qayta)")


if __name__ == "__main__":
    main()

Natijaning muhim qismi:

text
=== 1. Birinchi va ikkinchi murojaat ===
  1: qayta hisoblandi (yo'q), 2: keshdan
  qiymat bir xil: True, jami hisoblashlar: 1

=== 2. Buzilgan kesh fayli ===
  3: qayta hisoblandi (imzo mos emas) (imzo mos kelmadi → qayta hisoblandi)

=== 3. Soxta kesh (imzosiz hujum) ===
  4: qayta hisoblandi (imzo mos emas), qiymat soxta emas: True

=== 4. Eskirgan versiya ===
  5: qayta hisoblandi (versiya 1 ≠ 2)
  jami hisoblashlar: 4 (har xato → qayta)

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


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

Noto'g'ri fikr To'g'risi
"pickle shunchaki ma'lumot saqlaydi" Obyektni qayta qurish chaqiruvini saqlaydi — kod bajaradi
"Cheklangan Unpickler pickle ni xavfsiz qiladi" Hujum yuzasini kamaytiradi, yo'q qilmaydi
"pickle fayllarni almashish uchun yaxshi" Faqat Python, versiyaga mo'rt — JSON ishlating
"pickle matn format" Binar — "wb"/"rb"
"Saqlangan model doim yuklanadi" Sinf/kutubxona o'zgarsa — buziladi
"pickle JSON dan sekin" Odatda tezroq va ixchamroq
"JSON o'zaro havolalarni saqlaydi" Yo'q; pickle saqlaydi
"__reduce__ kamdan-kam ishlatiladi" Har bir pickle uning ustida ishlaydi

6. Keng tarqalgan xatolar va yechimlari

1. Ishonchsiz pickle ni yuklash

python
model = pickle.loads(sorov.body)                   # ❌ RCE
data = json.loads(sorov.body)                      # ✅ yoki imzolangan/cheklangan

2. Matn rejimida ochish

python
open("a.pkl", "w")                                 # ❌ TypeError yoki buzilgan
open("a.pkl", "wb")                                # ✅

3. Uzoq muddatli saqlash uchun pickle

python
pickle.dump(sozlama, f)                            # ❌ versiya o'zgarsa yo'qoladi
json.dump(sozlama, f, ensure_ascii=False)          # ✅

4. Saqlab bo'lmaydigan atributli obyekt

python
pickle.dumps(obj_with_socket)                      # ❌ TypeError
# ✅ __getstate__ da soketni chiqaring, __setstate__ da qayta oching

5. Imzosiz kesh

python
qiymat = pickle.load(kesh_fayli)                   # ❌ kesh buzilsa/almashtirsa
# ✅ HMAC imzo + versiya + TTL tekshiruvi

6. Lambda ni pickle qilish (multiprocessing)

python
pool.map(lambda x: x * 2, data)                    # ❌ PicklingError
pool.map(kopaytir, data)                           # ✅ modul darajasidagi funksiya

7. Faqat pickle.loads(...) da keng except

python
try: pickle.loads(b)
except Exception: pass                             # ❌ kod allaqachon bajarilgan bo'lishi mumkin
# ✅ yuklashdan OLDIN imzoni tekshiring

8. Protokolni ko'rsatmaslik (eski Python bilan almashinuv)

python
pickle.dumps(obj)                                  # protokol 5 — eski Python o'qiy olmaydi
pickle.dumps(obj, protocol=2)                      # ✅ moslik kerak bo'lsa

7. Integratsiya — bu bilim qayerda kerak bo'ladi

  • 8-qism (o'tilgan): OOP — __dict__, dunder metodlar
  • 14-qism (o'tilgan): multiprocessing argumentlarni pickle bilan uzatadi
  • 16.2-dars (o'tilgan): JSON — xavfsiz, tillararo muqobil
  • 16.7-dars: sqlite3 — tuzilgan saqlash
  • 23-qism: ORM va bazalar — obyektlarni saqlashning ishonchli yo'li
  • 25-qism: ML modellarini saqlash (joblib, formatlar va ularning xavfi)
  • 28-qism: deploy — model artefaktlari va ularni ishonchli manbadan olish

8. Eng yaxshi amaliyotlar

  1. Ishonchsiz pickle ni HECH QACHON yuklamang. Bu birinchi va eng muhim qoida.

  2. Almashinuv, sozlama, uzoq saqlash — JSON; pickle faqat Python-ichi, vaqtinchalik holat.

  3. O'zingiz yozgan pickle ni HMAC bilan imzolang va yuklashdan oldin tekshiring.

  4. Binar rejim ("wb"/"rb") va aniq protokol.

  5. Saqlab bo'lmaydigan qismni __getstate__/__setstate__ bilan boshqaring.

  6. Kesh — versiya belgisi, TTL va atomar yozish bilan.

  7. multiprocessing uchun modul darajasidagi funksiyalar (lambda emas).

  8. ML modellarini pickle qilsangiz — muhitni (Python va kutubxona versiyalari) yozib qo'ying.


9. Amaliy topshiriq

Vazifa 1: Natijani bashorat qiling

python
import pickle, json
1.  print(type(pickle.dumps([1, 2])).__name__)
2.  print(pickle.loads(pickle.dumps((1, 2))) == (1, 2))
3.  print(pickle.loads(pickle.dumps({1, 2, 3})) == {1, 2, 3})
4.  a = [1]; b = [a, a]; r = pickle.loads(pickle.dumps(b)); print(r[0] is r[1])
5.  print(pickle.DEFAULT_PROTOCOL)
6.  print(pickle.dumps({}, protocol=2)[:2])
7.  try:
        pickle.dumps(lambda: 1)
    except Exception as e:
        print(type(e).__name__)
8.  x = []; x.append(x); print(pickle.loads(pickle.dumps(x))[0] is not None)
9.  print(len(pickle.dumps(0)) < len(json.dumps(0)))
10. print(pickle.loads(pickle.dumps("Oʻzbek")) == "Oʻzbek")
11. try:
        json.dumps({1, 2})
    except Exception as e:
        print(type(e).__name__)
12. print(pickle.HIGHEST_PROTOCOL >= pickle.DEFAULT_PROTOCOL)
Javoblar
  1. bytes
  2. True
  3. True
  4. True
  5. 5
  6. b'\x80\x02'
  7. PicklingError
  8. True
  9. False (pickle.dumps(0) — 5 bayt, json.dumps(0) — 1 belgi)
  10. True
  11. TypeError
  12. True

Vazifa 2: Xatolarni tuzating

python
1.  def saqla(yol, obyekt):
        with open(yol, "w") as f:
            pickle.dump(obyekt, f)

2.  def api_yukla(sorov_tanasi):
        return pickle.loads(sorov_tanasi)          # foydalanuvchidan keladi

3.  def sozlama_saqla(yol, sozlama):
        with open(yol, "wb") as f:
            pickle.dump(sozlama, f)                 # boshqa tildagi xizmat ham o'qiydi

4.  def kesh_oqi(yol):
        with open(yol, "rb") as f:
            return pickle.load(f)                   # kesh papkasiga hamma yoza oladi

5.  def parallel(sonlar):
        with multiprocessing.Pool() as p:
            return p.map(lambda n: n * n, sonlar)
Javoblar
python
1.  def saqla(yol, obyekt):
        with open(yol, "wb") as f:                 # binar rejim
            pickle.dump(obyekt, f, protocol=pickle.HIGHEST_PROTOCOL)

2.  def api_yukla(sorov_tanasi):
        return json.loads(sorov_tanasi)            # ishonchsiz — pickle emas, JSON + validatsiya

3.  def sozlama_saqla(yol, sozlama):
        with open(yol, "w", encoding="utf-8") as f:
            json.dump(sozlama, f, ensure_ascii=False, indent=2)   # tillararo — JSON

4.  def kesh_oqi(yol):
        blob = yol.read_bytes()
        imzo, data = blob[:32], blob[32:]
        if not hmac.compare_digest(imzo, hmac.new(KALIT, data, hashlib.sha256).digest()):
            raise ValueError("kesh buzilgan yoki soxta")
        return pickle.loads(data)

5.  def kvadrat(n):
        return n * n

    def parallel(sonlar):
        with multiprocessing.Pool() as p:
            return p.map(kvadrat, sonlar)          # modul darajasidagi funksiya

Vazifa 3: Xavfsiz saqlash formati

Umumiy saqla(obyekt, yol) / yukla(yol) yozing:

  1. HMAC imzo (kalit os.environ dan), versiya belgisi, yaratilgan vaqt
  2. Yuklashda: imzo → versiya → keyin pickle.loads; har bosqichda aniq xato
  3. Atomar yozish (os.replace), yozish yarim qolganda eski fayl saqlansin
  4. find_class bilan cheklangan variantni ham qo'shing va ikkalasini solishtiring

Vazifa 4: pickle "hujumini" tahlil qilish (mudofaa)

  1. __reduce__ orqali zararsiz marker yozadigan obyekt yarating (masalan faylga "audit" yozadi)
  2. pickletools.dis bilan opcode larni ko'rsating va xavfli chaqiruvni toping
  3. Cheklangan Unpickler uni bloklaganini isbotlang
  4. Xulosa: nega statik tahlil pickle ni to'liq xavfsiz qila olmaydi

Vazifa 5: Kesh dekoratori

  1. @keshla(ttl=...) — funksiya natijasini diskka imzolangan pickle bilan saqlasin
  2. Kesh kaliti — argumentlardan barqaror xesh (16.2 dagi sort_keys g'oyasi)
  3. TTL, versiya va buzilganlik tekshiruvi; buzilgan kesh — qayta hisob
  4. functools.lru_cache (10-dars) bilan solishtiring: xotira vs disk, jarayonlararo

Vazifa 6: Migratsiya

Eski pickle keshni yangi sinf tuzilishiga ko'chiring:

  1. Eski formatdagi bir nechta .pkl yarating (v1 sinf)
  2. __setstate__ bilan yangi maydonlarga sukut bering
  3. O'qib bo'lmaydiganlarni aniqlang va qayta hisoblash uchun belgilang
  4. Xulosa: nega bunday migratsiya JSON da osonroq bo'lardi

Vazifa 7: O'ylash

pickle ning xavfsizlik ogohlantirishi 20 yildan beri hujjatda turadi, ammo pickle bugun ham ML ekotizimida (joblib, PyTorch .pt, ko'plab modellar) keng ishlatiladi va HuggingFace kabi platformalarda ishonchsiz .pkl yuklash haqiqiy hujum vektori bo'lib qolmoqda. Nima uchun xavfli ekani ma'lum bo'lgan format bu qadar chuqur o'rnashib qoldi, va uni qanday yondashuvlar (safetensors, imzolash, sandbox) almashtirmoqda?

Javob

Qisqa javob: pickle chuqur o'rnashgan, chunki u Python obyektini hech qanday qo'shimcha ish qilmasdan saqlashning yagona universal usuli edi — model, murakkab tuzilma, holat, hammasi bir chaqiruv bilan. ML ekotizimi tez o'sib, pickle ustiga qurildi (joblib, PyTorch), va format almashtirish ekotizim inertsiyasiga urildi. Xavf endi jiddiy qabul qilinmoqda: safetensors kabi faqat-ma'lumot formatlar, imzolash va sandbox yuklash tarqalmoqda.

1. Nega o'rnashib qoldi

Sabab Tafsilot
Universal Har qanday Python obyekti, kod yozmasdan
Ekotizim joblib, numpy, PyTorch, scikit-learn — hammasi pickle ustida
Qulaylik save/load bir qatorda; muqobil ko'proq ish talab qilardi
Ichki xavf ko'rinmaydi O'z keshingizda ishlaydi — muammo faqat ishonchsiz manbada
Inertsiya Milliardlab mavjud .pkl fayl va tutorial

2. Nima uchun xavfli qoladi

Omil Tafsilot
Dizayn __reduce__ — kod bajarish formatning asosi, xatolik emas
Ishonch chegarasi noaniq Model "ma'lumot" kabi ko'rinadi, aslida bajariladigan kod
Platformalar Model almashish saytlari — ishonchsiz .pkl ning tabiiy manbai
Yechim noqulaylik talab qiladi Imzo, sandbox — qo'shimcha infratuzilma

3. Almashtiruvchi yondashuvlar

Yondashuv Nima beradi
safetensors Faqat tensorlar — kod yo'q, tez, xavfsiz (yangi standart)
ONNX Model grafigi, til-neytral
Imzolash (HMAC/imzo) Manba va yaxlitlikni tasdiqlaydi
Sandbox yuklash (fickling, cheklangan Unpickler) Opcode tahlili, xavfli chaqiruvlarni bloklash
JSON/Parquet + aniq sxema Ma'lumot uchun umuman pickle dan voz kechish

4. Muhandislik saboqlari

  1. Format tanlashda xavfsizlik — dastlabki mezon, keyingi o'ylash emas
  2. "Ma'lumot" va "kod" chegarasini aniq belgilang — pickle uni xiralashtiradi
  3. Ishonchsiz manba (yuklama, internet, umumiy papka) — hech qachon pickle.load
  4. Ekotizim inertsiyasi — xavfli qarorlarni uzoq yashatadi; yangi loyihada xavfsiz standartni tanlang

5. Xulosa

  1. pickle qulaylik va ekotizim tufayli o'rnashib qoldi
  2. Xavf dizaynning bir qismi — patch bilan yo'qolmaydi
  3. safetensors, imzolash, sandbox almashtirmoqda
  4. Amaliyotda: o'zingiznikini imzolang, begonasini yuklamang, model uchun xavfsiz format tanlang

Nimani mustahkamlaydi: 2.1–2.8-bo'limlar.


Xulosa

Bu darsda pickle ni — uning kuchini va jiddiy xavfini o'rgandik.

Eng muhim uch fikr:

  1. pickle istalgan Python obyektini saqlaydi — chunki u qanday qayta qurishni saqlaydi. Sinf namunalari, o'zaro va rekursiv havolalar, deyarli har qanday stdlib turi ishlaydi (JSON qila olmaydigan narsalar ham); lambda, ochiq fayl, mahalliy sinf esa yo'q. Natija binar ("wb"/"rb"), protokol 5 — ixcham va tez, lekin eski Python o'qiy olmaydi.

  2. pickle.load — kod bajarish, ma'lumot o'qish emas. Har bir pickle __reduce__ ustida ishlaydi, va hujumkor uni istalgan chaqiruvga sozlashi mumkin — shuning uchun ishonchsiz pickle ni yuklash masofadan kod bajarilishiga olib keladi. Bu dizayn xususiyati. find_class bilan cheklangan Unpickler hujum yuzasini kamaytiradi, lekin yo'q qilmaydi; yagona ishonchli qoida — ishonchsiz pickle ni umuman yuklamaslik.

  3. pickle — faqat ishonchli, Python-ichi, vaqtinchalik holat uchun. Almashinuv, sozlama va uzoq saqlash JSON ga tegishli, chunki pickle sinf ta'rifi o'zgarganda mo'rt. O'zingiz yozgan pickle ni (kesh, multiprocessing holati) HMAC bilan imzolang, versiya va TTL bilan tekshiring, atomar yozing — shunda pickle ni to'g'ri va xavfsiz ishlatgan bo'lasiz.

Keyingi darsda tuzilgan hujjatlar formatini — XML ni o'rganamiz: ElementTree bilan tahlil qilish, nom fazolari va nega ishonchsiz XML pickle kabi o'ziga xos xavflarga (entity portlashi, XXE) ega.

Ulashish:Telegram'da

Izohlar (0)

Izoh yozish uchun kiring.

  • Hozircha izoh yo'q. Birinchi bo'ling!
16.4-dars: pickle va xavfsizlik — IlmHamroh