पाइथन में सूची निर्देशिका वृक्ष संरचना?
हम आमतौर पर सिर्फ जीएनयू के पेड़ का उपयोग करना पसंद करते हैं, लेकिन हमारे पास हमेशा tree
हर प्रणाली नहीं होती है, और कभी-कभी पायथन 3 भी उपलब्ध होता है। यहां एक अच्छा जवाब आसानी से कॉपी-पेस्ट किया जा सकता है और जीएनयू को tree
एक आवश्यकता नहीं बना सकता है ।
tree
उत्पादन इस तरह दिखता है:
$ tree
.
├── package
│ ├── __init__.py
│ ├── __main__.py
│ ├── subpackage
│ │ ├── __init__.py
│ │ ├── __main__.py
│ │ └── module.py
│ └── subpackage2
│ ├── __init__.py
│ ├── __main__.py
│ └── module2.py
└── package2
└── __init__.py
4 directories, 9 files
मैंने अपनी निर्देशिका में उपरोक्त निर्देशिका संरचना को एक निर्देशिका कॉल के तहत बनाया है pyscratch
।
मैं यहाँ अन्य उत्तरों को भी देखता हूँ जो उस प्रकार के आउटपुट को प्राप्त करते हैं, लेकिन मुझे लगता है कि हम सरल, अधिक आधुनिक कोड और आलसियों के मूल्यांकन के तरीकों से बेहतर कर सकते हैं।
अजगर में पेड़
शुरुआत करने के लिए, आइए एक उदाहरण का उपयोग करें
- पायथन 3 का उपयोग करता है
Path
ऑब्जेक्ट
yield
और का उपयोग करता हैyield from
अभिव्यक्ति (जो एक जनरेटर फ़ंक्शन बनाता है)
- सुरुचिपूर्ण सादगी के लिए पुनरावर्तन का उपयोग करता है
- अतिरिक्त स्पष्टता के लिए टिप्पणियों और कुछ प्रकार के एनोटेशन का उपयोग करता है
from pathlib import Path
# prefix components:
space = ' '
branch = '│ '
# pointers:
tee = '├── '
last = '└── '
def tree(dir_path: Path, prefix: str=''):
"""A recursive generator, given a directory Path object
will yield a visual tree structure line by line
with each line prefixed by the same characters
"""
contents = list(dir_path.iterdir())
# contents each get pointers that are ├── with a final └── :
pointers = [tee] * (len(contents) - 1) + [last]
for pointer, path in zip(pointers, contents):
yield prefix + pointer + path.name
if path.is_dir(): # extend the prefix and recurse:
extension = branch if pointer == tee else space
# i.e. space because last, └── , above so no more |
yield from tree(path, prefix=prefix+extension)
और अब:
for line in tree(Path.home() / 'pyscratch'):
print(line)
प्रिंट:
├── package
│ ├── __init__.py
│ ├── __main__.py
│ ├── subpackage
│ │ ├── __init__.py
│ │ ├── __main__.py
│ │ └── module.py
│ └── subpackage2
│ ├── __init__.py
│ ├── __main__.py
│ └── module2.py
└── package2
└── __init__.py
हमें प्रत्येक निर्देशिका को सूची में शामिल करने की आवश्यकता है क्योंकि हमें यह जानने की आवश्यकता है कि यह कितना लंबा है, लेकिन बाद में हम सूची को फेंक देते हैं। गहरी और व्यापक पुनरावृत्ति के लिए यह पर्याप्त आलसी होना चाहिए।
उपरोक्त कोड, टिप्पणियों के साथ, यह पूरी तरह से समझने के लिए पर्याप्त होना चाहिए कि हम यहाँ क्या कर रहे हैं, लेकिन बेझिझक एक डिबगर के माध्यम से इसके माध्यम से कदम उठाने के लिए स्वतंत्र महसूस करें यदि आपको इसकी आवश्यकता है।
अधिक सुविधाएं
अब GNU tree
हमें कुछ उपयोगी सुविधाएँ प्रदान करता है जिन्हें मैं इस फ़ंक्शन के साथ रखना चाहता हूँ:
- विषय निर्देशिका नाम को पहले प्रिंट करता है (स्वचालित रूप से, हमारा नहीं है)
- की गिनती छापता है
n directories, m files
- प्रत्यावर्तन को सीमित करने का विकल्प,
-L level
- विकल्प सिर्फ निर्देशिका तक सीमित करने के लिए,
-d
इसके अलावा, जब कोई विशाल पेड़ होता है, तो islice
पाठ के साथ अपने दुभाषिया को लॉक करने से बचने के लिए पुनरावृत्ति (जैसे के साथ ) को सीमित करना उपयोगी होता है , क्योंकि कुछ बिंदु पर आउटपुट उपयोगी होने के लिए बहुत अधिक क्रिया हो जाता है। हम इसे डिफ़ॉल्ट रूप से उच्चतर बना सकते हैं - कह सकते हैं 1000
।
तो चलो पिछली टिप्पणियों को हटा दें और इस कार्यक्षमता को भरें:
from pathlib import Path
from itertools import islice
space = ' '
branch = '│ '
tee = '├── '
last = '└── '
def tree(dir_path: Path, level: int=-1, limit_to_directories: bool=False,
length_limit: int=1000):
"""Given a directory Path object print a visual tree structure"""
dir_path = Path(dir_path) # accept string coerceable to Path
files = 0
directories = 0
def inner(dir_path: Path, prefix: str='', level=-1):
nonlocal files, directories
if not level:
return # 0, stop iterating
if limit_to_directories:
contents = [d for d in dir_path.iterdir() if d.is_dir()]
else:
contents = list(dir_path.iterdir())
pointers = [tee] * (len(contents) - 1) + [last]
for pointer, path in zip(pointers, contents):
if path.is_dir():
yield prefix + pointer + path.name
directories += 1
extension = branch if pointer == tee else space
yield from inner(path, prefix=prefix+extension, level=level-1)
elif not limit_to_directories:
yield prefix + pointer + path.name
files += 1
print(dir_path.name)
iterator = inner(dir_path, level=level)
for line in islice(iterator, length_limit):
print(line)
if next(iterator, None):
print(f'... length_limit, {length_limit}, reached, counted:')
print(f'\n{directories} directories' + (f', {files} files' if files else ''))
और अब हम उसी प्रकार का आउटपुट प्राप्त कर सकते हैं जैसे tree
:
tree(Path.home() / 'pyscratch')
प्रिंट:
pyscratch
├── package
│ ├── __init__.py
│ ├── __main__.py
│ ├── subpackage
│ │ ├── __init__.py
│ │ ├── __main__.py
│ │ └── module.py
│ └── subpackage2
│ ├── __init__.py
│ ├── __main__.py
│ └── module2.py
└── package2
└── __init__.py
4 directories, 9 files
और हम स्तरों तक सीमित कर सकते हैं:
tree(Path.home() / 'pyscratch', level=2)
प्रिंट:
pyscratch
├── package
│ ├── __init__.py
│ ├── __main__.py
│ ├── subpackage
│ └── subpackage2
└── package2
└── __init__.py
4 directories, 3 files
और हम आउटपुट को डायरेक्टरी में सीमित कर सकते हैं:
tree(Path.home() / 'pyscratch', level=2, limit_to_directories=True)
प्रिंट:
pyscratch
├── package
│ ├── subpackage
│ └── subpackage2
└── package2
4 directories
पूर्वप्रभावी
पूर्वव्यापी में, हम path.glob
मिलान के लिए उपयोग कर सकते थे । हम शायद path.rglob
पुनरावर्ती ग्लोबिंग के लिए भी उपयोग कर सकते हैं , लेकिन इसके लिए फिर से लिखना होगा। हम भी इस्तेमाल कर सकते हैंitertools.tee
निर्देशिका सामग्री की एक सूची को भौतिक बजाय हैं, लेकिन इससे नकारात्मक ट्रेडऑफ़ हो सकते हैं और संभवतः कोड को और भी अधिक जटिल बना देगा।
टिप्पणियों का स्वागत है!