Làm cách nào để có được tất cả các thư mục con ngay lập tức trong Python


150

Tôi đang cố gắng viết một tập lệnh Python đơn giản sẽ sao chép index.tpl sang index.html trong tất cả các thư mục con (với một vài ngoại lệ).

Tôi đang bị sa lầy bằng cách cố gắng để có được danh sách các thư mục con.


11
Bạn có thể thấy rằng câu trả lời được chấp nhận tại câu hỏi SO trước đó đã giải quyết vấn đề: stackoverflow.com/questions/120656/directory-listing-in-python
Jarret Hardie

Câu trả lời:


31

Tôi đã thực hiện một số thử nghiệm tốc độ trên các chức năng khác nhau để trả lại đường dẫn đầy đủ cho tất cả các thư mục con hiện tại.

tl; dr: Luôn sử dụng scandir:

list_subfolders_with_paths = [f.path for f in os.scandir(path) if f.is_dir()]

Phần thưởng: Với scandirbạn cũng có thể chỉ đơn giản là nhận tên thư mục bằng cách sử dụng f.namethay vì f.path.

Điều này (cũng như tất cả các chức năng khác bên dưới) sẽ không sử dụng phân loại tự nhiên . Điều này có nghĩa là kết quả sẽ được sắp xếp như sau: 1, 10, 2. Để có được sự sắp xếp tự nhiên (1, 2, 10), vui lòng xem tại https://stackoverflow.com/a/48030307/2441026




Kết quả : scandirlà: nhanh hơn 3 lần so với walk, nhanh hơn 32 lần so với listdir(với bộ lọc), Pathlibnhanh hơn listdir35 lần so với và nhanh hơn 36 lần so với và nhanh hơn 37 lần (!) glob.

Scandir:           0.977
Walk:              3.011
Listdir (filter): 31.288
Pathlib:          34.075
Listdir:          35.501
Glob:             36.277

Đã thử nghiệm với W7x64, Python 3.8.1. Thư mục với 440 thư mục con.
Trong trường hợp bạn tự hỏi liệu listdircó thể tăng tốc bằng cách không thực hiện os.path.join () hai lần, có, nhưng về cơ bản sự khác biệt là không có.

Mã số:

import os
import pathlib
import timeit
import glob

path = r"<example_path>"



def a():
    list_subfolders_with_paths = [f.path for f in os.scandir(path) if f.is_dir()]
    # print(len(list_subfolders_with_paths))


def b():
    list_subfolders_with_paths = [os.path.join(path, f) for f in os.listdir(path) if os.path.isdir(os.path.join(path, f))]
    # print(len(list_subfolders_with_paths))


def c():
    list_subfolders_with_paths = []
    for root, dirs, files in os.walk(path):
        for dir in dirs:
            list_subfolders_with_paths.append( os.path.join(root, dir) )
        break
    # print(len(list_subfolders_with_paths))


def d():
    list_subfolders_with_paths = glob.glob(path + '/*/')
    # print(len(list_subfolders_with_paths))


def e():
    list_subfolders_with_paths = list(filter(os.path.isdir, [os.path.join(path, f) for f in os.listdir(path)]))
    # print(len(list(list_subfolders_with_paths)))


def f():
    p = pathlib.Path(path)
    list_subfolders_with_paths = [x for x in p.iterdir() if x.is_dir()]
    # print(len(list_subfolders_with_paths))



print(f"Scandir:          {timeit.timeit(a, number=1000):.3f}")
print(f"Listdir:          {timeit.timeit(b, number=1000):.3f}")
print(f"Walk:             {timeit.timeit(c, number=1000):.3f}")
print(f"Glob:             {timeit.timeit(d, number=1000):.3f}")
print(f"Listdir (filter): {timeit.timeit(e, number=1000):.3f}")
print(f"Pathlib:          {timeit.timeit(f, number=1000):.3f}")

1
Chỉ muốn cảm ơn bạn, đã thực sự tìm kiếm này. Phân tích tuyệt vời.
Cing

224
import os
def get_immediate_subdirectories(a_dir):
    return [name for name in os.listdir(a_dir)
            if os.path.isdir(os.path.join(a_dir, name))]

76

Tại sao không có ai nhắc đến glob? globcho phép bạn sử dụng mở rộng tên đường dẫn kiểu Unix và là chức năng của tôi cho hầu hết mọi thứ cần tìm nhiều hơn một tên đường dẫn. Nó làm cho nó rất dễ dàng:

from glob import glob
paths = glob('*/')

Lưu ý rằng globsẽ trả về thư mục với dấu gạch chéo cuối cùng (như unix sẽ) trong khi hầu hết các pathgiải pháp dựa trên sẽ bỏ qua dấu gạch chéo cuối cùng.


3
Giải pháp tốt, đơn giản và hiệu quả. Đối với những người không muốn dấu gạch chéo cuối cùng, anh ta có thể sử dụng nó paths = [ p.replace('/', '') for p in glob('*/') ].
Evan Hu

5
Có thể an toàn hơn khi chỉ cần cắt ký tự cuối cùng [p[:-1] for p in paths], vì phương thức thay thế đó cũng sẽ thay thế bất kỳ dấu gạch chéo nào được thoát trong tên tệp (không phải là ký tự phổ biến).
ari

3
Thậm chí an toàn hơn, sử dụng dải ('/') để xóa dấu gạch chéo. Cách này đảm bảo rằng bạn không cắt bỏ bất kỳ ký tự nào không chuyển tiếp dấu gạch chéo
Eliezer Miron

8
Bằng cách xây dựng, bạn được đảm bảo có một dấu gạch chéo (vì vậy nó không an toàn hơn), nhưng tôi nghĩ nó dễ đọc hơn. Tuy nhiên, bạn chắc chắn muốn sử dụng rstripthay vì vì stripsau này sẽ biến bất kỳ đường dẫn đủ điều kiện nào thành đường dẫn tương đối.
ari

7
bổ sung cho nhận xét @ari cho những người mới chơi python như I: strip('/')sẽ xóa cả bắt đầu và theo dõi '/', rstrip('/')sẽ chỉ xóa một dấu vết
Titou

35

Kiểm tra " Lấy danh sách tất cả các thư mục con trong thư mục hiện tại ".

Đây là phiên bản Python 3:

import os

dir_list = next(os.walk('.'))[1]

print(dir_list)

2
Cực kì thông minh. Mặc dù hiệu quả không thành vấn đề ( ... hoàn toàn có ), tôi tò mò không biết liệu biểu thức trình tạo dựa trên toàn cầu (s.rstrip("/") for s in glob(parent_dir+"*/"))này có hiệu quả hơn về thời gian hay không. Sự nghi ngờ trực giác của tôi là một giải pháp stat()dựa trên cơ sở nên nhanh hơn rất nhiều so với phương pháp tạo hình vỏ sò. Đáng buồn thay, tôi thiếu ý chí và thực sự tìm ra. os.walk()timeit
Cecil Curry

3
Lưu ý rằng điều này trả về các tên thư mục con mà không có tên thư mục mẹ được đặt trước nó.
Paul Chernoch

19
import os, os.path

Để có được (toàn đường dẫn) thư mục con ngay lập tức trong một thư mục:

def SubDirPath (d):
    return filter(os.path.isdir, [os.path.join(d,f) for f in os.listdir(d)])

Để có được thư mục con mới nhất (mới nhất):

def LatestDirectory (d):
    return max(SubDirPath(d), key=os.path.getmtime)

Để có được một danh sách , chỉ cần thêm list( filter(...) ).
dùng136036

12

os.walk là bạn của bạn trong tình huống này.

Trực tiếp từ tài liệu:

walk () tạo tên tệp trong cây thư mục, bằng cách đi cây từ trên xuống hoặc từ dưới lên. Đối với mỗi thư mục trong cây gốc ở thư mục trên cùng (bao gồm cả đỉnh), nó mang lại một bộ 3 (dirpath, dirnames, tên tệp).


1
Chỉ cần lưu ý rằng nếu bạn chỉ muốn các thư mục con cấp đầu tiên thì hãy thoát khỏi vòng lặp os.walk sau tập giá trị trả về đầu tiên.
yoyo

11

Phương pháp này độc đáo làm tất cả trong một lần.

from glob import glob
subd = [s.rstrip("/") for s in glob(parent_dir+"*/")]

7

Sử dụng mô-đun FilePath của Twisted:

from twisted.python.filepath import FilePath

def subdirs(pathObj):
    for subpath in pathObj.walk():
        if subpath.isdir():
            yield subpath

if __name__ == '__main__':
    for subdir in subdirs(FilePath(".")):
        print "Subdirectory:", subdir

Vì một số người bình luận đã hỏi những lợi thế của việc sử dụng các thư viện của Twisted cho việc này là gì, tôi sẽ vượt xa câu hỏi ban đầu ở đây.


một số tài liệu được cải tiến trong một nhánh giải thích các ưu điểm của FilePath; bạn có thể muốn đọc nó

Cụ thể hơn trong ví dụ này: không giống như phiên bản thư viện tiêu chuẩn, chức năng này có thể được thực hiện mà không cần nhập . Hàm "subirs" hoàn toàn chung chung, ở chỗ nó hoạt động không có gì ngoài đối số của nó. Để sao chép và di chuyển các tệp bằng thư viện chuẩn, bạn cần phụ thuộc vào " open" dựng sẵn listdir"," có lẽ " isdir" hoặc " os.walk" hoặc " shutil.copy". Có lẽ " os.path.join" cũng vậy. Không đề cập đến thực tế là bạn cần một chuỗi thông qua một đối số để xác định tệp thực tế. Chúng ta hãy xem việc thực hiện đầy đủ sẽ sao chép "index.tpl" của mỗi thư mục thành "index.html":

def copyTemplates(topdir):
    for subdir in subdirs(topdir):
        tpl = subdir.child("index.tpl")
        if tpl.exists():
            tpl.copyTo(subdir.child("index.html"))

Hàm "subirs" ở trên có thể hoạt động trên mọi FilePathđối tượng giống như. Có nghĩa là, trong số những thứ khác, ZipPathcác đối tượng. Thật không may ZipPathlà chỉ đọc ngay bây giờ, nhưng nó có thể được mở rộng để hỗ trợ viết.

Bạn cũng có thể vượt qua các đối tượng của riêng bạn cho mục đích thử nghiệm. Để kiểm tra các API sử dụng os.path được đề xuất ở đây, bạn phải sử dụng tên đã nhập và phụ thuộc ngầm và thường thực hiện phép thuật đen để thử nghiệm của bạn hoạt động. Với FilePath, bạn làm một cái gì đó như thế này:

class MyFakePath:
    def child(self, name):
        "Return an appropriate child object"

    def walk(self):
        "Return an iterable of MyFakePath objects"

    def exists(self):
        "Return true or false, as appropriate to the test"

    def isdir(self):
        "Return true or false, as appropriate to the test"
...
subdirs(MyFakePath(...))

Vì tôi ít tiếp xúc với Twisted, tôi luôn hoan nghênh thông tin và ví dụ bổ sung; Câu trả lời này là tốt đẹp để xem cho điều đó. Phải nói rằng, vì cách tiếp cận này dường như đòi hỏi nhiều công việc hơn là sử dụng các mô-đun python tích hợp và cài đặt Twisted, có bất kỳ lợi thế nào khi sử dụng phương pháp này mà bạn có thể thêm vào câu trả lời không?
Jarret Hardie

1
Câu trả lời của Glyph có lẽ được lấy cảm hứng từ thực tế là TwistedLore cũng sử dụng các tệp .tpl.
Constantin

Chà, rõ ràng tôi không mong đợi câu hỏi của Tây Ban Nha :-) Tôi cho rằng "* .tpl" là một tham chiếu chung cho một số phần mở rộng trừu tượng có nghĩa là "mẫu" và không phải là một mẫu Twisted cụ thể (tôi đã thấy .tpl được sử dụng trong nhiều ngôn ngữ sau tất cả). Tốt để biết.
Jarret Hardie

Do đó, +1 để xoay theo góc Twisted có thể, mặc dù tôi vẫn muốn hiểu đối tượng Twisted'd 'FilePath' và hàm 'walk ()' được thêm vào API tiêu chuẩn.
Jarret Hardie

Cá nhân tôi thấy "FilePath.walk () mang lại các đối tượng đường dẫn" dễ nhớ hơn rất nhiều so với "os.walk mang lại 3-tuples của dir, dir, tập tin". Nhưng có những lợi ích khác. FilePath cho phép đa hình, có nghĩa là bạn có thể duyệt qua những thứ khác ngoài hệ thống tệp. Ví dụ: bạn có thể chuyển một hàm twist.txtthon.zippath.ZipArchive cho chức năng 'thư mục con' của tôi và nhận một trình tạo ZipPaths thay vì FilePaths; logic của bạn không thay đổi, nhưng ứng dụng của bạn hiện xử lý một cách kỳ diệu các tệp zip. Nếu bạn muốn kiểm tra nó, bạn chỉ cần cung cấp một đối tượng, bạn không phải viết các tập tin thực sự.
Glyph

4

Tôi chỉ viết một số mã để di chuyển các máy ảo vmware xung quanh, và cuối cùng đã sử dụng os.pathshutilđể thực hiện sao chép tệp giữa các thư mục con.

def copy_client_files (file_src, file_dst):
    for file in os.listdir(file_src):
            print "Copying file: %s" % file
            shutil.copy(os.path.join(file_src, file), os.path.join(file_dst, file))

Nó không thanh lịch khủng khiếp, nhưng nó hoạt động.


1

Đây là một cách:

import os
import shutil

def copy_over(path, from_name, to_name):
  for path, dirname, fnames in os.walk(path):
    for fname in fnames:
      if fname == from_name:
        shutil.copy(os.path.join(path, from_name), os.path.join(path, to_name))


copy_over('.', 'index.tpl', 'index.html')

-1: sẽ không hoạt động, vì shutil.copy sẽ sao chép vào thư mục hiện tại, vì vậy bạn sẽ kết thúc việc ghi đè 'index.html' trong thư mục hiện tại một lần cho mỗi 'index.tpl' bạn tìm thấy trong cây thư mục con.
nosklo

1

Tôi phải đề cập đến thư viện path.py mà tôi sử dụng rất thường xuyên.

Tìm nạp các thư mục con ngay lập tức trở nên đơn giản như sau:

my_dir.dirs()

Ví dụ làm việc đầy đủ là:

from path import Path

my_directory = Path("path/to/my/directory")

subdirs = my_directory.dirs()

NB: my_directory vẫn có thể được thao tác dưới dạng một chuỗi, vì Đường dẫn là một lớp con của chuỗi, nhưng cung cấp một loạt các phương thức hữu ích để thao tác các đường dẫn


1
def get_folders_in_directories_recursively(directory, index=0):
    folder_list = list()
    parent_directory = directory

    for path, subdirs, _ in os.walk(directory):
        if not index:
            for sdirs in subdirs:
                folder_path = "{}/{}".format(path, sdirs)
                folder_list.append(folder_path)
        elif path[len(parent_directory):].count('/') + 1 == index:
            for sdirs in subdirs:
                folder_path = "{}/{}".format(path, sdirs)
                folder_list.append(folder_path)

    return folder_list

Hàm sau có thể được gọi là:

get_ Foldftimein_directories_recursively (thư mục, index = 1) -> đưa ra danh sách các thư mục ở cấp độ đầu tiên

get_ Foldftimein_directories_recursively (thư mục) -> cung cấp cho tất cả các thư mục con


làm tốt, phiên bản python 3.6, nhưng tôi cần xóa "bản thân", từ các biến chức năng bên trong
locometro

1
đã sử dụng bên trong một lớp học, đã cập nhật
K Biến Mathew

0
import glob
import os

def child_dirs(path):
     cd = os.getcwd()        # save the current working directory
     os.chdir(path)          # change directory 
     dirs = glob.glob("*/")  # get all the subdirectories
     os.chdir(cd)            # change directory to the script original location
     return dirs

Các child_dirschức năng có một con đường một thư mục và trả về một danh sách các thư mục con ngay trong đó.

dir
 |
  -- dir_1
  -- dir_2

child_dirs('dir') -> ['dir_1', 'dir_2']

0
import pathlib


def list_dir(dir):
    path = pathlib.Path(dir)
    dir = []
    try:
        for item in path.iterdir():
            if item.is_dir():
                dir.append(item)
        return dir
    except FileNotFoundError:
        print('Invalid directory')

0

Một lớp lót sử dụng pathlib:

list_subfolders_with_paths = [p for p in pathlib.Path(path).iterdir() if p.is_dir()]
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.