parcours de dossier¶
en 2024 tous les calculs/parcours sur le contenu du disque, dossiers, fichiers, et métadonnées telles que tailles, dates, etc... se font avec le couteau suisse pathlib
lisez-bien tout le notebook, et surtout les indices, avant de commencer
from pathlib import Path(et non plus avec os.path et autres glob comme on aurait pu le faire dans le passé)
initialisation¶
on va partir d’un dossier avec un peu de contenu; c’est pour simuler par exemple un dossier avec des logs
# on nettoie bien tout pour être sûr
!rm -rf pathlib-foofrom files_sortby import init
init()Wrote file pathlib-foo/logs/file1 - size 11
Wrote file pathlib-foo/logs/dir1/filebxxxxxx - size 12
Wrote file pathlib-foo/logs/dir1/filebbxxxxxx - size 13
Wrote file pathlib-foo/logs/dir1/filebbbxxxxxx - size 14
Wrote file pathlib-foo/logs/file10 - size 10
Wrote file pathlib-foo/logs/dir10/fileaxxxx - size 102
Wrote file pathlib-foo/logs/dir10/fileaaxxxx - size 103
Wrote file pathlib-foo/logs/dir10/fileaaaxxxx - size 104
Wrote file pathlib-foo/logs/file100 - size 9
Wrote file pathlib-foo/logs/dir100/filecxx - size 1002
Wrote file pathlib-foo/logs/dir100/fileccxx - size 1003
Wrote file pathlib-foo/logs/dir100/filecccxx - size 1004
pour faire la même chose sur votre ordi:
from pathlib import Path
def init():
"""
a utility function only for populating the 'pathlib-foo' subfolder
"""
scale = 10
logs = Path("pathlib-foo") / "logs"
logs.mkdir(parents=True, exist_ok = True)
for i, log, key in ( (1, 0, 'b'), (10, 1, 'a'), (100, 2, 'c')):
f = logs / f"file{i}"
with f.open('w') as output:
content = (10-log) * '*'
output.write(content + "\n")
size = f.stat().st_size
print(f"Wrote file {f} - size {size}")
d = logs / f"dir{i}"
d.mkdir(exist_ok = True)
for j in range(1, 4):
pad = (6-2*log) * 'x'
f = d / f"file{j*key}{pad}"
with f.open('w') as output:
content = (i*scale+j) * '*'
output.write(content + "\n")
size = f.stat().st_size
print(f"Wrote file {f} - size {size}")
pb1: parcours de dossier¶
on veut parcourir tout un dossier, c’est-à-dire calculer la liste des fichiers qui se trouvent dans un dossier; et cela récursivement ou pas (en parcourant ou non les sous-dossiers)
on vous demande d’écrire une fonction scan_dir qui prend en paramètres:
root(le nom d’)un dossier racinerelative: un chemin relatif (en dessous de cette racine; peut être vide ou ‘.’)un booléen
recursive
et qui renvoie une liste d’objets de type Path, qui correspondent aux fichiers (pas les dossiers) qui se situent en dessous de root/relative
exemples¶
from files_sortby import scan_dir
for p in scan_dir("pathlib-foo/", relative="logs/dir1", recursive=False):
print(p)pathlib-foo/logs/dir1/filebxxxxxx
pathlib-foo/logs/dir1/filebbbxxxxxx
pathlib-foo/logs/dir1/filebbxxxxxx
for p in scan_dir("pathlib-foo/", relative="logs", recursive=True):
print(p)pathlib-foo/logs/file1
pathlib-foo/logs/file10
pathlib-foo/logs/file100
pathlib-foo/logs/dir10/fileaxxxx
pathlib-foo/logs/dir10/fileaaxxxx
pathlib-foo/logs/dir10/fileaaaxxxx
pathlib-foo/logs/dir1/filebxxxxxx
pathlib-foo/logs/dir1/filebbbxxxxxx
pathlib-foo/logs/dir1/filebbxxxxxx
pathlib-foo/logs/dir100/filecxx
pathlib-foo/logs/dir100/fileccxx
pathlib-foo/logs/dir100/filecccxx
pb2: idem mais en triant¶
on veut maintenant pouvoir trier cette information
on veut écrire une fonction sort_dir qui prend les mêmes paramètres, et en plus
un paramètre
by(une chaine) qui vautnamepour trier selon le nom du fichiernamelenpour trier par la longueur du nom du fichiersizepour trier selon la taille du fichiermtimepour trier selon la date de modification du fichier
exemples¶
from files_sortby import sort_dir
sort_dir("pathlib-foo", relative="logs", recursive=True, by='size')[PosixPath('pathlib-foo/logs/file100'),
PosixPath('pathlib-foo/logs/file10'),
PosixPath('pathlib-foo/logs/file1'),
PosixPath('pathlib-foo/logs/dir1/filebxxxxxx'),
PosixPath('pathlib-foo/logs/dir1/filebbxxxxxx'),
PosixPath('pathlib-foo/logs/dir1/filebbbxxxxxx'),
PosixPath('pathlib-foo/logs/dir10/fileaxxxx'),
PosixPath('pathlib-foo/logs/dir10/fileaaxxxx'),
PosixPath('pathlib-foo/logs/dir10/fileaaaxxxx'),
PosixPath('pathlib-foo/logs/dir100/filecxx'),
PosixPath('pathlib-foo/logs/dir100/fileccxx'),
PosixPath('pathlib-foo/logs/dir100/filecccxx')]from files_sortby import sort_dir
sort_dir("pathlib-foo", relative="logs", recursive=True, by='namelen')[PosixPath('pathlib-foo/logs/file1'),
PosixPath('pathlib-foo/logs/file10'),
PosixPath('pathlib-foo/logs/file100'),
PosixPath('pathlib-foo/logs/dir100/filecxx'),
PosixPath('pathlib-foo/logs/dir100/fileccxx'),
PosixPath('pathlib-foo/logs/dir10/fileaxxxx'),
PosixPath('pathlib-foo/logs/dir100/filecccxx'),
PosixPath('pathlib-foo/logs/dir10/fileaaxxxx'),
PosixPath('pathlib-foo/logs/dir10/fileaaaxxxx'),
PosixPath('pathlib-foo/logs/dir1/filebxxxxxx'),
PosixPath('pathlib-foo/logs/dir1/filebbxxxxxx'),
PosixPath('pathlib-foo/logs/dir1/filebbbxxxxxx')]Indices¶
# on utilise ici quelques traits de `pathlib.Path`
from pathlib import Path
# pour construire un Path
root = Path("pathlib-foo")
rootPosixPath('pathlib-foo')# on peut utiliser l'opérateur `/` pour construire des chemins
# mais ça ne marche pas (évidemment) entre deux chaines
# par contre dès qu'un des deux opérandes est un Path:
path = root / "logs/dir100/filecxx"
pathPosixPath('pathlib-foo/logs/dir100/filecxx')# ou encore, donne le même résultat
path = root / "logs" / "dir100" / "filecxx"# pour avoir la taille
path.stat().st_size1002# pour chercher les fichiers on peut utiliser la méthode glob
# 2 remarques:
# - ici j'appelle list(), c'est juste pour avoir un affichage correct (essayez de l'enlever...)
logs = root / "logs"
list(logs.glob("*"))[PosixPath('pathlib-foo/logs/file1'),
PosixPath('pathlib-foo/logs/dir10'),
PosixPath('pathlib-foo/logs/dir1'),
PosixPath('pathlib-foo/logs/file10'),
PosixPath('pathlib-foo/logs/file100'),
PosixPath('pathlib-foo/logs/dir100')]# et pour les recherches récursives on fait comme ceci
# le **/ va matcher tous les dossiers en dessous de 'logs'
list(logs.glob("**"))[PosixPath('pathlib-foo/logs'),
PosixPath('pathlib-foo/logs/dir10'),
PosixPath('pathlib-foo/logs/dir1'),
PosixPath('pathlib-foo/logs/dir100')]# du coup pour trouver tous les fichiers on fait
list(logs.glob("**/*"))[PosixPath('pathlib-foo/logs/file1'),
PosixPath('pathlib-foo/logs/dir10'),
PosixPath('pathlib-foo/logs/dir1'),
PosixPath('pathlib-foo/logs/file10'),
PosixPath('pathlib-foo/logs/file100'),
PosixPath('pathlib-foo/logs/dir100'),
PosixPath('pathlib-foo/logs/dir10/fileaxxxx'),
PosixPath('pathlib-foo/logs/dir10/fileaaxxxx'),
PosixPath('pathlib-foo/logs/dir10/fileaaaxxxx'),
PosixPath('pathlib-foo/logs/dir1/filebxxxxxx'),
PosixPath('pathlib-foo/logs/dir1/filebbbxxxxxx'),
PosixPath('pathlib-foo/logs/dir1/filebbxxxxxx'),
PosixPath('pathlib-foo/logs/dir100/filecxx'),
PosixPath('pathlib-foo/logs/dir100/fileccxx'),
PosixPath('pathlib-foo/logs/dir100/filecccxx')]# pas utilisé dans cet exercice, mais on peut facilement
# faire des calculs du genre de:
path = root / "logs" / "dir100" / "filecxx"
path.parts('pathlib-foo', 'logs', 'dir100', 'filecxx')# ou encore
list(path.parents)[PosixPath('pathlib-foo/logs/dir100'),
PosixPath('pathlib-foo/logs'),
PosixPath('pathlib-foo'),
PosixPath('.')]# ou encore
path.absolute()PosixPath('/home/runner/work/exos-python/exos-python/notebooks/exos/basic/pathlib-foo/logs/dir100/filecxx')path.relative_to(root)PosixPath('logs/dir100/filecxx')# etc etc..solution¶
ouvrez-moi
def scan_dir(root, *, relative=None, recursive=False):
# we need a Path instance to start with
if not isinstance(root, Path):
root = Path(root)
# if relative is not provided, we use the current folder
if relative is None:
relative = '.'
# the pattern to use is '**/*' if recursive is True
glob_pattern = '**/*' if recursive else '*'
# the most accessible version uses a comprehension
# however, as we will see, in this case we can use a genexpr instead
return (f for f in (root/relative).glob(glob_pattern) if f.is_file())
def sort_dir(root: Path, *, relative: str = '.', recursive=False, by='name'):
"""
sort files under the folder 'root'
only the subfolder under 'relative' is considered,
Parameters:
root: a Path to the main folder
relative: a str, the relative path to subfolder
recursive: a boolean that says if a recursive search is needed
criteria: a str among 'name', 'namelen', or 'size'
"""
files = scan_dir(root, relative=relative, recursive=recursive)
match by:
case 'name':
sort_function = lambda p: p.name
case 'namelen':
sort_function = lambda p: len(p.name)
case 'size':
sort_function = lambda p: p.stat().st_size
case 'mtime':
sort_function = lambda p: p.stat().st_mtime
# not too interesting - not advertized
case 'path':
sort_function = lambda p: len(str(p.relative_to(root)))
return sorted(files, key = sort_function)
variantes possibles¶
passer en paramètre les extensions de fichier qui sont intéressantes; par exemple on pourrait accepter pour ce paramètre
None: le défaut, comme on vient de faireune chaine unique, e.g.
"py"pour ne regarder que les*.pyune liste d’extensions
afficher la première ligne de chaque fichier - pour cela il est sans doute idoine de se définir une nouvelle fonction
en faire un script qui puisse se lancer depuis le terminal
etc...