Ordre des listes non alphanumériques depuis os.listdir ()

109

J'utilise souvent python pour traiter des répertoires de données. Récemment, j'ai remarqué que l'ordre par défaut des listes a changé pour quelque chose de presque absurde. Par exemple, si je suis dans un répertoire courant contenant les sous-répertoires suivants: run01, run02, ... run19, run20, puis je génère une liste à partir de la commande suivante:

dir = os.listdir(os.getcwd())

alors j'obtiens généralement une liste dans cet ordre:

dir = ['run01', 'run18', 'run14', 'run13', 'run12', 'run11', 'run08', ... ]

etc. L'ordre était alphanumérique. Mais ce nouvel ordre m'est resté depuis un moment maintenant.

Qu'est-ce qui détermine l'ordre (affiché) de ces listes?

marshall.ward
la source
L'ordre dans les listes python est en fait pertinent (c'est-à-dire que les listes sont ordonnées). Je suis d'accord avec Nowayz: l'ordre étrange que vous voyez est probablement une fonction du système de fichiers. J'ai vu cela se produire il y a quelques années avec un système de fichiers réseau tiers attaché à un mac.
David P Simons
Merci pour l'info, j'ai supprimé le commentaire d'ordre de la liste.
marshall.ward
@ shog9 Ok, maintenant je peux voir que la question a été posée et qu'elle a répondu (la manière de trier les données n'a jamais été fournie dans la réponse liée) mais le sujet de la question n'était pas très clair (en effectuant une recherche, la réponse n'apparaissait pas) et les balises n'étaient pas très utiles
Dimitris
@Dimitris: c'est une critique juste - j'ai renommé celle-ci et fusionné les deux questions, donc maintenant les deux ensembles de réponses peuvent être trouvés ici et le vôtre continue de le pointer.
Shog9
BTW si quelqu'un d'autre est aussi confus que moi au sujet des réponses ici, c'est parce que ma question a été fusionnée avec une autre question demandant une listdirsortie triée . Je ne sais pas pourquoi les questions ont été fusionnées.
marshall.ward

Réponses:

63

Je pense que la commande a à voir avec la façon dont les fichiers sont indexés sur votre FileSystem. Si vous voulez vraiment le faire adhérer à un ordre, vous pouvez toujours trier la liste après avoir récupéré les fichiers.

Nowayz
la source
128

Vous pouvez utiliser la sortedfonction intégrée pour trier les chaînes comme vous le souhaitez. Sur la base de ce que vous décrivez,

sorted(os.listdir(whatever_directory))

Vous pouvez également utiliser la .sortméthode d'une liste:

lst = os.listdir(whatever_directory)
lst.sort()

Je pense que ça devrait faire l'affaire.

Notez que l'ordre dans lequel os.listdirles noms de fichiers sont obtenus dépend probablement complètement de votre système de fichiers.

mgilson
la source
1
Ne change pas l'ordre s'il s'agit de noms de fichiers numérotés en premier (c'est-à-dire que 59.9780radps-0096 est toujours avant 9.9746radps-0082). Je pense que c'est parce que tout est une chaîne, donc la décimale n'est pas traitée correctement.
Elliot
2
Ou utilisez la bibliothèque natsort, que je viens de trouver.
Elliot
5
Seulement sorted(listdir)travaillé pour moi. listdir.sort()m'a donné: TypeError: l'objet 'NoneType' n'est pas itérable
paul_h
1
@AlexB - bien sûr ... il suffit de passer reverse=Truepour faire un tri décroissant.
mgilson
1
@ user3895596 - Je pense que la sortedchose écrite en premier le fait en une seule ligne OK?
mgilson
43

Selon la documentation :

os.listdir (chemin)

Renvoie une liste contenant les noms des entrées dans le répertoire donné par chemin. La liste est dans un ordre arbitraire . Il n'inclut pas les entrées spéciales «.» et '..' même s'ils sont présents dans le répertoire.

L'ordre ne peut être invoqué et est un artefact du système de fichiers.

Pour trier le résultat, utilisez sorted(os.listdir(path)).

Mark Tolonen
la source
28

Python pour une raison quelconque ne vient pas avec un moyen intégré d'avoir un tri naturel (ce qui signifie 1, 2, 10 au lieu de 1, 10, 2), vous devez donc l'écrire vous-même:

import re
def sorted_alphanumeric(data):
    convert = lambda text: int(text) if text.isdigit() else text.lower()
    alphanum_key = lambda key: [ convert(c) for c in re.split('([0-9]+)', key) ] 
    return sorted(data, key=alphanum_key)

Vous pouvez maintenant utiliser cette fonction pour trier une liste:

dirlist = sorted_alphanumeric(os.listdir(...))

PROBLÈMES: Si vous utilisez la fonction ci-dessus pour trier les chaînes (par exemple les noms de dossier) et que vous voulez les trier comme le fait l'Explorateur Windows, cela ne fonctionnera pas correctement dans certains cas extrêmes.
Cette fonction de tri renverra des résultats incorrects sous Windows, si vous avez des noms de dossier contenant certains caractères «spéciaux». Par exemple, cette fonction triera1, !1, !a, a , alors que l'Explorateur Windows triera !1, 1, !a, a.

Donc, si vous voulez trier exactement comme l'explorateur Windows le fait en Python, vous devez utiliser la fonction intégrée de Windows StrCmpLogicalW via ctypes (cela ne fonctionnera bien sûr pas sous Unix):

from ctypes import wintypes, windll
from functools import cmp_to_key
def winsort(data):
    _StrCmpLogicalW = windll.Shlwapi.StrCmpLogicalW
    _StrCmpLogicalW.argtypes = [wintypes.LPWSTR, wintypes.LPWSTR]
    _StrCmpLogicalW.restype  = wintypes.INT

    cmp_fnc = lambda psz1, psz2: _StrCmpLogicalW(psz1, psz2)
    return sorted(data, key=cmp_to_key(cmp_fnc))

Cette fonction est légèrement plus lente que sorted_alphanumeric().

Bonus: winsortpeut également trier les chemins complets sous Windows .

Alternativement, surtout si vous utilisez Unix, vous pouvez utiliser la natsortbibliothèque ( pip install natsort) pour trier par chemins complets d'une manière correcte (c'est-à-dire les sous-dossiers à la bonne position).

Vous pouvez l'utiliser comme ceci pour trier les chemins complets:

from natsort import natsorted, ns
dirlist = natsorted(dirlist, alg=ns.PATH | ns.IGNORECASE)

Ne l'utilisez pas pour le tri normal des noms de dossiers (ou des chaînes en général), car c'est un peu plus lent que la sorted_alphanumeric()fonction ci-dessus.
natsortedLa bibliothèque vous donnera des résultats incorrects si vous vous attendez à un tri dans l'Explorateur Windows, alors utilisez-le winsort()pour cela.

user136036
la source
Fonctionne parfaitement bien. print( sorted_aphanumeric(["1", "10", "2", "foo_10", "foo_8"]) )-> ['1', '2', '10', 'foo_8', 'foo_10']. Exactement comme prévu.
user136036
Il existe un problème de longue date natsortedpour la mise en œuvre de la fonctionnalité de correspondance de l'Explorateur Windows. Peut-être devriez-vous apporter une solution? github.com/SethMMorton/natsort/issues/41
SethMMorton le
8

Je pense que par défaut, l'ordre est déterminé avec la valeur ASCII. La solution à ce problème est la suivante

dir = sorted(os.listdir(os.getcwd()), key=len)
Zied Khlif
la source
5

C'est probablement juste l'ordre que readdir()retourne C. Essayez d'exécuter ce programme C:

#include <dirent.h>
#include <stdio.h>
int main(void)
{   DIR *dirp;
    struct dirent* de;
    dirp = opendir(".");
    while(de = readdir(dirp)) // Yes, one '='.
        printf("%s\n", de->d_name);
    closedir(dirp);
    return 0;
}

La ligne de construction devrait être quelque chose comme gcc -o foo foo.c.

PS J'ai juste exécuté ceci et votre code Python, et ils m'ont tous les deux donné une sortie triée, donc je ne peux pas reproduire ce que vous voyez.

Mike DeSimone
la source
1
La raison pour laquelle vous voyez une sortie sotée peut dépendre de nombreux facteurs, tels que le système d'exploitation, le système de fichiers, l'heure de création des fichiers, les actions lors de la dernière défragmentation, ...
Joachim Sauer
4
aaa = ['row_163.pkl', 'row_394.pkl', 'row_679.pkl', 'row_202.pkl', 'row_1449.pkl', 'row_247.pkl', 'row_1353.pkl', 'row_749.pkl', 'row_1293.pkl', 'row_1304.pkl', 'row_78.pkl', 'row_532.pkl', 'row_9.pkl', 'row_1435.pkl']                                                                                                                                                                                                                                                                                                 
sorted(aaa, key=lambda x: int(os.path.splitext(x.split('_')[1])[0]))

Comme dans le cas de mes besoins, j'ai le cas comme row_163.pklici, os.path.splitext('row_163.pkl')je vais le diviser en ('row_163', '.pkl')donc je dois le diviser en fonction de '_' également.

mais en cas de besoin, vous pouvez faire quelque chose comme

sorted(aa, key = lambda x: (int(re.sub('\D','',x)),x))

aa = ['run01', 'run08', 'run11', 'run12', 'run13', 'run14', 'run18']

et aussi pour la récupération de répertoire, vous pouvez faire sorted(os.listdir(path))

et pour le cas 'run01.txt'ou 'run01.csv'vous pouvez faire comme ça

sorted(files, key=lambda x : int(os.path.splitext(x)[0]))
rajeshcis
la source
Incontestablement la meilleure réponse ici.
Amit Amola il y a
2

J'ai trouvé que le «tri» ne faisait pas toujours ce que j'attendais. par exemple, j'ai un répertoire comme ci-dessous, et le "tri" me donne un résultat très étrange:

>>> os.listdir(pathon)
['2', '3', '4', '5', '403', '404', '407', '408', '410', '411', '412', '413', '414', '415', '416', '472']
>>> sorted([ f for f in os.listdir(pathon)])
['2', '3', '4', '403', '404', '407', '408', '410', '411', '412', '413', '414', '415', '416', '472', '5']

Il semble qu'il compare le premier personnage en premier, si c'est le plus gros, ce serait le dernier.

Jue
la source
2
Il s'agit d'un comportement attendu. ('5' > '403') is True.
AXO
2
@AXO est correct, car à ce stade, vous comparez le tri alphanumérique et non les valeurs quantitatives des nombres. Afin d'obtenir un tri similaire à vos attentes, vous pouvez utiliser un remplissage numérique sur vos dossiers ... ['002', '003', '004', '005', '403', '404', ' 405 ',' 406 ']
Andrew
2

De la documentation :

La liste est dans un ordre arbitraire et n'inclut pas les entrées spéciales «.» et '..' même s'ils sont présents dans le répertoire.

Cela signifie que l'ordre dépend probablement du système d'exploitation / du système de fichiers, n'a pas d'ordre particulièrement significatif et n'est donc pas garanti d'être quelque chose de particulier. Autant de réponses mentionnées: si vous préférez, la liste récupérée peut être triée.

À votre santé :)

Code élégant
la source
2

La réponse d'Elliot le résout parfaitement mais parce que c'est un commentaire, cela passe inaperçu donc dans le but d'aider quelqu'un, je le réitère comme une solution.

Utilisez la bibliothèque natsort:

Installez la bibliothèque avec la commande suivante pour Ubuntu et d'autres versions de Debian

Python 2

sudo pip install natsort

Python 3

sudo pip3 install natsort

Les détails de l'utilisation de cette bibliothèque se trouve ici

Rockyne
la source
1
C'est plus précis que sorted()! Merci
Färid Alijani
1

La combinaison proposée des commandes os.listdiret sortedgénère le même résultat que la ls -lcommande sous Linux. L'exemple suivant vérifie cette hypothèse:

user@user-PC:/tmp/test$ touch 3a 4a 5a b c d1 d2 d3 k l p0 p1 p3 q 410a 409a 408a 407a
user@user-PC:/tmp/test$ ls -l
total 0
-rw-rw-r-- 1 user user 0 Feb  15 10:31 3a
-rw-rw-r-- 1 user user 0 Feb  15 10:31 407a
-rw-rw-r-- 1 user user 0 Feb  15 10:31 408a
-rw-rw-r-- 1 user user 0 Feb  15 10:31 409a
-rw-rw-r-- 1 user user 0 Feb  15 10:31 410a
-rw-rw-r-- 1 user user 0 Feb  15 10:31 4a
-rw-rw-r-- 1 user user 0 Feb  15 10:31 5a
-rw-rw-r-- 1 user user 0 Feb  15 10:31 b
-rw-rw-r-- 1 user user 0 Feb  15 10:31 c
-rw-rw-r-- 1 user user 0 Feb  15 10:31 d1
-rw-rw-r-- 1 user user 0 Feb  15 10:31 d2
-rw-rw-r-- 1 user user 0 Feb  15 10:31 d3
-rw-rw-r-- 1 user user 0 Feb  15 10:31 k
-rw-rw-r-- 1 user user 0 Feb  15 10:31 l
-rw-rw-r-- 1 user user 0 Feb  15 10:31 p0
-rw-rw-r-- 1 user user 0 Feb  15 10:31 p1
-rw-rw-r-- 1 user user 0 Feb  15 10:31 p3
-rw-rw-r-- 1 user user 0 Feb  15 10:31 q

user@user-PC:/tmp/test$ python
Python 2.7.6 (default, Jun 22 2015, 17:58:13) 
[GCC 4.8.2] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> import os
>>> os.listdir( './' )
['d3', 'k', 'p1', 'b', '410a', '5a', 'l', 'p0', '407a', '409a', '408a', 'd2', '4a', 'p3', '3a', 'q', 'c', 'd1']
>>> sorted( os.listdir( './' ) )
['3a', '407a', '408a', '409a', '410a', '4a', '5a', 'b', 'c', 'd1', 'd2', 'd3', 'k', 'l', 'p0', 'p1', 'p3', 'q']
>>> exit()
user@user-PC:/tmp/test$ 

Donc, pour quelqu'un qui veut reproduire le résultat de la ls -lcommande bien connue dans son code python, cela sorted( os.listdir( DIR ) )fonctionne plutôt bien.

trouille
la source
0
In [6]: os.listdir?

Type:       builtin_function_or_method
String Form:<built-in function listdir>
Docstring:
listdir(path) -> list_of_strings
Return a list containing the names of the entries in the directory.
path: path of directory to list
The list is in **arbitrary order**.  It does not include the special
entries '.' and '..' even if they are present in the directory.
Denis
la source
1
Cela explique pourquoi ils voient le comportement, sans proposer de solution.
Daniel Watkins
1
OP veut juste savoir pourquoi, pas comment.
Denis
@Denis merci d'avoir signalé cela - je ne l'avais pas remarqué auparavant
Dimitris
@DanielWatkins OK, pas ce n'est pas.)
Denis