Question récurrente, voici mon implémentation en Python 3. Ça fait réinvention de la roue comparer à ce que la bibliothèque standard propose déjà depuis un moment :
import os
for root, _, files in os.walk(path):
print(root, len(files))
Mais depuis la version 3.5 de Python, os.scandir a été grandement optimisée. Je m'y suis donc attelé :
#!/usr/bin/env python3
# coding: utf-8
""" Compter le nombre de fichiers de chaque dossier.
Les liens symboliques sont ignorés.
"""
import os.path
from os import scandir
class Counter(object):
""" Classe qui gère le listage et le comptage de chaque dossier. """
def __init__(self, path):
""" Initialisation des variables. """
if not path:
raise ValueError('A girl needs a path.')
self.path = path
self.files = 0
def work(self):
""" La méthode qui scanne le dossier et fait le décompte. """
# `entry` contient pas mal d'informations. Voir :
# https://docs.python.org/3/library/os.html#os.DirEntry
for entry in scandir(self.path):
# S'il s'agit d'un dossier qui n'est pas un lien symbolique...
if entry.is_dir() and not entry.is_symlink():
# ... On instancie une autre classe de `Counter` afin de faire
# le décompte des fichiers de ce dossier.
path = os.path.join(self.path, entry.name)
counter = Counter(path)
# `yield from` permet de capter et renvoyer le `yield self` du
# compteur que l'on vient d'instancier. Pour faire plus simple,
# on récupère la classe une fois que le décompte du dossier
# est terminé.
yield from counter.work()
else:
# Il s'agit d'un fichier, on incrémente le compteur.
self.files += 1
# On renvoie la classe elle-même. On pourrait aussi renvoyer seulement
# les infos nécessaires avec un `yield (self.path, self.files)`.
yield self
def __str__(self):
""" Représentation de la classe. Permet de faire :
>>> print(cls)
/etc 115
"""
return '{} {}'.format(self.path, self.files)
Exemple 1
Admettons que nous voulions une liste de chaque dossier suivi du nombre de fichiers qu'il contient :
counter = Counter(path)
for cls in counter.work():
# Afficher seulement les dossiers non vides
if cls.files:
print(cls)
Ce qui donnera, par exemple :
$ python3 exemple1.py "/etc"
...
/etc/apt/apt.conf.d 7
/etc/apt/trusted.gpg.d 7
/etc/apt/sources.list.d 2
/etc/apt/preferences.d 1
/etc/apt 4
/etc/rc6.d 37
/etc/rcS.d 24
/etc/UPower 1
/etc/wpa_supplicant 3
/etc/openvpn 4
/etc 115
Exemple 2
Maintenant, nous voudrions savoir combien de fichiers contient un dossier (sous-dossiers inclus) :
counter = Counter(path)
total = 0
for cls in counter.work():
total += cls.files
print(path, total)
Ce qui donnera, par exemple :
$ python3 exemple2.py "/etc"
/etc 3155
Note : pour cet exemple, il aurait été intéressant d'implémenter Counter.__iter__()
et Counter.__add__()
, puis d'utiliser reduce ; mais le temps aurait été bien trop décuplé (de l'ordre de 10).
Exemple 3
Enfin, calqué sur l'exemple précedent, comment savoir combien de fichiers contient un dossier ?
counter = Counter(path)
for cls in counter.work():
pass
print(counter)
Ce qui donnera, par exemple :
$ python3 exemple3.py "/etc"
/etc 115