Как Python способ обходить дерево каталогов?
Я чувствую, что присвоение файлов и папок и выполнение части += [item] немного странно. Какие-либо предложения? Я использую Python 3.2
from os import *
from os.path import *
def dir_contents(path):
contents = listdir(path)
files = []
folders = []
for i, item in enumerate(contents):
if isfile(contents[i]):
files += [item]
elif isdir(contents[i]):
folders += [item]
return files, folders
17 ответов
Посмотрите на os.walk
функция, которая возвращает путь вместе с каталогами и файлами, которые она содержит. Это должно значительно сократить ваше решение.
os.walk
и
os.scandir
являются отличными вариантами, однако я все больше и больше использую pathlib, а с pathlib вы можете использовать
.glob()
метод:
root_directory = Path(".")
for path_object in root_directory.glob('**/*'):
if path_object.is_file():
print(f"hi, I'm a file: {path_object}")
elif path_object.is_dir():
print(f"hi, I'm a dir: {path_object}")
Для тех, кто ищет решение, использующее
pathlib
(python >= 3.4
)
from pathlib import Path
def walk(path):
for p in Path(path).iterdir():
if p.is_dir():
yield from walk(p)
continue
yield p.resolve()
# recursively traverse all files from current directory
for p in walk(Path('.')):
print(p)
# the function returns a generator so if you need a list you need to build one
all_files = list(walk(Path('.')))
Однако, как упоминалось выше, это не сохраняет нисходящий порядок, задаваемый
os.walk
С
Python >= 3.4
существует метод генератора
Path.rglob
. Итак, чтобы обработать все пути под
some/starting/path
просто сделайте что-нибудь вроде
from pathlib import Path
path = Path('some/starting/path')
for subpath in path.rglob('*'):
# do something with subpath
Чтобы получить все подпути в списке, выполните
list(path.rglob('*'))
. Чтобы получить только файлы с
sql
расширение, сделать
list(path.rglob('*.sql'))
.
Если вы хотите рекурсивно перебирать все файлы, включая все файлы в подпапках, я считаю, что это лучший способ.
import os
def get_files(input):
for fd, subfds, fns in os.walk(input):
for fn in fns:
yield os.path.join(fd, fn)
## now this will print all full paths
for fn in get_files(fd):
print(fn)
Другое решение, как пройтись по дереву каталогов, используяpathlib
модуль:
from pathlib import Path
for directory in Path('.').glob('**'):
for item in directory.iterdir():
print(item)
Шаблон**
соответствует текущему каталогу и всем подкаталогам рекурсивно, а методiterdir
затем перебирает содержимое каждого каталога. Полезно, когда вам нужно больше контроля при обходе дерева каталогов.
Начиная с Python 3.4 появился новый модуль pathlib. Таким образом, чтобы получить все каталоги и файлы, можно сделать:
from pathlib import Path
dirs = [str(item) for item in Path(path).iterdir() if item.is_dir()]
files = [str(item) for item in Path(path).iterdir() if item.is_file()]
Действительно используя
items += [item]
это плохо по многим причинам...
append
метод был сделан именно для этого (добавление одного элемента в конец списка)Вы создаете временный список из одного элемента, чтобы просто выбросить его. Хотя грубая скорость не должна вас беспокоить при использовании Python (в противном случае вы используете не тот язык), тем не менее бесполезная потеря скорости кажется неправильной.
Вы используете небольшую асимметрию языка Python... для записи объектов списка
a += b
не то же самое, что писатьa = a + b
потому что первый изменяет объект на месте, а второй вместо этого выделяет новый список, и это может иметь другую семантику, если объектa
также достижимо с помощью других способов. В вашем конкретном коде это не так, но это может стать проблемой позже, когда кто-то другой (или вы сами через несколько лет, то же самое) должны будут изменить код. У Python даже есть методextend
с менее тонким синтаксисом, который специально создан для обработки случая, когда вы хотите изменить на месте объект списка, добавив в конце элементы другого списка.
Также, как другие заметили, кажется, что ваш код пытается сделать то, что os.walk
уже делает...
def dir_contents(path):
files,folders = [],[]
for p in listdir(path):
if isfile(p): files.append(p)
else: folders.append(p)
return files, folders
Вместо встроенных os.walk и os.path.walk я использую что-то полученное из этого фрагмента кода, который я нашел в другом месте:
http://code.google.com/p/mylibs/source/browse/lib/Python/MyPyLib/DirectoryStatWalker.py
Я не буду это повторять, но он рекурсивно просматривает каталоги, довольно эффективен и легко читается.
Вот версия, которая использует и возвращает древовидную структуру. С использованиемos.scandir
вернетсяos.DirEntry
объекты, которые содержат информацию об объектах пути в памяти, что позволяет запрашивать информацию об элементах без вызовов файловой системы.
import os
def treedir(path):
files = []
folders = {}
for entry in os.scandir(path):
if entry.is_file():
files.append(entry)
elif entry.is_dir():
folders[entry.name] = treedir(entry)
result = {}
if files:
result['files'] = files
if folders:
result['folders'] = folders
return result
Мне нравится структура результата, но я предпочитаюpathlib
общий. Поэтому мое ленивое решение — просто создатьPath
с каждого товара, возвращенногоos.walk()
.
import os
import pathlib
def walk(path='bin'):
for root, dirs, files in os.walk(path):
root = pathlib.Path(root)
dirs = [root / d for d in dirs]
files = [root / f for f in files]
yield root, dirs, files
import pathlib
import time
def prune_empty_dirs(path: pathlib.Path):
for current_path in list(path.rglob("*"))[::-1]:
if current_path.is_dir() and not any(current_path.iterdir()):
current_path.rmdir()
while current_path.exists():
time.sleep(0.1)
Я еще не проверял это подробно, но я верю, что это расширит os.walk
генератор, присоедините dirnames ко всем путям файлов и сгладьте результирующий список; Чтобы получить прямой список конкретных файлов в вашем пути поиска.
import itertools
import os
def find(input_path):
return itertools.chain(
*list(
list(os.path.join(dirname, fname) for fname in files)
for dirname, _, files in os.walk(input_path)
)
)
В поисках той же информации я нашел этот вопрос.
Я публикую здесь самый маленький и понятный код, который я нашел по адресу http://www.pythoncentral.io/how-to-traverse-a-directory-tree-in-python-guide-to-os-walk/ (вместо просто разместив URL, в случае ссылки гниль).
На странице есть некоторая полезная информация, а также указывает на несколько других соответствующих страниц.
# Import the os module, for the os.walk function
import os
# Set the directory you want to start from
rootDir = '.'
for dirName, subdirList, fileList in os.walk(rootDir):
print('Found directory: %s' % dirName)
for fname in fileList:
print('\t%s' % fname)
Скопируйте и вставьте код для тех, кто хочет глубоко просмотреть все подкаталоги, мы можем использоватьpython recursion
вызов:
import os
def deep_walk(mypath):
file_list = []
for root, dirs, files in os.walk(mypath):
for file in files:
file_list.append(os.path.join(root, file))
for dir in dirs:
if os.path.isdir(dir):
mypath = os.path.join(root, dir)
deep_walk(mypath)
for f in file_list:
print(f)
def main():
mypath="/tmp"
deep_walk(mypath)
if __name__ == '__main__':
main()