J'essaie d'écrire un simple script Python qui copiera un index.tpl vers index.html dans tous les sous-répertoires (à quelques exceptions près).
Je m'embourbe en essayant d'obtenir la liste des sous-répertoires.
J'essaie d'écrire un simple script Python qui copiera un index.tpl vers index.html dans tous les sous-répertoires (à quelques exceptions près).
Je m'embourbe en essayant d'obtenir la liste des sous-répertoires.
Réponses:
J'ai fait des tests de vitesse sur diverses fonctions pour renvoyer le chemin complet vers tous les sous-répertoires actuels.
tl; dr:
utilisez toujours scandir
:
list_subfolders_with_paths = [f.path for f in os.scandir(path) if f.is_dir()]
Bonus: Avec scandir
vous pouvez également obtenir simplement les noms de dossiers en utilisant f.name
au lieu de f.path
.
Ceci (ainsi que toutes les autres fonctions ci-dessous) n'utilisera pas le tri naturel . Cela signifie que les résultats seront triés comme suit: 1, 10, 2. Pour obtenir un tri naturel (1, 2, 10), veuillez consulter https://stackoverflow.com/a/48030307/2441026
Résultats :
scandir
est: 3x plus rapide que walk
, 32x plus rapide que listdir
(avec filtre), 35x plus rapide que Pathlib
et 36x plus rapide que listdir
et 37x (!) Plus rapide que glob
.
Scandir: 0.977
Walk: 3.011
Listdir (filter): 31.288
Pathlib: 34.075
Listdir: 35.501
Glob: 36.277
Testé avec W7x64, Python 3.8.1. Dossier avec 440 sous-dossiers.
Au cas où vous vous demanderiez si cela listdir
pourrait être accéléré en ne faisant pas os.path.join () deux fois, oui, mais la différence est pratiquement inexistante.
Code:
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))]
Pourquoi personne n'a mentionné glob
? glob
vous permet d'utiliser l'expansion des noms de chemin de style Unix, et c'est ma fonction préférée pour presque tout ce qui a besoin de trouver plus d'un nom de chemin. Cela rend les choses très faciles:
from glob import glob
paths = glob('*/')
Notez que glob
cela renverra le répertoire avec la barre oblique finale (comme le ferait unix) tandis que la plupart des path
solutions basées omettra la barre oblique finale.
paths = [ p.replace('/', '') for p in glob('*/') ]
.
[p[:-1] for p in paths]
, car cette méthode replace remplacera également toutes les barres obliques échappées dans le nom de fichier (non pas que celles-ci soient courantes).
rstrip
place de strip
, car ce dernier transformera tous les chemins complets en chemins relatifs.
strip('/')
supprimera à la fois le début et la fin '/', rstrip('/')
ne supprimera que le dernier
Cochez " Obtenir une liste de tous les sous-répertoires dans le répertoire courant ".
Voici une version de Python 3:
import os
dir_list = next(os.walk('.'))[1]
print(dir_list)
(s.rstrip("/") for s in glob(parent_dir+"*/"))
est plus efficace en temps. Mon soupçon intuitif est qu'une solution stat()
basée sur une base devrait être profondément plus rapide que le globbing de type shell. Malheureusement, je n'ai pas la volonté de le découvrir. os.walk()
timeit
import os, os.path
Pour obtenir (chemin complet) des sous-répertoires immédiats dans un répertoire:
def SubDirPath (d):
return filter(os.path.isdir, [os.path.join(d,f) for f in os.listdir(d)])
Pour obtenir le dernier sous-répertoire (le plus récent):
def LatestDirectory (d):
return max(SubDirPath(d), key=os.path.getmtime)
list( filter(...) )
.
os.walk
est votre ami dans cette situation.
Directement à partir de la documentation:
walk () génère les noms de fichiers dans une arborescence de répertoires, en parcourant l'arborescence de haut en bas ou de bas en haut. Pour chaque répertoire de l'arborescence enraciné au sommet du répertoire (y compris le sommet lui-même), il donne un 3-tuple (dirpath, dirnames, fichiers).
Cette méthode fait tout cela en une seule fois.
from glob import glob
subd = [s.rstrip("/") for s in glob(parent_dir+"*/")]
Utilisation du module FilePath de 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
Étant donné que certains commentateurs ont demandé quels sont les avantages d'utiliser les bibliothèques de Twisted pour cela, je vais aller un peu au-delà de la question originale ici.
Il existe une documentation améliorée dans une branche qui explique les avantages de FilePath; vous voudrez peut-être lire cela.
Plus précisément dans cet exemple: contrairement à la version standard de la bibliothèque, cette fonction peut être implémentée sans importation . La fonction "sous-répertoires" est totalement générique, en ce qu'elle n'opère que sur son argument. Pour copier et déplacer les fichiers en utilisant la bibliothèque standard, vous devez dépendre du " open
" builtin listdir
"," peut-être " isdir
" ou " os.walk
" ou " shutil.copy
". Peut-être " os.path.join
" aussi. Sans parler du fait que vous avez besoin d'une chaîne passée un argument pour identifier le fichier réel. Jetons un coup d'œil à l'implémentation complète qui copiera "index.tpl" de chaque répertoire dans "index.html":
def copyTemplates(topdir):
for subdir in subdirs(topdir):
tpl = subdir.child("index.tpl")
if tpl.exists():
tpl.copyTo(subdir.child("index.html"))
La fonction "sous-répertoires" ci-dessus peut fonctionner sur n'importe quel FilePath
objet semblable. Ce qui signifie, entre autres, des ZipPath
objets. MalheureusementZipPath
est actuellement en lecture seule, mais il pourrait être étendu pour prendre en charge l'écriture.
Vous pouvez également transmettre vos propres objets à des fins de test. Afin de tester les API utilisant os.path suggérées ici, vous devez singe avec des noms importés et des dépendances implicites et généralement effectuer de la magie noire pour que vos tests fonctionnent. Avec FilePath, vous faites quelque chose comme ceci:
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(...))
Je viens d'écrire du code pour déplacer les machines virtuelles vmware, et j'ai fini par utiliser os.path
et shutil
pour effectuer la copie de fichiers entre les sous-répertoires.
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))
Ce n'est pas très élégant, mais ça marche.
Voici une façon:
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')
Je dois mentionner la bibliothèque path.py , que j'utilise très souvent.
Récupérer les sous-répertoires immédiats devient aussi simple que cela:
my_dir.dirs()
L'exemple de travail complet est:
from path import Path
my_directory = Path("path/to/my/directory")
subdirs = my_directory.dirs()
NB: my_directory peut toujours être manipulé comme une chaîne, puisque Path est une sous-classe de string, mais fournit un tas de méthodes utiles pour manipuler les chemins
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
La fonction suivante peut être appelée comme:
get_folders_in_directories_recursively (directory, index = 1) -> donne la liste des dossiers du premier niveau
get_folders_in_directories_recursively (directory) -> donne tous les sous-dossiers
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
La child_dirs
fonction prend un chemin dans un répertoire et retourne une liste des sous-répertoires immédiats qu'il contient .
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')
Un liner utilisant pathlib:
list_subfolders_with_paths = [p for p in pathlib.Path(path).iterdir() if p.is_dir()]