Estou tentando escrever um script Python simples que copiará um index.tpl para index.html em todos os subdiretórios (com algumas exceções).
Estou ficando atolado tentando obter a lista de subdiretórios.
Estou tentando escrever um script Python simples que copiará um index.tpl para index.html em todos os subdiretórios (com algumas exceções).
Estou ficando atolado tentando obter a lista de subdiretórios.
Respostas:
Fiz alguns testes de velocidade em várias funções para retornar o caminho completo para todos os subdiretórios atuais.
tl; dr:
Sempre use scandir
:
list_subfolders_with_paths = [f.path for f in os.scandir(path) if f.is_dir()]
Bônus: Com scandir
você, você também pode obter apenas nomes de pastas usando em f.name
vez de f.path
.
Isso (assim como todas as outras funções abaixo) não usará a classificação natural . Isso significa que os resultados serão classificados da seguinte forma: 1, 10, 2. Para obter a classificação natural (1, 2, 10), consulte https://stackoverflow.com/a/48030307/2441026
Resultados :
scandir
é: 3x mais rápido que walk
, 32x mais rápido que listdir
(com filtro), 35x mais rápido que Pathlib
e 36x mais rápido que listdir
e 37x (!) Mais rápido que glob
.
Scandir: 0.977
Walk: 3.011
Listdir (filter): 31.288
Pathlib: 34.075
Listdir: 35.501
Glob: 36.277
Testado com W7x64, Python 3.8.1. Pasta com 440 subpastas.
Caso você se pergunte se listdir
poderia ser acelerado não executando os.path.join () duas vezes, sim, mas a diferença é basicamente inexistente.
Código:
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))]
Por que ninguém mencionou glob
? glob
permite usar a expansão de nome de caminho no estilo Unix, e é minha função funcionar para quase tudo que precisa encontrar mais de um nome de caminho. Isso torna muito fácil:
from glob import glob
paths = glob('*/')
Observe que glob
retornará o diretório com a barra final (como o unix faria), enquanto a maioria das path
soluções baseadas omitirá a barra final.
paths = [ p.replace('/', '') for p in glob('*/') ]
.
[p[:-1] for p in paths]
, pois esse método de substituição também substituirá as barras invertidas escapadas no nome do arquivo (não que sejam comuns).
rstrip
vez de strip
, já que o último transformará todos os caminhos totalmente qualificados em caminhos relativos.
strip('/')
removerá o inicial e o final '/', rstrip('/')
removerá apenas o final
Marque " Obtendo uma lista de todos os subdiretórios no diretório atual ".
Aqui está uma versão do Python 3:
import os
dir_list = next(os.walk('.'))[1]
print(dir_list)
(s.rstrip("/") for s in glob(parent_dir+"*/"))
é mais eficiente em termos de tempo. Minha suspeita intuitiva é que uma solução stat()
baseada em deve ser profundamente mais rápida do que o globbing do tipo shell. Infelizmente, eu não tenho vontade e realmente descubro. os.walk()
timeit
import os, os.path
Para obter subdiretórios imediatos (caminho completo) em um diretório:
def SubDirPath (d):
return filter(os.path.isdir, [os.path.join(d,f) for f in os.listdir(d)])
Para obter o subdiretório mais recente (mais recente):
def LatestDirectory (d):
return max(SubDirPath(d), key=os.path.getmtime)
list( filter(...) )
.
os.walk
é seu amigo nesta situação.
Diretamente da documentação:
walk () gera os nomes de arquivo em uma árvore de diretórios, percorrendo a árvore de cima para baixo ou de baixo para cima. Para cada diretório na árvore enraizado no topo do diretório (incluindo o próprio topo), ele gera três tuplas (caminho de rota, nome de diretório, nome de arquivo).
Este método faz tudo de uma só vez.
from glob import glob
subd = [s.rstrip("/") for s in glob(parent_dir+"*/")]
Usando o módulo FilePath do 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
Como alguns comentaristas perguntaram quais são as vantagens de usar as bibliotecas do Twisted, vou além da pergunta original aqui.
Há alguma documentação aprimorada em uma ramificação que explica as vantagens do FilePath; você pode querer ler isso.
Mais especificamente neste exemplo: diferente da versão da biblioteca padrão, esta função pode ser implementada sem importações . A função "subdireta" é totalmente genérica, pois opera apenas com base em seu argumento. Para copiar e mover os arquivos usando a biblioteca padrão, você precisa depender do " open
" builtin " listdir
" ", talvez" isdir
"ou" os.walk
"ou" shutil.copy
". Talvez " os.path.join
" também. Sem mencionar o fato de que você precisa de uma sequência passada em um argumento para identificar o arquivo real. Vamos dar uma olhada na implementação completa que copiará o "index.tpl" de cada diretório para "index.html":
def copyTemplates(topdir):
for subdir in subdirs(topdir):
tpl = subdir.child("index.tpl")
if tpl.exists():
tpl.copyTo(subdir.child("index.html"))
A função "subdirs" acima pode funcionar em qualquer FilePath
objeto semelhante. O que significa, entre outras coisas, ZipPath
objetos. Infelizmente, agora ZipPath
é somente leitura, mas pode ser estendido para oferecer suporte à gravação.
Você também pode passar seus próprios objetos para fins de teste. Para testar as APIs de uso do os.path sugeridas aqui, você precisa usar nomes importados e dependências implícitas e geralmente executar magia negra para que seus testes funcionem. Com o FilePath, você faz algo assim:
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(...))
Acabei de escrever um código para mover as máquinas virtuais vmware e acabei usando os.path
e shutil
realizando a cópia de arquivos entre subdiretórios.
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ão é muito elegante, mas funciona.
Aqui está uma maneira:
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')
Eu tenho que mencionar o path.py biblioteca , que eu uso com muita frequência.
A busca dos subdiretórios imediatos se torna tão simples quanto isso:
my_dir.dirs()
O exemplo completo de trabalho é:
from path import Path
my_directory = Path("path/to/my/directory")
subdirs = my_directory.dirs()
NB: my_directory ainda pode ser manipulado como uma string, já que Path é uma subclasse de string, mas fornece vários métodos úteis para manipular caminhos
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
A seguinte função pode ser chamada como:
get_folders_in_directories_recursively (diretório, índice = 1) -> fornece a lista de pastas no primeiro nível
get_folders_in_directories_recursively (directory) -> fornece todas as subpastas
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
A child_dirs
função pega um caminho de diretório e retorna uma lista dos subdiretórios imediatos nele.
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')
Um liner usando pathlib:
list_subfolders_with_paths = [p for p in pathlib.Path(path).iterdir() if p.is_dir()]