Saya mencoba menulis skrip Python sederhana yang akan menyalin index.tpl ke index.html di semua subdirektori (dengan beberapa pengecualian).
Saya terjebak dengan mencoba mendapatkan daftar subdirektori.
Saya mencoba menulis skrip Python sederhana yang akan menyalin index.tpl ke index.html di semua subdirektori (dengan beberapa pengecualian).
Saya terjebak dengan mencoba mendapatkan daftar subdirektori.
Jawaban:
Saya melakukan beberapa pengujian kecepatan pada berbagai fungsi untuk mengembalikan path lengkap ke semua subdirektori saat ini.
tl; dr:
Selalu gunakan scandir
:
list_subfolders_with_paths = [f.path for f in os.scandir(path) if f.is_dir()]
Bonus: Dengan scandir
Anda juga bisa hanya mendapatkan nama folder dengan menggunakan f.name
bukan f.path
.
Ini (serta semua fungsi lainnya di bawah) tidak akan menggunakan penyortiran alami . Ini berarti hasil akan diurutkan seperti ini: 1, 10, 2. Untuk mendapatkan penyortiran alami (1, 2, 10), silakan lihat di https://stackoverflow.com/a/48030307/2441026
Hasil :
scandir
adalah: 3x lebih cepat dari walk
, 32x lebih cepat dari listdir
(dengan filter), 35x lebih cepat dari Pathlib
dan 36x lebih cepat dari listdir
dan 37x (!) Lebih cepat dari glob
.
Scandir: 0.977
Walk: 3.011
Listdir (filter): 31.288
Pathlib: 34.075
Listdir: 35.501
Glob: 36.277
Diuji dengan W7x64, Python 3.8.1. Folder dengan 440 subfolder.
Jika Anda bertanya-tanya apakah listdir
bisa dipercepat dengan tidak melakukan os.path.join () dua kali, ya, tetapi perbedaannya pada dasarnya tidak ada.
Kode:
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}")
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))]
Kenapa tidak ada yang menyebutkan glob
? glob
memungkinkan Anda menggunakan perluasan pathname gaya Unix, dan merupakan fungsi saya untuk hampir semua yang perlu menemukan lebih dari satu nama jalur. Itu membuatnya sangat mudah:
from glob import glob
paths = glob('*/')
Catatan yang glob
akan mengembalikan direktori dengan slash akhir (seperti yang akan unix) sementara sebagian besar path
solusi berbasis akan menghilangkan slash akhir.
paths = [ p.replace('/', '') for p in glob('*/') ]
.
[p[:-1] for p in paths]
, karena metode ganti juga akan menggantikan setiap garis miring maju yang melarikan diri dalam nama file (bukan yang umum).
rstrip
bukan strip
, karena yang terakhir akan mengubah jalur yang sepenuhnya memenuhi syarat menjadi jalur relatif.
strip('/')
akan menghapus starting dan trailing '/', rstrip('/')
hanya akan menghapus yang trailing
Centang " Mendapatkan daftar semua subdirektori di direktori saat ini ".
Ini versi Python 3:
import os
dir_list = next(os.walk('.'))[1]
print(dir_list)
(s.rstrip("/") for s in glob(parent_dir+"*/"))
lebih efisien waktu. Kecurigaan intuisi saya adalah bahwa solusi stat()
berbasiskan seharusnya jauh lebih cepat daripada globbing gaya shell. Sayangnya, saya tidak memiliki kemauan untuk dan benar-benar mencari tahu. os.walk()
timeit
import os, os.path
Untuk mendapatkan sub-direktori langsung (jalur penuh) langsung di direktori:
def SubDirPath (d):
return filter(os.path.isdir, [os.path.join(d,f) for f in os.listdir(d)])
Untuk mendapatkan sub-direktori terbaru (terbaru):
def LatestDirectory (d):
return max(SubDirPath(d), key=os.path.getmtime)
list( filter(...) )
.
os.walk
adalah teman Anda dalam situasi ini.
Langsung dari dokumentasi:
walk () menghasilkan nama file di pohon direktori, dengan berjalan pohon itu dari atas ke bawah atau dari bawah ke atas. Untuk setiap direktori di pohon yang di-root di direktori teratas (termasuk top itu sendiri), ia menghasilkan 3-tuple (dirpath, dirnames, nama file).
Metode ini dengan baik melakukan semuanya sekaligus.
from glob import glob
subd = [s.rstrip("/") for s in glob(parent_dir+"*/")]
Menggunakan modul 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
Karena beberapa komentator telah bertanya apa keuntungan menggunakan perpustakaan Twisted untuk ini, saya akan sedikit melampaui pertanyaan aslinya di sini.
Ada beberapa dokumentasi yang ditingkatkan di cabang yang menjelaskan kelebihan FilePath; Anda mungkin ingin membacanya.
Lebih khusus dalam contoh ini: tidak seperti versi perpustakaan standar, fungsi ini dapat diimplementasikan tanpa impor . Fungsi "subdirs" sepenuhnya generik, dalam arti ia beroperasi hanya dengan argumennya. Untuk menyalin dan memindahkan file menggunakan pustaka standar, Anda harus bergantung pada " open
" builtin, " listdir
", mungkin " isdir
" atau " os.walk
" atau " shutil.copy
". Mungkin " os.path.join
" juga. Belum lagi fakta bahwa Anda perlu sebuah string melewati argumen untuk mengidentifikasi file yang sebenarnya. Mari kita lihat implementasi penuh yang akan menyalin "index.tpl" setiap direktori ke "index.html":
def copyTemplates(topdir):
for subdir in subdirs(topdir):
tpl = subdir.child("index.tpl")
if tpl.exists():
tpl.copyTo(subdir.child("index.html"))
Fungsi "subdirs" di atas dapat bekerja pada FilePath
objek seperti apa pun . Yang artinya, antara lain, ZipPath
benda. Sayangnya ZipPath
hanya baca-sekarang, tetapi dapat diperluas untuk mendukung penulisan.
Anda juga dapat melewatkan objek Anda sendiri untuk tujuan pengujian. Untuk menguji API yang menggunakan os.path yang disarankan di sini, Anda harus menggunakan nama impor dan dependensi tersirat dan umumnya melakukan sihir hitam agar tes Anda berfungsi. Dengan FilePath, Anda melakukan sesuatu seperti ini:
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(...))
Saya baru saja menulis beberapa kode untuk memindahkan mesin virtual vmware, dan akhirnya menggunakan os.path
dan shutil
menyelesaikan penyalinan file antara sub-direktori.
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))
Itu tidak terlalu elegan, tetapi itu berhasil.
Ini salah satu caranya:
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')
Saya harus menyebutkan perpustakaan path.py , yang sering saya gunakan.
Mengambil subdirektori langsung menjadi sesederhana itu:
my_dir.dirs()
Contoh kerja penuh adalah:
from path import Path
my_directory = Path("path/to/my/directory")
subdirs = my_directory.dirs()
NB: my_directory masih dapat dimanipulasi sebagai string, karena Path adalah subkelas string, tetapi menyediakan banyak metode yang berguna untuk memanipulasi jalur
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
Fungsi berikut dapat disebut sebagai:
get_folders_in_directories_recursively (direktori, indeks = 1) -> memberikan daftar folder di tingkat pertama
get_folders_in_directories_recursively (direktori) -> memberikan semua sub folder
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
Itu child_dirs
fungsi mengambil jalur direktori dan mengembalikan daftar subdirektori langsung di dalamnya.
dir
|
-- dir_1
-- dir_2
child_dirs('dir') -> ['dir_1', 'dir_2']
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')
Satu liner menggunakan pathlib:
list_subfolders_with_paths = [p for p in pathlib.Path(path).iterdir() if p.is_dir()]