• בלוג
  • עוד דוגמה ל os.walk ב Python

עוד דוגמה ל os.walk ב Python

16/12/2021

הפונקציה os.walk מספקת דרך קלה לעבור על כל עץ הקבצים והתיקיות החל מנקודת התחלה מסוימת. בדוגמה בפוסט היום אשתמש בה כדי לבנות אוביקט JSON שמתאים לעץ קבצים ותיקיות בפורמט מסוים.

1. מה אנחנו בונים

נתון עץ קבצים ותיקיות שנראה כך:

.
├── a
│   ├── b
│   │   ├── 00
│   │   │   └── 11
│   │   │       ├── 22
│   │   │       ├── 33
│   │   │       └── 44
│   │   ├── c
│   │   │   ├── c1.txt
│   │   │   ├── c2.txt
│   │   │   └── d
│   │   └── ff
│   │       └── gg
│   ├── f1.txt
│   └── f2.txt
└── build_json.py

ואני רוצה להפוך אותו לאוביקט JSON שנראה כך:

[
  {
    "type": "f",
    "title": "build_json.py",
    "key": "build_json.py",
    "children": []
  },
  {
    "type": "d",
    "key": "a",
    "title": "a",
    "children": [
      {
        "type": "f",
        "title": "f1.txt",
        "key": "a/f1.txt",
        "children": []
      },
      {
        "type": "f",
        "title": "f2.txt",
        "key": "a/f2.txt",
        "children": []
      },
      {
        "type": "d",
        "key": "a/b",
        "title": "b",
        "children": [
          {
            "type": "d",
            "key": "a/b/00",
            "title": "00",
            "children": [
              {
                "type": "d",
                "key": "a/b/00/11",
                "title": "11",
                "children": [
                  {
                    "type": "d",
                    "key": "a/b/00/11/33",
                    "title": "33",
                    "children": []
                  },
                  {
                    "type": "d",
                    "key": "a/b/00/11/44",
                    "title": "44",
                    "children": []
                  },
                  {
                    "type": "d",
                    "key": "a/b/00/11/22",
                    "title": "22",
                    "children": []
                  }
                ]
              }
            ]
          },
          {
            "type": "d",
            "key": "a/b/ff",
            "title": "ff",
            "children": [
              {
                "type": "d",
                "key": "a/b/ff/gg",
                "title": "gg",
                "children": []
              }
            ]
          },
          {
            "type": "d",
            "key": "a/b/c",
            "title": "c",
            "children": [
              {
                "type": "f",
                "title": "c1.txt",
                "key": "a/b/c/c1.txt",
                "children": []
              },
              {
                "type": "f",
                "title": "c2.txt",
                "key": "a/b/c/c2.txt",
                "children": []
              },
              {
                "type": "d",
                "key": "a/b/c/d",
                "title": "d",
                "children": []
              }
            ]
          }
        ]
      }
    ]
  }
]

כדי שיתאים לקומפוננטה שמציגה עץ בצורה גרפית על המסך. המבנה הולך ככה:

  1. האוביקט הראשי הוא מערך של אוביקטים

  2. כל אוביקט במערך מכיל 4 שדות: שדה type שיכול להיות d או f כדי לציין אם זה תיקיה או קובץ; שדה key שמכיל את הנתיב המלא לדבר (והוא ייחודי בכל העץ); שדה title שמכיל את שם הדבר ושדה בשם children שמכיל מערך של אוביקטים באותו מבנה בדיוק.

  3. אם האוביקט הוא קובץ אז מערך הילדים שלו ריק.

  4. אם האוביקט הוא תיקיה אז מערך הילדים שלו מייצג את הקבצים והתיקיות שנמצאים בתוך התיקיה.

מוזמנים לנסות לבנות את המנגנון ואחרי זה להמשיך לקוד הדוגמה שלי.

2. התוכנית

עם os.walk אנחנו יכולים לכתוב כל תוכנית שעוברת על עץ הקבצים בצורה פשוטה ובלי ללכת לאיבוד ביער. הפונקציה מחזירה איטרטור שבכל איטרציה בוחר תיקיה אחרת ומחזיר את רשימת הקבצים והתיקיות באותה תיקיה שהוא בחר. האיטרציה היא לפי הסדר האמיתי של הקבצים והתיקיות ולכן נוח להשתמש בה כדי לבנות את אוביקט ה JSON.

זה היה הפיתרון שלי:

import os, json

def add_node(doc, path, key, pos=''):
    if len(path) == 0:
        return doc

    try:
        next_child = next(child for child in doc if '/'+child["key"] == pos+'/'+path[0])
    except StopIteration:
        doc.append({ "type": "d", 'key': key, 'title': path[-1], 'children': [] })
        next_child = doc[-1]

    return add_node(next_child["children"], path[1:], key, pos+'/'+path[0])


doc = []

for root, dirs, files in os.walk('.'):
    key = root[1:].split('/')[1:]
    node = add_node(doc, key, '/'.join(key))
    prefix = '/'.join(key) + '/' if len(key) > 0 else ''
    node.extend([{ "type": "f", "title": f, "key": f"{prefix}{f}", "children": [] } for i, f in enumerate(files)])

print(json.dumps(doc))

נתחיל מהחלק השני - הפונקציה os.walk:

  1. מפעילים את os.walk בתוך for כדי לרוץ על כל הקבצים והתיקיות בצורה רקורביסית בעץ התיקיות.

  2. כל כניסה לגוף הלולאה אומרת שנכנסתי לתיקיה חדשה. אני מפעיל את הפונקציה add_node כדי להוסיף צומת חדש בעץ בדיוק במקום שמתאים לאותה תיקיה. פונקציית add_node מחזירה את מערך הילדים של התיקיה שהיא הרגע הוסיפה.

  3. אחרי זה אני משתמש במשתנה files שחוזר מאותו איטרטור כדי להוסיף גם את כל הקבצים ולמלא באמצעותם את מערך הילדים.

הפונקציה add_node מטיילת בצורה רקורביסית ב Dictionary לפי המבנה של מילון הקבצים ומוסיפה תיקיה חדשה למקום הנכון. היא משתמשת ב key של כל אוביקט כדי לדעת לאיזה נתיבים לנווט בתוך ה Dictionary.