IdentifiantMot de passe
Loading...
Mot de passe oublié ?Je m'inscris ! (gratuit)
Navigation

Inscrivez-vous gratuitement
pour pouvoir participer, suivre les réponses en temps réel, voter pour les messages, poser vos propres questions et recevoir la newsletter

Contribuez Python Discussion :

Mais où sont donc les fichiers python que j'ai modifiés la semaine dernière?


Sujet :

Contribuez Python

  1. #1
    Expert éminent
    Avatar de tyrtamos
    Homme Profil pro
    Retraité
    Inscrit en
    Décembre 2007
    Messages
    4 462
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Var (Provence Alpes Côte d'Azur)

    Informations professionnelles :
    Activité : Retraité

    Informations forums :
    Inscription : Décembre 2007
    Messages : 4 462
    Points : 9 249
    Points
    9 249
    Billets dans le blog
    6
    Par défaut Mais où sont donc les fichiers python que j'ai modifiés la semaine dernière?
    Bonjour,

    Nos PC actuels, complétés par les disques accessibles en réseau, peuvent comporter plusieurs millions de fichiers. Et la question de mon titre est l'une des questions que je me pose de temps en temps, et jusqu'à présent, je n'ai rien trouvé de pratique pour y répondre facilement. Alors, j'ai cherché à créer une solution en Python qui soit très adaptable à mes besoins.

    Voilà un petit exemple simplifié de ce que j'ai fait.

    Il faut d'abord créer ce qu'il faut pour chercher tous les fichiers d'un répertoire et de ses sous-répertoires, et ne retenir que ceux qui satisfont un filtre: j'ai utilisé os.walk pour naviguer de façon récursive dans une arborescence:

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    # -*- coding: utf-8 -*-
    # Python 3
     
    import os
    from datetime import datetime, timedelta, date
    from fnmatch import fnmatch
     
    class Cherchefichiers(object):
        """Permet de trouver tous les fichiers d'un répertoire et de ses 
           sous-répertoire qui répond à des tests de filtrage
        """
     
        #========================================================================
        def __init__(self, repertoire=""):
            # Enregistre le répertoire fourni après sa normalisation
            self.repertoire = os.path.abspath(os.path.expanduser(repertoire))
            # initialise la liste des erreurs consultable en fin de recherche
            self.listerreurs = [] 
     
        #========================================================================
        def fnerreurs(self, msgerr):
            """Reçoit et enregistre les éventuels messages d'erreur de os.walk
            """
            self.listerreurs.append([msgerr.errno, msgerr.strerror, msgerr.filename])
     
        #========================================================================
        def cherche(self, filtre=None):
            """Trouve les fichiers du répertoire et de ses sous-répertoires
               qui satisfont le filtre donné
            """
            if filtre==None:
                filtre = lambda x: True # sans filtre donné, on accepte tout
            self.listerreurs = [] # remet à vide la liste des erreurs
            for repert, sreps, fics in os.walk(self.repertoire, onerror=self.fnerreurs):
                for fic in fics:
                    fichier = os.path.join(repert, fic) # ajout du chemin
                    try:
                        os.path.getsize(fichier) # uniquement pour valider l'accès
                        if filtre(fichier):
                            yield fichier
                    except OSError as msgerr:
                        self.fnerreurs(msgerr)
    Il était, bien sûr, indispensable de gérer les erreurs d'accès à des répertoires ou à des fichiers, et dans le code précédent, on peut les questionner après coup pour les afficher (si on veut).

    On va ensuite créer un filtre portant sur les noms de fichier, permettant de ne retenir que les noms qui satisfont un motif donné, par exemple "*.py", et même sous Windows, "*.py;*.pyw":

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    class Filtrewildcard(object):
        """Dit si le nom de fichier donné satisfait le motif wildcard donné
           Le motif (type str) peut être plusieurs motifs séparés par des ';'
           Le fichier peut être donné avec ou sans son chemin
        """
     
        #========================================================================
        def __init__(self, motif="*"):
            self.motifs = motif.split(';') # plusieurs motifs séparés par ';'
     
        #========================================================================
        def __call__(self, fichier):
            nomfichier = os.path.basename(fichier) # élimine le chemin s'il y est
            for motif in self.motifs:
                if fnmatch(nomfichier, motif):
                    return True # on a trouvé un motif qui marche!
            return False # aucun des motifs donnés n'a été satisfait
    Et enfin, on va créer un filtre que dit si un fichier a été modifié à une date donné ou après:

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    class Filtredate(object):
        """Filtre un fichier (avec son chemin) selon sa date de modification. 
           La date dtime est donnée en type datetime: dtime=datetime(an,mois,jour)
           Les comparaisons de date ne tiennent pas compte des heures
        """
     
        #========================================================================
        def __init__(self, dtime=None):
            if dtime==None:
                self.datecible = date.today() # on prend la date d'aujourd'hui
            else:    
                self.datecible = dtime.date()
            self.epoch = datetime.utcfromtimestamp(0) # calcul de l'epoch de l'OS
     
        #========================================================================
        def apres(self, fichier):
            """Dit si le fichier a été modifié à la date dtime ou après
            """
            tempsfichier = os.path.getmtime(fichier)
            dtimefichier = (self.epoch + timedelta(seconds=tempsfichier))
            return dtimefichier.date() >= self.datecible
    Il ne reste plus qu'à combiner les filtres et les donner à la recherche. Voilà un exemple d'utilisation:

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
        # répertoire de recherche
        repertoire = r"C:\Python34"
     
        # filtre par wildcard: on cherche tous les scripts Python
        filtrenom = Filtrewildcard("*.py;*.pyw")
     
        # filtre par date: ici c'est le 17/5/2016 et après
        filtredate = Filtredate(datetime(2016,5,17)).apres
     
        # combine les 2 filtres
        filtre = lambda f: filtrenom(f) and filtredate(f)
     
        # cherche et affiche les fichiers satisfaisant aux filtres
        fichiers = Cherchefichiers(repertoire)
        nb = 0 # compteur
         for fichier in fichiers.cherche(filtre):
            datefichier = (datetime.utcfromtimestamp(0) + timedelta(seconds=os.path.getmtime(fichier))).strftime('%Y-%m-%d')
            print(fichier, datefichier) # on ajoute la date du fichier seulement pour vérifier
            nb += 1
        print()
        print("Nombre de fichiers trouvés:", nb)
        print()
    On peut si on veut consulter et afficher les erreurs après la recherche:

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
        # on affiche les erreurs s'il en a
        if fichiers.listerreurs==[]:
            print("Aucune erreur")
        else:
            print(len(fichiers.listerreurs), "Erreurs trouvées:")
            for erreur in fichiers.listerreurs:
                print(erreur)
    Cela affichera quelque chose comme:

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    C:\Python34\Lib\site-packages\easy_install.py 2016-08-25
    C:\Python34\Lib\site-packages\path.py 2016-05-17
    C:\Python34\Lib\site-packages\pyparsing.py 2016-05-17
    ...
    ...
    C:\Python34\Scripts\pyi-makespec-script.py 2016-05-17
    C:\Python34\Scripts\pyi-set_version-script.py 2016-05-17
    C:\Python34\Scripts\pyinstaller-script.py 2016-05-17
     
    Nombre de fichiers trouvés: 1104
     
    Aucune erreur
    Ce n'est qu'un exemple simple, mais qui peut être la base d'une recherche beaucoup plus approfondie:

    Pour le filtre par nom de fichier, on peut ajouter par exemple:
    - un filtre par expression régulière,
    - un filtre par recherche de mots similaires (avec ratio de proximité), utile si vous ne vous rappelez plus comment vous avez nommé "exactement" le fichier cherché.

    Pour ces filtres par nom, on peut ajouter l'option "tenir compte de la casse ou pas" (majuscules/minuscules). Pour ne pas tenir compte de la casse, il suffit en général pour la comparaison de convertir avant le nom de fichier ainsi que le motif, en majuscules. Sauf pour les expressions régulières pour lesquelles il existe une option prédéfinie à inclure dans la recherche.

    Pour le filtre par date, on peut ajouter:
    - les fichiers "avant" une date,
    - les fichiers de date "égale" à une date,
    - les fichiers "entre" 2 dates.
    - les fichiers créés ou modifiés le week-end?
    - etc...

    Vous pouvez aussi ajouter n'importe quel autre filtre qui vous intéresse, par exemple sur la taille du fichier (trouver tous les fichiers >1Go), ou même sur la longueur du nom de fichier (trouver tous les noms de fichiers de plus de 25 caractères)!

    Les fichiers trouvés sont affichés dans l'ordre de recherche par os.walk. Mais si on veut les présenter autrement, par exemple filtrés par ordre alphabétique, ou chronologique, ou par n'importe quel autre critère, on les met tous dans une liste qu'on trie ensuite avec le critère choisi (.sort(key=...)).

    Je vais essayer de trouver un peu de temps pour mettre tout ça dans un tuto sur mon site de recettes...

    J'ai fait ça avec Python 3.4.3 en respectant un codage multiplateforme: n'hésitez pas à me signaler les éventuels problèmes rencontrés!

    Pour mon usage perso, j'ai créé un programme graphique PyQt5 (.exe avec cx_freeze) pour faire toutes ces recherches à l'écran: ça marche très bien, c'est très pratique et mon but a été atteint.

    Amusez-vous bien!
    Un expert est une personne qui a fait toutes les erreurs qui peuvent être faites, dans un domaine étroit... (Niels Bohr)
    Mes recettes python: http://www.jpvweb.com

  2. #2
    Expert éminent Avatar de BufferBob
    Profil pro
    responsable R&D vidage de truites
    Inscrit en
    Novembre 2010
    Messages
    3 035
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations professionnelles :
    Activité : responsable R&D vidage de truites

    Informations forums :
    Inscription : Novembre 2010
    Messages : 3 035
    Points : 8 400
    Points
    8 400
    Par défaut
    salut,

    merci pour la contrib (avec un lien vers le code complet ça aurait été parfait ), je m'étais déjà posé vaguement la même question à savoir comment trouver facilement sous Windows des fichiers en se basant sur leur horodatage (...) bref la question qui m'est venue en voyant ce post c'était celle des perfs, donc essentiellement celles de os.walk() (ici avec Python 3.4)

    après quelques benchs rapide sur ma machine (temps mis pour lister tous les fichiers présents sur le disque, moins le temps d'output) :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    TotalSeconds      : 203,4162611	// PowserShell Get-ChildItem (!!!)
    TotalSeconds      : 173,3635433	// python2 + os.walk()
    TotalSeconds      : 60,8264129	// Cygwin find.exe
    TotalSeconds      : 40,8101082	// cmd.exe dir /s
    TotalSeconds      : 31,674726	// python2 + scandir.walk()
    il s'avère que os.walk() est _lent_ (pas autant que le Get-ChildItem natif du PowerShell mais pas loin) et que -ô comble du hasard qui fait parfois bien les choses- avec la version 3.5 de Python arrive os.scandir(), un remplaçant pour os.walk, dont on peut constater plus haut les bienfaits
    pour Python < 3.5 il faudra installer le module scandir explicitement, mais ça vaut le coup

  3. #3
    Expert éminent
    Avatar de tyrtamos
    Homme Profil pro
    Retraité
    Inscrit en
    Décembre 2007
    Messages
    4 462
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Var (Provence Alpes Côte d'Azur)

    Informations professionnelles :
    Activité : Retraité

    Informations forums :
    Inscription : Décembre 2007
    Messages : 4 462
    Points : 9 249
    Points
    9 249
    Billets dans le blog
    6
    Par défaut
    Bonjour BufferBob,

    Un grand merci pour tes infos!

    Citation Envoyé par BufferBob Voir le message
    avec un lien vers le code complet ça aurait été parfait )
    Ça, c'est facile: voilà le code assemblé:

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    # -*- coding: utf-8 -*-
    # Python 3
     
    import os
    from datetime import datetime, timedelta, date
    from fnmatch import fnmatch
     
     
    class Filtrewildcard(object):
        """Dit si le nom de fichier donné satisfait le motif wildcard donné
           Le motif (type str) peut être plusieurs motifs séparés par des ';'
           Le fichier peut être donné avec ou sans son chemin
        """
     
        #========================================================================
        def __init__(self, motif="*"):
            self.motifs = motif.split(';') # plusieurs motifs séparés par ';'
     
        #========================================================================
        def __call__(self, fichier):
            nomfichier = os.path.basename(fichier) # élimine le chemin s'il y est
            for motif in self.motifs:
                if fnmatch(nomfichier, motif):
                    return True # on a trouvé un motif qui marche!
            return False # aucun des motifs donnés n'a été satisfait
     
    class Filtredate(object):
        """Filtre un fichier (avec son chemin) selon sa date de modification. 
           La date dtime est donnée en type datetime: dtime=datetime(an,mois,jour)
           Les comparaisons de date ne tiennent pas compte des heures
        """
     
        #========================================================================
        def __init__(self, dtime=None):
            if dtime==None:
                self.datecible = date.today() # on prend la date d'aujourd'hui
            else:    
                self.datecible = dtime.date()
            self.epoch = datetime.utcfromtimestamp(0) # calcul de l'epoch de l'OS
     
        #========================================================================
        def apres(self, fichier):
            """Dit si le fichier a été modifié à la date dtime ou après
            """
            tempsfichier = os.path.getmtime(fichier)
            dtimefichier = (self.epoch + timedelta(seconds=tempsfichier))
            return dtimefichier.date() >= self.datecible
     
    class Cherchefichiers(object):
        """Permet de trouver tous les fichiers d'un répertoire et de ses 
           sous-répertoire qui répond à des tests de filtrage
        """
     
        #========================================================================
        def __init__(self, repertoire=""):
            # Enregistre le répertoire fourni après sa normalisation
            self.repertoire = os.path.abspath(os.path.expanduser(repertoire))
            # initialise la liste des erreurs consultable en fin de recherche
            self.listerreurs = [] 
     
        #========================================================================
        def fnerreurs(self, msgerr):
            """Reçoit et enregistre les éventuels messages d'erreur de os.walk
            """
            self.listerreurs.append([msgerr.errno, msgerr.strerror, msgerr.filename])
     
        #========================================================================
        def cherche(self, filtre=None):
            """Trouve les fichiers du répertoire et de ses sous-répertoires
               qui satisfont le filtre donné
            """
            if filtre==None:
                filtre = lambda x: True # sans filtre donné, on accepte tout
            self.listerreurs = [] # remet à vide la liste des erreurs
            for repert, sreps, fics in os.walk(self.repertoire, onerror=self.fnerreurs):
                for fic in fics:
                    fichier = os.path.join(repert, fic) # ajout du chemin
                    try:
                        os.path.getsize(fichier) # uniquement pour valider l'accès
                        if filtre(fichier):
                            yield fichier
                    except OSError as msgerr:
                        self.fnerreurs(msgerr)
     
    # répertoire de recherche
    repertoire = r"C:\Python34"
     
    # filtre par wildcard: on cherche tous les scripts Python
    filtrenom = Filtrewildcard("*.py;*.pyw")
     
    # filtre par date: ici c'est le 17/5/2016 et après
    filtredate = Filtredate(datetime(2016,5,17)).apres
     
    # combine les 2 filtres
    filtre = lambda f: filtrenom(f) and filtredate(f)
     
    # cherche et affiche les fichiers satisfaisant aux filtres
    fichiers = Cherchefichiers(repertoire)
    nb = 0 # compteur
    for fichier in fichiers.cherche(filtre):
        datefichier = (datetime.utcfromtimestamp(0) + timedelta(seconds=os.path.getmtime(fichier))).strftime('%Y-%m-%d')
        print(fichier, datefichier) # on ajoute la date du fichier seulement pour vérifier
        nb += 1
    print()
    print("Nombre de fichiers trouvés:", nb)
    print()
     
    # on affiche les erreurs s'il en a
    if fichiers.listerreurs==[]:
        print("Aucune erreur")
    else:
        print(len(fichiers.listerreurs), "Erreurs trouvées:")
        for erreur in fichiers.listerreurs:
            print(erreur)
    Pour le reste, merci pour l'info concernant scandir! Comme je n'utilise pas encore Python 3.5 (à cause de cx_freeze), j'ai installé scandir avec pip (c'est facile: "pip install scandir"): c'est effectivement plus rapide, mais dans les recherches que je fais, je ne trouve que 10% d'écart. Mais même avec os.walk, ça fait tout de même beaucoup de travail en peu de temps!

    Par exemple, dans un répertoire de développement de 47Go contenant plus de 400.000 fichiers et 37.000 répertoires, je cherche les fichiers python (*.py et *.pyw) postérieurs au 17/5/2016. J'en trouve 4979 en 52 secondes (46s avec scandir). Imaginons un peu la même recherche à la main...

    Mais en fait, le principal avantage de la solution proposée, c'est surtout la possibilité d'ajouter facilement des critères de filtrage qu'on ne trouve pas autrement, grâce au caractère "boite de construction" du code.

    Par exemple, voilà un filtre nouveau basé sur les expressions régulières:

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    class Filtreregex(object):
        """Dit si le texte donné satisfait le motif regex donné
           en tenant compte de la casse (True) ou pas (False)
        """
     
        #========================================================================
        def __init__(self, motif="", casse=False):
            if casse:
                # Tient compte de la casse (maj/min)
                self.filtre = re.compile(motif)  
            else:    
                # Ne tient pas compte de la casse (maj/min)
                self.filtre = re.compile(motif, re.IGNORECASE) 
     
        #========================================================================
        def __call__(self, fichier):
            nomfichier = os.path.basename(fichier) # élimine le chemin s'il y est
            return self.filtre.match(nomfichier)!=None
    Il n'y a rien à changer au code précédent, à part ajouter la classe ci-dessus et d'importer le module "re". Il suffira ensuite de créer le motif regex qui va bien, et on pourra trouver les fichiers qu'on cherche.

    Par exemple, dans le répertoire d'installation de Python, je cherche tous les fichiers dont le nom contient "comp" avec le motif regex: r"^.*comp.*$". Je trouve 506 fichiers en moins de 3 secondes (sur les 32.600 fichiers du répertoire).


    Voilà un autre filtre facile à construire et qui fait des choses difficiles à trouver ailleurs: la recherche de mots similaires!

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    class Filtresimil(object):
        """Filtre le texte avec un motif de type "mot similaire" avec ratio de
           similitude. En tenant compte de la casse (True) ou pas (False)
        """
     
        #========================================================================
        def __init__(self, motif="", ratio=0.75, casse=False):
            self.motif = motif
            self.casse = casse
            if not self.casse:
                # Ne pas tenir compte de la casse => comparaison en majuscules
                self.motif = self.motif.upper()
            self.ratio = ratio
     
        #========================================================================
        def __call__(self, fichier):
            nomfichier = os.path.basename(fichier) # élimine le chemin s'il y est
            if not self.casse:
                # Ne pas tenir compte de la casse => comparaison en majuscules
                nomfichier = nomfichier.upper()
            return SequenceMatcher(None, self.motif, nomfichier).ratio() >= self.ratio
    Il suffit d'ajouter ce code et d'importer SequenceMatcher (from difflib import SequenceMatcher) et on peut l'utiliser:

    Par exemple, dans le répertoire d'installation de Python, je cherche les noms de fichiers proches de "passer.py" avec un ratio de similitude de 0.80 (1.0 = identité). Je trouve en moins de 5 secondes 16 fichiers: tous les "parser.py", les "parse.py" et un "uiparser.py".
    Un expert est une personne qui a fait toutes les erreurs qui peuvent être faites, dans un domaine étroit... (Niels Bohr)
    Mes recettes python: http://www.jpvweb.com

Discussions similaires

  1. Mais où sont donc stockées les Macros Word ?
    Par deca2 dans le forum VBA Word
    Réponses: 6
    Dernier message: 28/08/2018, 03h23
  2. [AC-2013] Mais où sont donc les macros ?
    Par PapyMouzot dans le forum Macros Access
    Réponses: 1
    Dernier message: 26/11/2013, 08h37
  3. Mais où sont donc passés les développeurs Java ?
    Par Stéphane le calme dans le forum Actualités
    Réponses: 86
    Dernier message: 21/08/2013, 18h02
  4. Comment classifier les fichiers autre que par nom?
    Par masta64 dans le forum Windows Vista
    Réponses: 4
    Dernier message: 07/03/2008, 09h49
  5. Réponses: 5
    Dernier message: 08/01/2008, 23h29

Partager

Partager
  • Envoyer la discussion sur Viadeo
  • Envoyer la discussion sur Twitter
  • Envoyer la discussion sur Google
  • Envoyer la discussion sur Facebook
  • Envoyer la discussion sur Digg
  • Envoyer la discussion sur Delicious
  • Envoyer la discussion sur MySpace
  • Envoyer la discussion sur Yahoo