Liệt kê cấu trúc cây thư mục trong Python?
Chúng tôi thường thích chỉ sử dụng GNU tree, nhưng không phải lúc nào chúng tôi cũng có tree
trên mọi hệ thống và đôi khi Python 3 cũng có sẵn. Một câu trả lời hay ở đây có thể dễ dàng được sao chép và không làm cho GNU trở thành tree
một yêu cầu.
tree
đầu ra của trông như thế này:
$ 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
Tôi đã tạo cấu trúc thư mục trên trong thư mục chính của mình trong một thư mục mà tôi gọi pyscratch
.
Tôi cũng thấy các câu trả lời khác ở đây tiếp cận loại đầu ra đó, nhưng tôi nghĩ chúng ta có thể làm tốt hơn, với mã đơn giản hơn, hiện đại hơn và cách tiếp cận đánh giá lười biếng.
Cây trong Python
Để bắt đầu, hãy sử dụng một ví dụ
- sử dụng
Path
đối tượng Python 3
- sử dụng các biểu thức
yield
và yield from
(tạo một hàm trình tạo)
- sử dụng đệ quy cho sự đơn giản thanh lịch
- sử dụng nhận xét và một số loại chú thích để thêm rõ ràng
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)
và bây giờ:
for line in tree(Path.home() / 'pyscratch'):
print(line)
bản in:
├── package
│ ├── __init__.py
│ ├── __main__.py
│ ├── subpackage
│ │ ├── __init__.py
│ │ ├── __main__.py
│ │ └── module.py
│ └── subpackage2
│ ├── __init__.py
│ ├── __main__.py
│ └── module2.py
└── package2
└── __init__.py
Chúng ta cần cụ thể hóa từng thư mục thành một danh sách vì chúng ta cần biết nó dài bao lâu, nhưng sau đó chúng ta sẽ vứt bỏ danh sách. Đối với đệ quy sâu và rộng, điều này sẽ đủ lười biếng.
Đoạn mã ở trên, với các nhận xét, sẽ đủ để hiểu đầy đủ những gì chúng tôi đang làm ở đây, nhưng vui lòng xem qua nó với trình gỡ lỗi để kiểm tra nó tốt hơn nếu bạn cần.
Các tính năng khác
Bây giờ GNU tree
cung cấp cho chúng ta một số tính năng hữu ích mà tôi muốn có với chức năng này:
- in tên thư mục chủ đề trước (tự động làm như vậy, của chúng tôi thì không)
- in số lượng
n directories, m files
- tùy chọn để giới hạn đệ quy,
-L level
- tùy chọn để giới hạn chỉ trong các thư mục,
-d
Ngoài ra, khi có một cây lớn, bạn nên hạn chế lặp lại (ví dụ: với islice
) để tránh khóa trình thông dịch của bạn với văn bản, vì tại một số điểm, kết quả đầu ra trở nên quá dài dòng không hữu ích. Theo mặc định, chúng tôi có thể đặt mức này cao tùy ý - giả sử 1000
.
Vì vậy, hãy xóa các nhận xét trước đó và điền vào chức năng này:
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 ''))
Và bây giờ chúng ta có thể nhận được cùng một loại đầu ra như tree
:
tree(Path.home() / 'pyscratch')
bản in:
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
Và chúng tôi có thể hạn chế ở các cấp độ:
tree(Path.home() / 'pyscratch', level=2)
bản in:
pyscratch
├── package
│ ├── __init__.py
│ ├── __main__.py
│ ├── subpackage
│ └── subpackage2
└── package2
└── __init__.py
4 directories, 3 files
Và chúng tôi có thể giới hạn đầu ra cho các thư mục:
tree(Path.home() / 'pyscratch', level=2, limit_to_directories=True)
bản in:
pyscratch
├── package
│ ├── subpackage
│ └── subpackage2
└── package2
4 directories
Hồi tưởng
Nhìn lại, chúng tôi có thể đã sử dụng path.glob
để đối sánh. Có lẽ chúng ta cũng có thể sử dụng path.rglob
cho việc đánh bóng đệ quy, nhưng điều đó sẽ yêu cầu viết lại. Chúng tôi cũng có thể sử dụng itertools.tee
thay vì hiện thực hóa danh sách nội dung thư mục, nhưng điều đó có thể có những đánh đổi tiêu cực và có thể sẽ làm cho mã trở nên phức tạp hơn.
Bình luận được chào đón!