วิธีรับไดเรกทอรีย่อยทั้งหมดทันทีใน Python


150

ฉันกำลังพยายามเขียนสคริปต์ Python แบบง่ายที่จะคัดลอก index.tpl ไปยัง index.html ในไดเรกทอรีย่อยทั้งหมด (มีข้อยกเว้นเล็กน้อย)

ฉันจมอยู่กับการพยายามหารายชื่อไดเรกทอรีย่อย


11
คุณอาจพบว่าคำตอบที่ได้รับการยอมรับในคำถามก่อนหน้านี้ดังนั้นการแก้ปัญหา: stackoverflow.com/questions/120656/directory-listing-in-python
Jarret ฮาร์ดี

คำตอบ:


31

ฉันทำการทดสอบความเร็วบางฟังก์ชั่นเพื่อคืนค่าพา ธ เต็มไปยังไดเรกทอรีย่อยปัจจุบันทั้งหมด

tl; dr: ใช้เสมอscandir:

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

โบนัส: ด้วยscandirคุณยังสามารถเพียงแค่เพียง แต่ได้รับชื่อโฟลเดอร์โดยใช้แทนf.namef.path

นี้ (เช่นเดียวกับฟังก์ชั่นอื่น ๆ ทั้งหมดด้านล่าง) จะไม่ใช้การเรียงลำดับตามธรรมชาติ ซึ่งหมายความว่าผลลัพธ์จะถูกจัดเรียงเช่นนี้: 1, 10, 2 หากต้องการรับการจัดเรียงแบบธรรมชาติ (1, 2, 10) โปรดดูที่https://stackoverflow.com/a/48030307/2441026




ผลการค้นหา : scandirคือ 3x เร็วกว่าwalk, เร็วกว่า 32x listdir(กรอง) 35x เร็วกว่าPathlibและเร็วกว่า 36x listdirและ 37x globเร็วกว่า

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

ทดสอบกับ W7x64, Python 3.8.1 โฟลเดอร์ที่มีโฟลเดอร์ย่อย 440 โฟลเดอร์
ในกรณีที่คุณสงสัยว่าlistdirสามารถเร่งความเร็วได้หรือไม่โดยไม่ทำ os.path.join () สองครั้งใช่ แต่ความแตกต่างนั้นไม่มีอยู่จริง

รหัส:

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
แค่อยากจะขอบคุณคุณกำลังมองหาสิ่งนี้จริงๆ การวิเคราะห์ที่ยอดเยี่ยม
Cing


76

ทำไมไม่มีใครพูดถึงglob? globให้คุณใช้การขยายชื่อพา ธ แบบ Unix และเป็นหน้าที่ของฉันในการทำงานเกือบทุกอย่างที่ต้องการค้นหาชื่อพา ธ มากกว่าหนึ่งชื่อ มันทำให้ง่ายมาก:

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

โปรดทราบว่าglobจะส่งคืนไดเร็กทอรีด้วยเครื่องหมายสแลชสุดท้าย (เหมือนยูนิกซ์) ในขณะที่pathโซลูชันพื้นฐานส่วนใหญ่จะละเว้นเครื่องหมายสแลชสุดท้าย


3
ทางออกที่ดีง่ายและใช้งานได้ paths = [ p.replace('/', '') for p in glob('*/') ]สำหรับผู้ที่ไม่ต้องการที่เฉือนสุดท้ายเขาสามารถใช้นี้
Evan Hu

5
มันอาจจะปลอดภัยกว่าที่จะตัดตัวอักษรตัวสุดท้ายด้วย[p[:-1] for p in paths]เนื่องจากวิธีการแทนที่นั้นจะแทนที่เครื่องหมายสแลชทางลัดใด ๆ ที่หลบหนีในชื่อไฟล์
อารี

3
แม้จะปลอดภัยกว่าให้ใช้ strip ('/') เพื่อลบเครื่องหมายสแลช วิธีนี้รับประกันได้ว่าคุณจะไม่ตัดตัวละครใด ๆ ที่ไม่ใช่เครื่องหมายทับหน้า
Eliezer Miron

8
โดยการก่อสร้างคุณรับประกันได้ว่าจะมีการต่อท้ายสแลช (ดังนั้นจึงไม่ปลอดภัยกว่า) แต่ฉันคิดว่ามันอ่านง่ายขึ้น คุณต้องการใช้rstripแทนแน่นอนstripเนื่องจากหลังจะเปลี่ยนเส้นทางที่ผ่านการรับรองเป็นเส้นทางสัมพัทธ์
อารีย์

7
เติมเต็มความคิดเห็นของ @ari สำหรับมือใหม่ของไพ ธ อนเช่น I: strip('/')จะลบทั้งการเริ่มต้นและการต่อท้าย '/' rstrip('/')จะลบเฉพาะการติดตามหนึ่งครั้งเท่านั้น
Titou

35

ทำเครื่องหมายที่ " รับรายการไดเรกทอรีย่อยทั้งหมดในไดเรกทอรีปัจจุบัน "

นี่เป็นเวอร์ชั่น Python 3:

import os

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

print(dir_list)

2
ฉลาดมาก ในขณะที่ประสิทธิภาพไม่สำคัญ ( ... มันทำทั้งหมด ) ฉันอยากรู้ว่าการแสดงออกของเครื่องกำเนิดไฟฟ้าแบบนี้หรือใช้(s.rstrip("/") for s in glob(parent_dir+"*/"))เวลามากขึ้นหรือไม่ ความสงสัยที่ใช้งานง่ายของฉันคือโซลูชันที่stat()ใช้งานควรเร็วกว่าการโค้งแบบเชลล์ น่าเศร้าที่ฉันขาดความตั้งใจและค้นพบจริง os.walk()timeit
เซซิลแกงกะหรี่

3
โปรดทราบว่าสิ่งนี้จะส่งคืนชื่อไดเรกทอรีย่อยที่ไม่มีชื่อไดเรกทอรีแม่นำหน้า
Paul Chernoch

19
import os, os.path

ในการรับ (เต็มเส้นทาง) ไดเรกทอรีย่อยทันทีในไดเรกทอรี:

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

วิธีรับไดเรกทอรีย่อย (ใหม่ที่สุด) ล่าสุด:

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

ที่จะได้รับรายชื่อlist( filter(...) )เพียงแค่เพิ่ม
136036

12

os.walk เป็นเพื่อนของคุณในสถานการณ์นี้

ส่งตรงจากเอกสาร:

walk () สร้างชื่อไฟล์ในแผนผังไดเร็กทอรีโดยการเดินแผนผังทั้งจากบนลงล่างหรือล่างขึ้นบน สำหรับแต่ละไดเร็กทอรีในทรีที่รูทที่ไดเร็กทอรีบนสุด (รวมถึงตัวท็อปเอง) จะให้ผลลัพธ์ 3-tuple (dirpath, dirnames, filenames)


1
โปรดทราบว่าหากคุณต้องการเฉพาะไดเรกทอรีย่อยระดับแรกให้แบ่งการทำซ้ำ os.walk หลังจากค่าส่งคืนชุดแรก
yoyo

11

วิธีนี้ไม่ได้ทั้งหมดในครั้งเดียว

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

7

ใช้โมดูล FilePath ของ 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

เนื่องจากผู้วิจารณ์บางคนถามว่าข้อดีของการใช้ห้องสมุดของ Twisted สำหรับเรื่องนี้คืออะไรฉันจะไปไกลกว่าคำถามเดิมที่นี่


มีเอกสารที่ได้รับการปรับปรุงในสาขาที่อธิบายข้อดีของ FilePath; คุณอาจต้องการอ่านสิ่งนั้น

โดยเฉพาะอย่างยิ่งในตัวอย่างนี้แตกต่างจากรุ่นมาตรฐานห้องสมุด, ฟังก์ชั่นนี้สามารถนำมาใช้กับการนำเข้าไม่มี ฟังก์ชั่น "subdirs" นั้นเป็นเรื่องทั่วไปโดยสิ้นเชิง ในการคัดลอกและย้ายไฟล์โดยใช้ไลบรารีมาตรฐานคุณต้องพึ่งพา " open" builtin, " listdir", บางที " isdir" หรือ " os.walk" หรือ " shutil.copy" อาจจะ " os.path.join" เช่นกัน ไม่ต้องพูดถึงความจริงที่ว่าคุณต้องการสตริงที่ผ่านการโต้แย้งเพื่อระบุไฟล์จริง มาดูการใช้งานแบบเต็มซึ่งจะคัดลอก "index.tpl" ของแต่ละไดเรกทอรีไปที่ "index.html":

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

ฟังก์ชัน "subdirs" ด้านบนสามารถทำงานกับFilePathวัตถุที่เหมือนกันได้ ซึ่งหมายความว่าเหนือสิ่งอื่นใดZipPathวัตถุ น่าเสียดายที่ZipPathเป็นแบบอ่านอย่างเดียวในขณะนี้ แต่อาจขยายเพื่อรองรับการเขียน

คุณสามารถส่งวัตถุของคุณเองเพื่อการทดสอบ เพื่อที่จะทดสอบ API ที่ใช้ os.path ที่แนะนำที่นี่คุณจะต้องมีชื่อที่นำเข้าและการพึ่งพาโดยนัยและมักจะใช้เวทย์มนตร์ดำเพื่อให้การทดสอบของคุณทำงาน ด้วย FilePath คุณทำสิ่งนี้:

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(...))

เนื่องจากฉันมีการเปิดเผย Twisted เล็กน้อยฉันจึงยินดีรับข้อมูลและตัวอย่างเพิ่มเติมอยู่เสมอ คำตอบนี้ดีที่ได้เห็น ต้องบอกว่าเนื่องจากวิธีนี้ดูเหมือนจะต้องใช้งานมากกว่าการใช้โมดูลหลามในตัวและการติดตั้งแบบ Twisted มีข้อดีใด ๆ ในการใช้สิ่งนี้ที่คุณสามารถเพิ่มลงในคำตอบได้
Jarret ฮาร์ดี

1
คำตอบของ Glyph อาจได้รับแรงบันดาลใจจากข้อเท็จจริงที่ว่า TwistedLore ยังใช้ไฟล์. tpl
Constantin

เห็นได้ชัดว่าฉันไม่ได้คาดหวังว่าการสอบสวนภาษาสเปน :-) ฉันถือว่า "* .tpl" เป็นการอ้างอิงทั่วไปถึงความหมายที่เป็นนามธรรมของ "เทมเพลต" และไม่ใช่เทมเพลต Twisted ที่เจาะจง ภาษาหลังจากทั้งหมด) ดีแล้วที่รู้.
Jarret ฮาร์ดี

+1 ดังนั้นสำหรับการ twigging ไปยังมุม Twisted ที่เป็นไปได้ แต่ฉันยังคงต้องการที่จะเข้าใจสิ่งที่วัตถุ 'FilePath' และ Twisted'd 'ฟังก์ชั่น' เดิน () 'เพิ่มไปยัง API มาตรฐาน
Jarret ฮาร์ดี

โดยส่วนตัวแล้วฉันพบว่า "FilePath.walk () ให้ผลตอบแทนวัตถุพา ธ " ง่ายต่อการจดจำมากกว่า "os.walk ให้ผล 3-tuples ของ dir, dirs, files" แต่มีประโยชน์อื่น ๆ FilePath อนุญาตให้มีความหลากหลายซึ่งหมายความว่าคุณสามารถสำรวจสิ่งอื่นนอกเหนือจากระบบไฟล์ ตัวอย่างเช่นคุณสามารถส่ง twisted.python.zippath.ZipArchive ไปยังฟังก์ชัน 'subdirs' ของฉันและรับตัวสร้าง ZipPaths แทน FilePaths ตรรกะของคุณไม่เปลี่ยนแปลง แต่ตอนนี้แอปพลิเคชันของคุณจัดการไฟล์ซิปได้อย่างน่าอัศจรรย์ หากคุณต้องการทดสอบคุณเพียงแค่จัดหาวัตถุคุณไม่ต้องเขียนไฟล์จริง
สัญลักษณ์

4

ฉันเพิ่งเขียนโค้ดบางอย่างเพื่อย้ายเครื่องเสมือน vmware ไปรอบ ๆ และจบลงด้วยการใช้os.pathและshutilเพื่อให้การคัดลอกไฟล์ระหว่างไดเรกทอรีย่อยสำเร็จ

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))

มันไม่ได้สง่างามมาก แต่มันใช้งานได้


1

นี่คือวิธีหนึ่ง:

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: ไม่ทำงานเนื่องจาก shutil.copy จะคัดลอกไปยัง dir ปัจจุบันดังนั้นคุณจะต้องเขียนทับ 'index.html' ใน dir ปัจจุบันหนึ่งครั้งสำหรับแต่ละ 'index.tpl' ที่คุณพบในแผนผังย่อย
nosklo

1

ฉันต้องพูดถึงไลบรารีpath.pyซึ่งฉันใช้บ่อยมาก

การเรียกไดเรกทอรีย่อยทันทีกลายเป็นเรื่องง่ายเหมือน:

my_dir.dirs()

ตัวอย่างการทำงานเต็มรูปแบบคือ:

from path import Path

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

subdirs = my_directory.dirs()

หมายเหตุ: my_directory ยังสามารถจัดการเป็นสตริงได้เนื่องจาก Path เป็นคลาสย่อยของสตริง แต่มีวิธีการที่เป็นประโยชน์มากมายสำหรับจัดการเส้นทาง


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

ฟังก์ชั่นต่อไปนี้สามารถเรียกได้ว่า:

get_folders_in_directories_recursively (ไดเรกทอรี, index = 1) -> ให้รายชื่อโฟลเดอร์ในระดับแรก

get_folders_in_directories_recursively (ไดเรกทอรี) -> ให้โฟลเดอร์ย่อยทั้งหมด


ทำดี, รุ่น 3.6 หลาม แต่ฉันต้องการที่จะลบ "ตัวเอง" จากตัวแปรฟังก์ชั่นภายใน
locometro

1
ใช้ในชั้นเรียนได้รับการปรับปรุง
Kanish 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

child_dirsฟังก์ชั่นใช้เวลาเส้นทางไดเรกทอรีและกลับรายการของที่ไดเรกทอรีย่อยทันทีในนั้น

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')

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.