IlmHamroh
Python kursi/Standart kutubxona12/16-dars21 daqiqa
Mundarija (21)

15.12-dars: pathlib

15-QISM — STANDART KUTUBXONA · 12-dars


1. Kirish va motivatsiya

Fayl yo'li — shunchaki satr emas. Unda qismlar (kataloglar, nom, kengaytma), platforma qoidalari (\ yoki /, harfga sezgirlik) va ma'no (absolyut yoki nisbiy, qaysi katalog ichida) bor. Satrlar bilan ishlaganda bularning hammasi qo'lda hisoblanadi — va xato qilinadi.

Real vaziyat. Fayl yuklash xizmati foydalanuvchi faylini shunday saqlardi:

python
yol = YUKLASH_KATALOGI + "/" + foydalanuvchi_fayl_nomi
if yol.startswith(YUKLASH_KATALOGI):
    saqla(yol)

Hujumchi fayl nomi sifatida ../../etc/cron.d/zararli yubordi. Satr YUKLASH_KATALOGI bilan boshlanardi — tekshiruv o'tdi, fayl esa katalogdan tashqariga yozildi. Bu path traversal hujumi. Yana bir xato: /srv/yuklash_eski ham "/srv/yuklash" bilan boshlanadi.

pathlib yo'lni obyekt sifatida ifodalaydi: qismlarga ajratadi, platforma qoidalarini biladi va resolve(), is_relative_to() kabi to'g'ri tekshiruvlarni beradi.

Bu darsda pathlib ni to'liq o'rganamiz: yo'l qurish va tahlil, fayl amallari, qidiruv va xavfsizlik.

Bu darsda:

  • Path va PurePath: yo'l qurish / operatori bilan
  • Qismlar: name, stem, suffix, suffixes, parent, parts
  • with_name, with_stem, with_suffix
  • Absolyut yo'l bilan birlashtirish, .. va resolve()
  • relative_to, is_relative_to — va startswith tuzog'i
  • Fayl amallari: read_text, write_text, mkdir, unlink, rename, replace
  • 3.14: Path.copy, Path.move
  • Qidiruv: glob, rglob, walk 3.12-bob, full_match (3.13)
  • Platforma farqlari: PurePosixPath, PureWindowsPath
  • Xavfsizlik: path traversal va fayl nomini tozalash
  • Amaliy: fotosuratlarni sana bo'yicha tartiblash vositasi

2. Nazariya — chuqur tushuntirish

2.1. Path va sinflar oilasi

Sinf Fayl tizimiga murojaat Qachon
Path Oddiy ish — joriy platforma
PurePath Faqat yo'l ustida hisob
PurePosixPath Linux/macOS yo'llari (masalan, serverdagi yo'lni Windows'da tahlil qilish)
PureWindowsPath Windows yo'llari
PosixPath / WindowsPath Path() avtomatik shulardan birini yaratadi
python
from pathlib import Path

p = Path("data") / "2026" / "sotuv.csv"      # ⭐ / operatori
Path.cwd(); Path.home()                      # joriy va uy katalogi

os, shutil, open kabi funksiyalar Path ni to'g'ridan-to'g'ri qabul qiladi (os.PathLike). Satr kerak bo'lsa — str(p) yoki os.fspath(p).

2.2. Qismlar

Path("data/2026/arxiv.tar.gz") uchun:

Atribut Qiymat
name "arxiv.tar.gz"
stem "arxiv.tar"
suffix ".gz"
suffixes [".tar", ".gz"]
parent Path("data/2026")
parents data/2026, data, .
parts ("data", "2026", "arxiv.tar.gz")
anchor "" (nisbiy); "/" yoki "C:\\" (absolyut)

Path(".bashrc").suffix — "", stem — ".bashrc": nuqta bilan boshlanadigan nom kengaytma emas.

Almashtirish (yangi obyekt qaytaradi):

Metod data/sotuv.csv dan
with_name("x.txt") data/x.txt
with_stem("xarajat") data/xarajat.csv
with_suffix(".json") data/sotuv.json
with_suffix("") data/sotuv
with_suffix("json") ValueError: Invalid suffix — nuqta shart

2.3. Birlashtirish va normallash

Ifoda Natija Izoh
Path("a") / "b" a/b
Path("a") / "/b" /b Absolyut qism oldingisini tashlaydi
Path("a/./b") a/b . olib tashlanadi
Path("a/b/../c") a/b/../c .. saqlanadi (symlink tufayli)
Path("a/../b") == Path("b") False
p.resolve() Absolyut, .. va symlink lar hal qilingan Xavfsizlik tekshiruvi uchun
p.absolute() Absolyut, lekin .. saqlanadi

Foydalanuvchi bergan qismni / bilan qo'shish xavfsiz emas: u absolyut bo'lishi yoki .. saqlashi mumkin.

2.4. relative_to va is_relative_to

python
Path("/home/aziz/proj/src/app.py").relative_to("/home/aziz/proj")   # src/app.py
Path("/a/b").relative_to("/c")                                     # ValueError
Path("/a/b").is_relative_to("/a")                                  # True
Tekshiruv /srv/yuklash_eski/x "/srv/yuklash" ichidami?
str(p).startswith("/srv/yuklash") True — noto'g'ri
p.is_relative_to("/srv/yuklash") False — qismlar bo'yicha

Xavfsiz tekshiruv: avval resolve(), keyin is_relative_to(ildiz.resolve()).

2.5. Fayl amallari

Metod Nima qiladi
exists(), is_file(), is_dir() Tekshiruv
read_text(encoding=...), write_text(...) Butun matn — kichik fayllar uchun
read_bytes(), write_bytes() Baytlar
open(...) Katta fayllar, oqim
mkdir(parents=True, exist_ok=True) Katalog yaratish
touch(exist_ok=True) Bo'sh fayl yoki vaqtni yangilash
unlink(missing_ok=True) Faylni o'chirish
rmdir() Faqat bo'sh katalog
rename(yangi) Qayta nomlash (Windows'da mavjud faylga — xato)
replace(yangi) Ustiga yozadi
copy(maqsad) / move(maqsad) 3.14+: fayl va katalog nusxasi/ko'chirish; ota katalog mavjud bo'lishi kerak
stat() Hajm, vaqtlar

read_text va write_text ga doim encoding="utf-8" bering — aks holda Windows'da tizim kodlashi ishlatiladi (16.1-dars).

exists() tekshiruvi va keyingi amal orasida fayl o'zgarishi mumkin (TOCTOU). "Yo'q bo'lsa yarat" — exist_ok, missing_ok yoki open(..., "x").

2.6. Qidiruv

Metod Nima qiladi
iterdir() Bevosita ichidagilar
glob("*.py") Shu katalogda naqsh bo'yicha
glob("**/*.py") Rekursiv
rglob("*.py") glob("**/*.py") bilan bir xil
walk() 3.12+: os.walk ning Path versiyasi — (katalog, ichki, fayllar)
match("*.py") Yo'l oxiridan naqsh bilan solishtirish
full_match("**/*.py") 3.13+: butun yo'l bilan solishtirish
Naqsh Ma'nosi
* Bitta qismdagi istalgan belgilar
? Bitta belgi
[abc] Belgilar to'plami
** Istalgan chuqurlikdagi kataloglar

rglob yashirin kataloglarga ham (.git, .venv) kiradi va natija tartibi kafolatlanmagan — kerak bo'lsa sorted(). Katta daraxtlarda kesish kerak bo'lsa — walk() va ichki[:] = ... (15.11-dars).

2.7. Platforma farqlari

PurePosixPath PureWindowsPath
Ajratgich / \ (lekin / ham qabul qilinadi)
Harfga sezgirlik (taqqoslash) Farqli Farqsiz
Anchor / C:\, \\server\share\
"C:/x" / "D:/y" — D:\y

Ma'lumotlar bazasi, JSON yoki URL uchun yo'lni as_posix() bilan saqlang — platformadan mustaqil.


3. Tez ma'lumotnoma

python
from pathlib import Path

ildiz = Path(__file__).resolve().parent
sozlama = ildiz / "config" / "app.toml"
sozlama.parent.mkdir(parents=True, exist_ok=True)
matn = sozlama.read_text(encoding="utf-8")
sozlama.with_suffix(".bak").write_text(matn, encoding="utf-8")

for f in sorted(ildiz.rglob("*.py")): ...
f.relative_to(ildiz).as_posix()

yol = (YUKLASH / nom).resolve()
if not yol.is_relative_to(YUKLASH.resolve()): raise ValueError("ruxsat yo'q")

Qoidalar

yo'l — Path, satr emas
Path / "/absolyut" — oldingi qism yo'qoladi
xavfsizlik: resolve() + is_relative_to(), startswith emas
read_text/write_text — encoding="utf-8"
rglob yashirin kataloglarga ham kiradi
saqlash va almashinuv — as_posix()

4. Batafsil misollar

Misol 1 — Yo'l qurish va qismlar

python
"""/ operatori; name, stem, suffix, suffixes, parent, parents, parts; with_* metodlari; absolyut qism va .. ; resolve; Pure yo'llar."""

from pathlib import Path, PurePosixPath, PureWindowsPath


def main() -> None:
    print("=== 1. Qurish va qismlar ===")
    p = PurePosixPath("data") / "2026" / "arxiv.tar.gz"
    print(f"  yo'l:     {p}")
    print(f"  name={p.name!r}, stem={p.stem!r}, suffix={p.suffix!r}, suffixes={p.suffixes}")
    print(f"  parent={p.parent}, parts={p.parts}")
    print(f"  parents: {[str(x) for x in p.parents]}")
    print(f"  ⚠️ '.bashrc': suffix={PurePosixPath('.bashrc').suffix!r}, stem={PurePosixPath('.bashrc').stem!r}")

    print("\n=== 2. with_* metodlari ===")
    fayl = PurePosixPath("hisobot/sotuv.csv")
    print(f"  with_name('x.txt')   → {fayl.with_name('x.txt')}")
    print(f"  with_stem('xarajat') → {fayl.with_stem('xarajat')}")
    print(f"  with_suffix('.json') → {fayl.with_suffix('.json')}")
    print(f"  with_suffix('')      → {fayl.with_suffix('')}")
    try:
        fayl.with_suffix("json")
    except ValueError as xato:
        print(f"  with_suffix('json')  → ValueError: {xato}")

    print("\n=== 3. ⚠️ Birlashtirish tuzoqlari ===")
    print(f"  'yuklash' / 'rasm.png'    → {PurePosixPath('yuklash') / 'rasm.png'}")
    print(f"  'yuklash' / '/etc/passwd' → {PurePosixPath('yuklash') / '/etc/passwd'}  ← oldingi qism yo'qoldi")
    print(f"  'a/./b' → {PurePosixPath('a/./b')},  'a/b/../c' → {PurePosixPath('a/b/../c')}  ← .. saqlanadi")
    print(f"  Path('a/../b') == Path('b'): {PurePosixPath('a/../b') == PurePosixPath('b')}")

    print("\n=== 4. resolve() ===")
    asosiy = Path.cwd()
    chalkash = asosiy / "a" / "b" / ".." / ".." / "c.txt"
    print(f"  resolve() '..' larni hal qildi: {chalkash.resolve() == (asosiy / 'c.txt').resolve()}")
    print(f"  absolute() esa saqlaydi: {'..' in chalkash.absolute().parts}")

    print("\n=== 5. Platforma yo'llari ===")
    print(f"  Windows: 'C:/Users/Aziz' == 'c:/users/aziz' → {PureWindowsPath('C:/Users/Aziz') == PureWindowsPath('c:/users/aziz')}")
    print(f"  POSIX:   '/home/Aziz' == '/home/aziz' → {PurePosixPath('/home/Aziz') == PurePosixPath('/home/aziz')}")
    print(f"  Windows anchor: {PureWindowsPath('C:/x/y').anchor!r}, POSIX anchor: {PurePosixPath('/x/y').anchor!r}")
    print(f"  Windows yo'li as_posix(): {PureWindowsPath('C:\\Loyiha\\src\\app.py').as_posix()}")
    print(f"  Path turi shu tizimda: {type(Path('x')).__name__} (Linux/macOS'da PosixPath)")


if __name__ == "__main__":
    main()

Natijaning muhim qismi:

text
=== 1. Qurish va qismlar ===
  yo'l:     data/2026/arxiv.tar.gz
  name='arxiv.tar.gz', stem='arxiv.tar', suffix='.gz', suffixes=['.tar', '.gz']
  parent=data/2026, parts=('data', '2026', 'arxiv.tar.gz')
  parents: ['data/2026', 'data', '.']
  ⚠️ '.bashrc': suffix='', stem='.bashrc'

=== 2. with_* metodlari ===
  with_name('x.txt')   → hisobot/x.txt
  with_stem('xarajat') → hisobot/xarajat.csv
  with_suffix('.json') → hisobot/sotuv.json
  with_suffix('')      → hisobot/sotuv
  with_suffix('json')  → ValueError: Invalid suffix 'json'

=== 3. ⚠️ Birlashtirish tuzoqlari ===
  'yuklash' / 'rasm.png'    → yuklash/rasm.png
  'yuklash' / '/etc/passwd' → /etc/passwd  ← oldingi qism yo'qoldi
  'a/./b' → a/b,  'a/b/../c' → a/b/../c  ← .. saqlanadi
  Path('a/../b') == Path('b'): False

=== 4. resolve() ===
  resolve() '..' larni hal qildi: True
  absolute() esa saqlaydi: True

=== 5. Platforma yo'llari ===
  Windows: 'C:/Users/Aziz' == 'c:/users/aziz' → True
  POSIX:   '/home/Aziz' == '/home/aziz' → False
  Windows anchor: 'C:\\', POSIX anchor: '/'
  Windows yo'li as_posix(): C:/Loyiha/src/app.py
  Path turi shu tizimda: WindowsPath (Linux/macOS'da PosixPath)

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

Misol 2 — Fayl amallari va qidiruv

python
"""mkdir, write_text, read_text, touch, unlink, rmdir, rename, replace; 3.14 copy va move; iterdir, glob, rglob, walk; match va full_match; TOCTOU."""

import tempfile
from pathlib import Path


def korinish(yollar, ildiz: Path) -> list[str]:
    return sorted(p.relative_to(ildiz).as_posix() for p in yollar)


def main() -> None:
    with tempfile.TemporaryDirectory() as vaqtinchalik:
        r = Path(vaqtinchalik)

        print("=== 1. Yaratish va o'qish ===")
        (r / "src" / "pkg").mkdir(parents=True)
        (r / "src" / "pkg" / "yordamchi.py").write_text("def f(): ...\n", encoding="utf-8")
        (r / "src" / "app.py").write_text("print('salom')\n", encoding="utf-8")
        (r / "README.md").write_text("# Loyiha\n", encoding="utf-8")
        (r / ".git" / "objects").mkdir(parents=True)
        (r / ".git" / "objects" / "hook.py").write_text("", encoding="utf-8")
        (r / "rasm.png").write_bytes(b"\x89PNG\r\n")
        print(f"  README: {(r / 'README.md').read_text(encoding='utf-8')!r}, hajm: {(r / 'README.md').stat().st_size} bayt")
        print(f"  rasm boshi: {(r / 'rasm.png').read_bytes()[:4]!r}")
        try:
            (r / "src").mkdir()
        except FileExistsError:
            print("  mkdir mavjud katalogga → FileExistsError; exist_ok=True bilan jim")
        (r / "src").mkdir(exist_ok=True)

        print("\n=== 2. O'chirish va nomlash ===")
        vaqt = r / "vaqtinchalik.txt"
        vaqt.touch()
        vaqt.unlink()
        vaqt.unlink(missing_ok=True)
        print(f"  unlink(missing_ok=True) — xato yo'q, mavjud: {vaqt.exists()}")
        try:
            (r / "src").rmdir()
        except OSError as xato:
            print(f"  bo'sh bo'lmagan katalogni rmdir → {type(xato).__name__}")
        eski = r / "README.md"
        yangi = eski.rename(r / "OQIBCHIQ.md")
        print(f"  rename natijasi: {yangi.name}, eski mavjud: {eski.exists()}")
        (r / "nusxa.md").write_text("eski", encoding="utf-8")
        yangi.replace(r / "nusxa.md")
        print(f"  replace mavjud faylga yozdi: {(r / 'nusxa.md').read_text(encoding='utf-8')!r}")

        print("\n=== 3. 3.14: copy va move ===")
        (r / "src" / "app.py").copy(r / "app_nusxa.py")
        (r / "src").copy(r / "src_zaxira")
        print(f"  fayl nusxasi: {(r / 'app_nusxa.py').read_text(encoding='utf-8')!r}")
        print(f"  katalog nusxasi: {korinish((r / 'src_zaxira').rglob('*'), r)}")
        (r / "arxiv").mkdir()                              # ⚠️ move ota katalogni yaratmaydi
        (r / "src_zaxira").move(r / "arxiv" / "src_2026")
        print(f"  ko'chirildi: {(r / 'arxiv' / 'src_2026' / 'app.py').exists()}, eski yo'q: {not (r / 'src_zaxira').exists()}")

        print("\n=== 4. Qidiruv ===")
        print(f"  iterdir:          {korinish(r.iterdir(), r)}")
        print(f"  glob('*.py'):     {korinish(r.glob('*.py'), r)}")
        print(f"  rglob('*.py'):    {korinish(r.rglob('*.py'), r)}")
        print("  ⚠️ rglob .git ichiga ham kirdi")
        yurish = []
        for katalog, ichki, fayllar in r.walk():
            ichki[:] = sorted(d for d in ichki if not d.startswith("."))
            yurish.extend((katalog / f).relative_to(r).as_posix() for f in fayllar if f.endswith(".py"))
        print(f"  walk + kesish:    {sorted(yurish)}")

        print("\n=== 5. match va full_match ===")
        yol = Path("src/pkg/yordamchi.py")
        for naqsh in ("*.py", "pkg/*.py", "src/*.py", "src/**/*.py"):
            print(f"  {naqsh:14} match={yol.match(naqsh)!s:5}  full_match={yol.full_match(naqsh)}")


if __name__ == "__main__":
    main()

Natijaning muhim qismi:

text
=== 1. Yaratish va o'qish ===
  README: '# Loyiha\n', hajm: 10 bayt
  rasm boshi: b'\x89PNG'
  mkdir mavjud katalogga → FileExistsError; exist_ok=True bilan jim

=== 2. O'chirish va nomlash ===
  unlink(missing_ok=True) — xato yo'q, mavjud: False
  bo'sh bo'lmagan katalogni rmdir → OSError
  rename natijasi: OQIBCHIQ.md, eski mavjud: False
  replace mavjud faylga yozdi: '# Loyiha\n'

=== 3. 3.14: copy va move ===
  fayl nusxasi: "print('salom')\n"
  katalog nusxasi: ['src_zaxira/app.py', 'src_zaxira/pkg', 'src_zaxira/pkg/yordamchi.py']
  ko'chirildi: True, eski yo'q: True

=== 4. Qidiruv ===
  iterdir:          ['.git', 'app_nusxa.py', 'arxiv', 'nusxa.md', 'rasm.png', 'src']
  glob('*.py'):     ['app_nusxa.py']
  rglob('*.py'):    ['.git/objects/hook.py', 'app_nusxa.py', 'arxiv/src_2026/app.py', 'arxiv/src_2026/pkg/yordamchi.py', 'src/app.py', 'src/pkg/yordamchi.py']
  ⚠️ rglob .git ichiga ham kirdi
  walk + kesish:    ['app_nusxa.py', 'arxiv/src_2026/app.py', 'arxiv/src_2026/pkg/yordamchi.py', 'src/app.py', 'src/pkg/yordamchi.py']

=== 5. match va full_match ===
  *.py           match=True   full_match=False
  pkg/*.py       match=True   full_match=False
  src/*.py       match=False  full_match=False
  src/**/*.py    match=True   full_match=True

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

Misol 3 — Xavfsiz yo'l tekshiruvi

python
"""Path traversal hujumi; startswith tuzog'i; resolve + is_relative_to; relative_to xatosi; fayl nomini tozalash."""

import re
import tempfile
from pathlib import Path


def xato_tekshiruv(ildiz: str, nom: str) -> bool:
    yol = ildiz + "/" + nom
    return yol.startswith(ildiz)


def togri_tekshiruv(ildiz: Path, nom: str) -> Path | None:
    yol = (ildiz / nom).resolve()
    return yol if yol.is_relative_to(ildiz.resolve()) else None


def nom_tozala(nom: str) -> str:
    asosiy = Path(nom.replace("\\", "/")).name                  # faqat oxirgi qism
    asosiy = re.sub(r"[^\w.\-]", "_", asosiy)                   # xavfli belgilar
    asosiy = asosiy.lstrip(".") or "fayl"                        # yashirin va bo'sh nomlar
    return asosiy[:100]


def main() -> None:
    with tempfile.TemporaryDirectory() as vaqtinchalik:
        yuklash = Path(vaqtinchalik) / "yuklash"
        yuklash.mkdir()
        (Path(vaqtinchalik) / "yuklash_eski").mkdir()
        nomlar = ["rasm.png", "hujjat/2026/shartnoma.pdf", "../yuklash_eski/sir.txt",
                  "../../etc/passwd", "/etc/passwd", "pkg/../rasm.png"]

        print("=== 1. ⚠️ Satr bilan tekshiruv ===")
        otdi, rad = "o'tdi", "rad"
        for nom in nomlar:
            print(f"  {nom:28} startswith → {otdi if xato_tekshiruv(str(yuklash), nom) else rad}")

        print("\n=== 2. ✅ resolve + is_relative_to ===")
        for nom in nomlar:
            natija = togri_tekshiruv(yuklash, nom)
            korinish = natija.relative_to(yuklash.resolve()).as_posix() if natija else "❌ rad etildi"
            print(f"  {nom:28} → {korinish}")

        print("\n=== 3. ⚠️ Prefiks bilan bir xil boshlanadigan katalog ===")
        eski = (Path(vaqtinchalik) / "yuklash_eski" / "sir.txt").resolve()
        print(f"  str.startswith: {str(eski).startswith(str(yuklash.resolve()))}")
        print(f"  is_relative_to: {eski.is_relative_to(yuklash.resolve())}")
        try:
            eski.relative_to(yuklash.resolve())
        except ValueError:
            print("  relative_to → ValueError (ichida emas)")

        print("\n=== 4. Fayl nomini tozalash ===")
        for nom in ("../../etc/passwd", "C:\\Windows\\win.ini", ".htaccess", "hisobot 2026 (yakuniy).pdf",
                    "rasm<script>.png", "..", ""):
            print(f"  {nom!r:32} → {nom_tozala(nom)!r}")


if __name__ == "__main__":
    main()

Natijaning muhim qismi:

text
=== 1. ⚠️ Satr bilan tekshiruv ===
  rasm.png                     startswith → o'tdi
  hujjat/2026/shartnoma.pdf    startswith → o'tdi
  ../yuklash_eski/sir.txt      startswith → o'tdi
  ../../etc/passwd             startswith → o'tdi
  /etc/passwd                  startswith → o'tdi
  pkg/../rasm.png              startswith → o'tdi

=== 2. ✅ resolve + is_relative_to ===
  rasm.png                     → rasm.png
  hujjat/2026/shartnoma.pdf    → hujjat/2026/shartnoma.pdf
  ../yuklash_eski/sir.txt      → ❌ rad etildi
  ../../etc/passwd             → ❌ rad etildi
  /etc/passwd                  → ❌ rad etildi
  pkg/../rasm.png              → rasm.png

=== 3. ⚠️ Prefiks bilan bir xil boshlanadigan katalog ===
  str.startswith: True
  is_relative_to: False
  relative_to → ValueError (ichida emas)

=== 4. Fayl nomini tozalash ===
  '../../etc/passwd'               → 'passwd'
  'C:\\Windows\\win.ini'           → 'win.ini'
  '.htaccess'                      → 'htaccess'
  'hisobot 2026 (yakuniy).pdf'     → 'hisobot_2026__yakuniy_.pdf'
  'rasm<script>.png'               → 'rasm_script_.png'
  '..'                             → 'fayl'
  ''                               → 'fayl'

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

Misol 4 — Amaliy: fotosuratlarni tartiblash vositasi

Telefondan ko'chirilgan "tartibsiz" katalogni tartibga keltiramiz: rasmlar YYYY/MM/ kataloglariga, bir xil nomli fayllar to'qnashuvsiz (_1, _2), takroriy fayllar (bir xil mazmun) o'chirilmasdan takrorlar/ ga, qo'llab-quvvatlanmaydigan fayllar joyida qoladi. Avval quruq ishga tushirish (dry run) rejasi, keyin bajarish va natijani tekshirish. Sana fayl nomidan olinadi (IMG_20260917_...).

python
"""rglob bilan qidirish; nomdan sana ajratish; with_stem bilan to'qnashuvsiz nom; xesh bilan takrorlarni aniqlash; quruq ishga tushirish rejasi; move va natijani tekshirish."""

import hashlib
import re
import tempfile
from collections import Counter
from pathlib import Path

KENGAYTMALAR = {".jpg", ".jpeg", ".png", ".heic"}
SANA = re.compile(r"(?<![0-9])(20[0-9]{2})([01][0-9])([0-3][0-9])(?![0-9])")

NAMUNA = {
    "DCIM/Camera/IMG_20260917_101500.jpg": b"toshkent-1",
    "DCIM/Camera/IMG_20260917_101501.jpg": b"toshkent-2",
    "DCIM/Camera/IMG_20260301_090000.JPG": b"navruz",
    "WhatsApp/Images/IMG-20260917-WA0001.jpg": b"toshkent-1",
    "Downloads/IMG_20260917_101500.jpg": b"boshqa-rasm-bir-xil-nom",
    "Downloads/screenshot.png": b"sanasiz",
    "Downloads/hujjat.pdf": b"pdf",
    "DCIM/.thumbnails/t1.jpg": b"kichik",
}


def xesh(yol: Path) -> str:
    return hashlib.sha256(yol.read_bytes()).hexdigest()


def bosh_nom(maqsad: Path, band: set[Path]) -> Path:
    nomzod, i = maqsad, 0
    while nomzod.exists() or nomzod in band:
        i += 1
        nomzod = maqsad.with_stem(f"{maqsad.stem}_{i}")
    return nomzod


def reja_tuz(manba: Path, natija: Path) -> list[tuple[Path, Path, str]]:
    reja, band, korilgan = [], set(), {}
    for fayl in sorted(manba.rglob("*")):
        if not fayl.is_file() or any(q.startswith(".") for q in fayl.relative_to(manba).parts):
            continue
        if fayl.suffix.lower() not in KENGAYTMALAR:
            continue
        mazmun = xesh(fayl)
        if mazmun in korilgan:
            maqsad = bosh_nom(natija / "takrorlar" / fayl.name, band)
            sabab = f"takror: {korilgan[mazmun].relative_to(manba).as_posix()}"
        else:
            korilgan[mazmun] = fayl
            topildi = SANA.search(fayl.name.replace("-", ""))
            katalog = natija / topildi[1] / topildi[2] if topildi else natija / "sanasiz"
            maqsad = bosh_nom(katalog / fayl.name.lower(), band)
            sabab = "sana" if topildi else "sana topilmadi"
        band.add(maqsad)
        reja.append((fayl, maqsad, sabab))
    return reja


def main() -> None:
    with tempfile.TemporaryDirectory() as vaqtinchalik:
        manba, natija = Path(vaqtinchalik) / "telefon", Path(vaqtinchalik) / "Rasmlar"
        for nisbiy, mazmun in NAMUNA.items():
            yol = manba / nisbiy
            yol.parent.mkdir(parents=True, exist_ok=True)
            yol.write_bytes(mazmun)

        print("=== 1. Quruq ishga tushirish (reja) ===")
        reja = reja_tuz(manba, natija)
        for fayl, maqsad, sabab in reja:
            print(f"  {fayl.relative_to(manba).as_posix():42} → {maqsad.relative_to(natija).as_posix():32} [{sabab}]")
        print(f"  hali hech narsa ko'chirilmadi: {not natija.exists()}")

        print("\n=== 2. Bajarish ===")
        for fayl, maqsad, _ in reja:
            maqsad.parent.mkdir(parents=True, exist_ok=True)
            fayl.move(maqsad)
        tuzilma = sorted(p.relative_to(natija).as_posix() for p in natija.rglob("*") if p.is_file())
        for yol in tuzilma:
            print(f"  {yol}")

        print("\n=== 3. Tekshiruvlar ===")
        qolganlar = sorted(p.relative_to(manba).as_posix() for p in manba.rglob("*") if p.is_file())
        print(f"  manbada qolganlar: {qolganlar}")
        print(f"  hech bir fayl yo'qolmadi: {len(tuzilma) + len(qolganlar) == len(NAMUNA)}")
        oylar = Counter(Path(y).parts[0] + "/" + Path(y).parts[1] for y in tuzilma if y[0].isdigit())
        print(f"  oylar bo'yicha: {dict(sorted(oylar.items()))}")
        nomlar = [Path(y).name for y in tuzilma]
        print(f"  to'qnashuv hal qilindi (_1 qo'shimchasi): {any('_1.' in n for n in nomlar)}")


if __name__ == "__main__":
    main()

Natijaning muhim qismi:

text
=== 1. Quruq ishga tushirish (reja) ===
  DCIM/Camera/IMG_20260301_090000.JPG        → 2026/03/img_20260301_090000.jpg  [sana]
  DCIM/Camera/IMG_20260917_101500.jpg        → 2026/09/img_20260917_101500.jpg  [sana]
  DCIM/Camera/IMG_20260917_101501.jpg        → 2026/09/img_20260917_101501.jpg  [sana]
  Downloads/IMG_20260917_101500.jpg          → 2026/09/img_20260917_101500_1.jpg [sana]
  Downloads/screenshot.png                   → sanasiz/screenshot.png           [sana topilmadi]
  WhatsApp/Images/IMG-20260917-WA0001.jpg    → takrorlar/IMG-20260917-WA0001.jpg [takror: DCIM/Camera/IMG_20260917_101500.jpg]
  hali hech narsa ko'chirilmadi: True

=== 2. Bajarish ===
  2026/03/img_20260301_090000.jpg
  2026/09/img_20260917_101500.jpg
  2026/09/img_20260917_101500_1.jpg
  2026/09/img_20260917_101501.jpg
  sanasiz/screenshot.png
  takrorlar/IMG-20260917-WA0001.jpg

=== 3. Tekshiruvlar ===
  manbada qolganlar: ['DCIM/.thumbnails/t1.jpg', 'Downloads/hujjat.pdf']
  hech bir fayl yo'qolmadi: True
  oylar bo'yicha: {'2026/03': 1, '2026/09': 3}
  to'qnashuv hal qilindi (_1 qo'shimchasi): True

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


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

Noto'g'ri fikr To'g'risi
"Path('a') / '/b' — a/b" /b — absolyut qism oldingisini tashlaydi
"Path .. ni avtomatik soddalashtiradi" Yo'q — resolve() kerak
"startswith bilan katalog ichidami tekshirsa bo'ladi" yuklash_eski ham o'tadi; is_relative_to
"'.bashrc' ning kengaytmasi .bashrc" suffix — ''
"with_suffix('json') ishlaydi" Nuqta shart
"rglob yashirin kataloglarni o'tkazib yuboradi" Kiradi
"rename hamma joyda ustiga yozadi" Windows'da yo'q — replace
"read_text() doim UTF-8" Tizim kodlashi — encoding bering

6. Keng tarqalgan xatolar va yechimlari

1. Satrlarni qo'shish

python
yol = katalog + "/" + nom                   # ❌
yol = Path(katalog) / nom                   # ✅

2. Foydalanuvchi nomini tekshirmaslik

python
(YUKLASH / nom).write_bytes(malumot)        # ❌ path traversal
yol = (YUKLASH / nom).resolve()
if not yol.is_relative_to(YUKLASH.resolve()): raise PermissionError   # ✅

3. Kodlashsiz o'qish

python
Path("izoh.txt").read_text()                # ⚠️ Windows'da cp1251
Path("izoh.txt").read_text(encoding="utf-8")   # ✅

4. Kengaytmani satr bilan kesish

python
nom.split(".")[0]                           # ❌ "arxiv.tar.gz", ".bashrc"
Path(nom).stem                              # ✅

5. Skript yonidagi fayl

python
open("config.toml")                         # ❌ joriy katalogga bog'liq
Path(__file__).resolve().parent / "config.toml"   # ✅

6. exists() bilan poyga

python
if not yol.exists(): yol.mkdir()            # ⚠️ TOCTOU
yol.mkdir(exist_ok=True)                    # ✅

7. Saqlashda platformaga bog'liq yo'l

python
baza.saqla(str(yol))                        # ⚠️ Windows'da "\"
baza.saqla(yol.relative_to(ildiz).as_posix())   # ✅

8. Katta katalogda rglob + .git

python
for f in ildiz.rglob("*"): ...              # ⚠️ .git, node_modules
for katalog, ichki, fayllar in ildiz.walk(): ichki[:] = [...]   # ✅

7. Integratsiya — bu bilim qayerda kerak bo'ladi

  • 15.11-dars (o'tilgan): os.walk, os.replace, platforma farqlari
  • 15.13-dars: shutil — arxivlash, disk hajmi
  • 16-qism: fayl formatlari — Path bilan ochish
  • 17-qism: pytest tmp_path fixture'i — Path obyekti
  • 19-qism: paket resurslari, importlib.resources
  • 20.13-dars: fayl yuklash — path traversal
  • 26.2-dars: fayllarni avtomatlashtirish

8. Eng yaxshi amaliyotlar

  1. Yo'llar — Path, satr faqat chegarada.

  2. Skript resurslari — Path(__file__).resolve().parent.

  3. Foydalanuvchi bergan yo'l — resolve() + is_relative_to(); nom — tozalangan.

  4. Matn — doim encoding="utf-8".

  5. Saqlash va almashinuv — as_posix(), nisbiy yo'l.

  6. Qidiruv natijalarini saralang.

  7. Xavfli amallar oldidan quruq ishga tushirish rejasi.

  8. exist_ok, missing_ok — tekshiruv va amal orasidagi poygasiz.


9. Amaliy topshiriq

Vazifa 1: Natijani bashorat qiling

python
from pathlib import PurePosixPath as P
1.  print(P("a/b/c.txt").name)
2.  print(P("a/b/c.tar.gz").suffix)
3.  print(P("a/b/c.tar.gz").stem)
4.  print(P("a") / "/b")
5.  print(P("a/b").parent.parent)
6.  print(P("x.csv").with_suffix(".json"))
7.  print(P("/a/b/c").relative_to("/a"))
8.  print(P("/ab").is_relative_to("/a"))
9.  print(P(".env").suffix == "")
10. print(P("a/../b") == P("b"))
11. print(P("src/app.py").match("*.py"))
12. print(P("a/b/c").parts)
Javoblar
  1. c.txt
  2. .gz
  3. c.tar
  4. /b
  5. .
  6. x.json
  7. b/c
  8. False
  9. True
  10. False
  11. True
  12. ('a', 'b', 'c')

Vazifa 2: Xatolarni tuzating

python
1.  def saqla(nom, malumot):
        yol = "/srv/yuklash/" + nom
        if yol.startswith("/srv/yuklash"):
            open(yol, "wb").write(malumot)

2.  def yangi_nom(fayl):
        return fayl.split(".")[0] + ".json"

3.  def sozlama():
        return open("config.toml").read()

4.  def barcha_py(ildiz):
        return list(Path(ildiz).rglob("*.py"))       # .venv ichidagilar kerak emas

5.  def zaxira(fayl):
        Path(fayl).rename(fayl + ".bak")            # .bak mavjud bo'lishi mumkin
Javoblar
python
1.  ILDIZ = Path("/srv/yuklash")
    def saqla(nom, malumot):
        yol = (ILDIZ / nom_tozala(nom)).resolve()
        if not yol.is_relative_to(ILDIZ.resolve()):
            raise PermissionError(nom)
        yol.write_bytes(malumot)

2.  def yangi_nom(fayl):
        return Path(fayl).with_suffix(".json")

3.  def sozlama():
        return (Path(__file__).resolve().parent / "config.toml").read_text(encoding="utf-8")

4.  def barcha_py(ildiz):
        natija = []
        for katalog, ichki, fayllar in Path(ildiz).walk():
            ichki[:] = [d for d in ichki if d not in {".venv", ".git", "node_modules"}]
            natija += [katalog / f for f in fayllar if f.endswith(".py")]
        return sorted(natija)

5.  def zaxira(fayl):
        p = Path(fayl)
        p.replace(p.with_name(p.name + ".bak"))

Vazifa 3: Takroriy fayllar qidiruvchisi

  1. Katalogdagi barcha fayllarni rekursiv toping
  2. Avval hajm bo'yicha guruhlang (arzon), faqat bir xil hajmlilarning xeshini hisoblang
  3. Takroriy guruhlarni va tejash mumkin bo'lgan joyni ko'rsating
  4. --ochir bayrog'i bilan eng eskisidan boshqalarini o'chirish; standart — quruq rejim
  5. Katta fayllarni bo'laklab xeshlang (butun faylni xotiraga yuklamang)

Vazifa 4: Loyiha tozalovchi

  1. __pycache__, *.pyc, .pytest_cache, build/, dist/, *.egg-info ni toping
  2. .venv va .git ichiga kirmang
  3. Jami bo'shatiladigan hajmni ko'rsating
  4. Quruq rejim va tasdiqlash bilan o'chiring
  5. Chiqish kodi: xato bo'lsa 1

Vazifa 5: Statik sayt yig'uvchi

  1. content/**/*.md fayllarini site/**/*.html ga aylantiring (yo'l tuzilmasi saqlansin)
  2. Faqat o'zgargan fayllarni qayta yig'ing (stat().st_mtime solishtirish)
  3. O'chirilgan manba fayllar uchun eskirgan HTML larni olib tashlang
  4. Nisbiy havolalarni to'g'ri hisoblang (relative_to, ..)

Vazifa 6: Platformalararo yo'llar

  1. Windows'da yozilgan jurnal (C:\Loyiha\src\app.py) va Linux jurnalini (/home/aziz/loyiha/src/app.py) bir xil nisbiy yo'lga keltiring
  2. PureWindowsPath va PurePosixPath bilan, qaysi platformada ishlashidan qat'i nazar
  3. Harfga sezgirlik farqini hisobga olib solishtiring
  4. 20 ta test bilan tekshiring

Vazifa 7: O'ylash

pathlib Python 3.4 da qo'shilgan, lekin ko'p yillar davomida os.path ustun bo'lib qoldi. Python 3.6 da os.PathLike protokoli qo'shilgach vaziyat o'zgardi. Nega "yangi, qulayroq API" darhol qabul qilinmadi va protokol qo'shilishi bunda qanday rol o'ynadi?

Javob

Qisqa javob: Yangi API ekotizim bilan o'zaro ishlamasa qabul qilinmaydi. 3.4–3.5 da Path obyektini ko'p funksiyalarga (open, os.*, shutil, uchinchi tomon kutubxonalar) to'g'ridan-to'g'ri berib bo'lmasdi — har joyda str(p) yozish kerak edi, bu esa qulaylikni yo'qqa chiqarardi. os.PathLike (PEP 519) har qanday funksiya "yo'lga o'xshash" obyektni qabul qilishining standart usulini berdi — va pathlib amalda standartga aylandi.

1. Muammo: 3.4–3.5

python
p = Path("data.csv")
open(p)              # 3.5 da ishlardi, lekin ko'p kutubxonalar
shutil.copy(p, ...)  # va os.* funksiyalar str kutardi
pandas.read_csv(p)   # uchinchi tomonlar — noma'lum

Natija: "Path bilan ishlab, chegaralarda str() ga o'tkazish" — yarim qulaylik.

2. PEP 519: os.PathLike

Qism Nima
__fspath__() Obyektni str yoki bytes yo'lga aylantiruvchi metod
os.fspath(obyekt) Standart o'giruvchi
Standart kutubxona open, os, shutil va boshqalar PathLike qabul qiladigan qilindi
Kutubxona mualliflari Bitta os.fspath() qo'shish kifoya

Protokol bog'liqlikni teskari qildi: kutubxonalar pathlib ni import qilmaydi, faqat protokolni qo'llaydi — istalgan "yo'lga o'xshash" obyekt (masalan, zipfile.Path, bulut yo'llari) ishlaydi.

3. Qabul qilishni sekinlatgan boshqa omillar

  1. Tezlik: dastlabki pathlib os.path dan sezilarli sekin edi; keyingi versiyalarda optimallashtirildi
  2. Odat va mavjud kod: millionlab qator os.path kodi
  3. Python 2 bilan moslik: 2020-yilgacha ko'p loyihalar ikkala versiyani qo'llagan
  4. Kamchiliklar: walk, copy, move yo'q edi — os/shutil ga qaytish kerak bo'lardi (3.12–3.14 da qo'shildi)

4. Umumiy tamoyil

Yangi API muvaffaqiyati uchun Misol
Mavjud ekotizim bilan protokol orqali ulanish PathLike, __index__, __fspath__, buffer protokoli
Eski yo'l bilan birga yashash os.path o'chirilmadi
To'liqlik pathlib.walk/copy/move — os/shutil ga qaytmaslik
Tezlik Yangi API sekin bo'lmasligi kerak

5. Xulosa

  1. Qulay API yetarli emas — u ekotizimga "ulanishi" kerak
  2. Protokol (duck typing, 8.9 va 13.5-darslar) — bog'liqliksiz moslik
  3. pathlib bugun — sukut tanlov, os.path — mavjud kod va maxsus holatlar uchun
  4. O'z kutubxonangizda: aniq turni emas, protokolni (os.PathLike) qabul qiling

Nimani mustahkamlaydi: 2.1–2.7-bo'limlar.


Xulosa

Bu darsda fayl yo'llarini obyekt sifatida boshqarishni o'rgandik.

Eng muhim uch fikr:

  1. Yo'l — obyekt. Path va / operatori yo'lni quradi, name, stem, suffix, suffixes, parent, parts qismlarni beradi, with_name/with_stem/with_suffix esa yangi yo'l yaratadi — satrlarni kesish va birlashtirish xatolarisiz. Saqlash va almashinuvda as_posix() bilan platformadan mustaqil shakl; skript resurslari — Path(__file__).resolve().parent.

  2. Xavfsizlik: resolve() + is_relative_to(). Foydalanuvchi bergan qism absolyut bo'lsa (/etc/passwd), oldingi yo'lni butunlay almashtiradi; .. esa resolve() siz saqlanadi. startswith bilan tekshiruv ../ hujumini ham, yuklash_eski kabi o'xshash prefiksli kataloglarni ham o'tkazib yuboradi. Fayl nomi alohida tozalanadi.

  3. Fayl amallari va qidiruv bir joyda. read_text/write_text (doim encoding="utf-8"), mkdir(parents=True, exist_ok=True), unlink(missing_ok=True), replace; 3.14 da copy va move kataloglar bilan ham ishlaydi. glob/rglob yashirin kataloglarga ham kiradi — kesish kerak bo'lsa walk() 3.12-bob va ichki[:]. Xavfli ommaviy amallardan oldin — quruq ishga tushirish rejasi.

Keyingi darsda shutil moduliga o'tamiz: nusxa ko'chirish tafsilotlari, arxivlar, disk hajmi va dasturlarni topish.

Ulashish:Telegram'da

Izohlar (0)

Izoh yozish uchun kiring.

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