แสดงรายการโครงสร้างไดเร็กทอรีใน Python?
เรามักจะชอบใช้ GNU tree แต่เราไม่ได้มีtree
ในทุกระบบเสมอไปและบางครั้ง Python 3 ก็มีให้ใช้งาน คำตอบที่ดีสามารถคัดลอกวางได้อย่างง่ายดายและไม่ทำให้ GNU 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
ฉันสร้างโครงสร้างไดเรกทอรีดังกล่าวข้างต้นในไดเรกทอรีบ้านของฉันภายใต้ผมโทรไดเรกทอรี
ฉันยังเห็นคำตอบอื่น ๆ ที่นี่ซึ่งเป็นแนวทางของผลลัพธ์แบบนั้น แต่ฉันคิดว่าเราทำได้ดีกว่าด้วยโค้ดที่ง่ายกว่าทันสมัยกว่าและประเมินวิธีการอย่างเฉื่อยชา
ต้นไม้ใน Python
เริ่มต้นด้วยการใช้ตัวอย่างที่
- ใช้
Path
วัตถุPython 3
- ใช้นิพจน์
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
แทนการสร้างรายการเนื้อหาไดเร็กทอรี แต่อาจมีการแลกเปลี่ยนเชิงลบและอาจทำให้โค้ดซับซ้อนยิ่งขึ้น
ยินดีต้อนรับความคิดเห็น!