Ordine elenco non alfanumerico da os.listdir ()


109

Uso spesso Python per elaborare directory di dati. Recentemente, ho notato che l'ordine predefinito degli elenchi è cambiato in qualcosa di quasi senza senso. Ad esempio, se mi trovo in una directory corrente contenente le seguenti sottodirectory: run01, run02, ... run19, run20, quindi creo un elenco dal seguente comando:

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

quindi di solito ottengo un elenco in questo ordine:

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

e così via. L'ordine era alfanumerico. Ma questo nuovo ordine è rimasto con me per un po '.

Cosa determina l'ordine (visualizzato) di questi elenchi?


L'ordine all'interno degli elenchi Python è effettivamente rilevante (cioè gli elenchi sono ordinati). Sono d'accordo con Nowayz: lo strano ordine che stai vedendo è probabilmente una funzione del file system. L'ho visto accadere alcuni anni fa con un file system di rete di terze parti collegato a un Mac.
David P Simons,

Grazie per le informazioni, ho rimosso il commento sull'ordine dell'elenco.
marshall.ward

@ shog9 Ok, ora posso vedere che la domanda è stata posta e il tipo di risposta (il modo di ordinare i dati non è mai stato fornito nella risposta collegata) ma l'argomento della domanda non era molto chiaro (facendo una ricerca la risposta non è apparsa) e le etichette non sono state molto utili
Dimitris

@ Dimitris: questa è una critica giusta: ho rinominato questa e ho unito le due domande, quindi ora entrambe le serie di risposte possono essere trovate qui e la tua rimane indicante.
Shog9

A proposito, se qualcun altro è confuso come me riguardo alle risposte qui, è perché la mia domanda è stata fusa con un'altra domanda che richiede un listdiroutput ordinato . Non sono sicuro del motivo per cui le domande siano state unite.
marshall.ward

Risposte:


63

Penso che l'ordine abbia a che fare con il modo in cui i file sono indicizzati sul tuo FileSystem. Se vuoi davvero che aderisca a un certo ordine, puoi sempre ordinare l'elenco dopo aver ottenuto i file.


128

Puoi usare la sortedfunzione incorporata per ordinare le stringhe come preferisci. In base a ciò che descrivi,

sorted(os.listdir(whatever_directory))

In alternativa, puoi utilizzare il .sortmetodo di un elenco:

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

Penso che dovrebbe fare il trucco.

Nota che l'ordine che os.listdirottiene i nomi dei file dipende probabilmente completamente dal tuo filesystem.


1
Non cambia l'ordine se si tratta di nomi di file con primi numeri (cioè 59.9780radps-0096 è ancora precedente a 9.9746radps-0082). Penso che sia perché tutto è una stringa, quindi il decimale non viene trattato correttamente.
Elliot

2
Oppure usa la libreria natsort, che ho appena trovato.
Elliot

5
Ha sorted(listdir)funzionato solo per me. listdir.sort()mi ha dato: TypeError: l'oggetto 'NoneType' non è iterabile
paul_h

1
@AlexB - certo ... basta passare reverse=Trueper renderlo un ordinamento discendente.
mgilson

1
@ user3895596 - Penso che la sortedcosa scritta per prima sia su una sola riga OK?
mgilson

43

Secondo la documentazione :

os.listdir (percorso)

Restituisce un elenco contenente i nomi delle voci nella directory data da path. L'elenco è in ordine arbitrario . Non include le voci speciali "." e ".." anche se sono presenti nella directory.

Non si può fare affidamento sull'ordine ed è un artefatto del filesystem.

Per ordinare il risultato, usa sorted(os.listdir(path)).


27

Python per qualsiasi motivo non viene fornito con un modo integrato per avere l'ordinamento naturale (che significa 1, 2, 10 invece di 1, 10, 2), quindi devi scriverlo da solo:

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)

È ora possibile utilizzare questa funzione per ordinare un elenco:

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

PROBLEMI: Nel caso in cui utilizzi la funzione sopra per ordinare le stringhe (ad esempio i nomi delle cartelle) e desideri che vengano ordinate come fa Windows Explorer, non funzionerà correttamente in alcuni casi limite.
Questa funzione di ordinamento restituirà risultati errati su Windows, se sono presenti nomi di cartelle con determinati caratteri "speciali". Ad esempio, questa funzione ordinerà 1, !1, !a, a, mentre Windows Explorer ordinerà !1, 1, !a, a.

Quindi, se vuoi ordinare esattamente come fa Windows Explorer in Python, devi usare la funzione incorporata di Windows StrCmpLogicalW tramite ctypes (questo ovviamente non funzionerà su 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))

Questa funzione è leggermente più lenta di sorted_alphanumeric().

Bonus: winsortpuò anche ordinare percorsi completi su Windows .

In alternativa, specialmente se usi Unix, puoi usare natsortlibrary ( pip install natsort) per ordinare i percorsi completi in modo corretto (cioè sottocartelle nella posizione corretta).

Puoi usarlo in questo modo per ordinare i percorsi completi:

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

Non usarlo per il normale ordinamento dei soli nomi di cartelle (o stringhe in generale), poiché è un po 'più lento di quanto sorted_alphanumeric()funzioni sopra.
natsortedlibrary ti darà risultati errati se ti aspetti l'ordinamento di Windows Explorer, quindi usa winsort()per quello.


Funziona perfettamente bene. print( sorted_aphanumeric(["1", "10", "2", "foo_10", "foo_8"]) )-> ['1', '2', '10', 'foo_8', 'foo_10']. Esattamente come previsto.
user136036

Esiste un problema aperto di lunga data natsortedper l'implementazione della funzionalità di corrispondenza di Windows Explorer. Forse dovresti contribuire con una soluzione? github.com/SethMMorton/natsort/issues/41
SethMMorton

8

Penso che per impostazione predefinita l'ordine sia determinato con il valore ASCII. La soluzione a questo problema è questa

dir = sorted(os.listdir(os.getcwd()), key=len)

5

Probabilmente è solo l'ordine che readdir()restituisce C. Prova a eseguire questo programma 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 linea di costruzione dovrebbe essere qualcosa di simile gcc -o foo foo.c.

PS Ho appena eseguito questo e il tuo codice Python, ed entrambi mi hanno fornito un output ordinato, quindi non posso riprodurre ciò che stai vedendo.


1
Il motivo per cui visualizzi l'output di Soted può dipendere da molti fattori, come OS, filesystem, ora di creazione dei file, azioni durante l'ultima deframmentazione, ...
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]))

Come nel caso del mio requisito, ho il caso come row_163.pklqui os.path.splitext('row_163.pkl')lo spezzerò('row_163', '.pkl') modo da doverlo dividere anche in base a '_'.

ma in caso di tua esigenza puoi fare qualcosa di simile

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

dove

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

e anche per il recupero della directory puoi farlo sorted(os.listdir(path))

e per il caso di like 'run01.txt'o 'run01.csv'puoi fare così

sorted(files, key=lambda x : int(os.path.splitext(x)[0]))

Indiscutibilmente la migliore risposta qui.
Amit Amola

2

Ho scoperto che "sort" non fa sempre quello che mi aspettavo. ad esempio, ho una directory come di seguito e "sort" mi dà un risultato molto strano:

>>> 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']

Sembra che confronti per primo il primo personaggio, se questo è il più grande, sarebbe l'ultimo.


2
Questo è un comportamento previsto. ('5' > '403') is True.
AXO

2
@AXO è corretto, perché a questo punto stai confrontando l'ordinamento alfanumerico, non i valori quantitativi dei numeri. Per ottenere un ordinamento simile alle tue aspettative, potresti utilizzare il riempimento numerico nelle cartelle ... ["002", "003", "004", "005", "403", "404", " 405 ',' 406 ']
Andrew,

2

Dalla documentazione :

L'elenco è in ordine arbitrario e non include le voci speciali "." e ".." anche se sono presenti nella directory.

Ciò significa che l'ordine dipende probabilmente dal sistema operativo o dal file system, non ha un ordine particolarmente significativo e quindi non è garantito che sia qualcosa in particolare. Come molte risposte menzionate: se si preferisce, l'elenco recuperato può essere ordinato.

Saluti :)


2

Elliot risposta risolve perfettamente ma siccome è un commento, passa inosservato quindi con l'obiettivo di aiutare qualcuno, lo ripeto come soluzione.

Usa la libreria natsort:

Installa la libreria con il seguente comando per Ubuntu e altre versioni di Debian

Python 2

sudo pip install natsort

Python 3

sudo pip3 install natsort

I dettagli su come utilizzare questa libreria si trovano qui


1
Questo è più accurato di sorted()! Grazie
Färid Alijani

1

La combinazione proposta di comandi os.listdire sortedgenera lo stesso risultato del ls -lcomando sotto Linux. Il seguente esempio verifica questo presupposto:

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$ 

Quindi, per qualcuno che vuole riprodurre il risultato del noto ls -lcomando nel proprio codice Python, sorted( os.listdir( DIR ) )funziona abbastanza bene.


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.

1
Questo spiega perché stanno vedendo il comportamento, senza offrire una soluzione.
Daniel Watkins,

1
OP voglio solo sapere perché, non come.
Denis

@ Denis, grazie per averlo sottolineato - non l'avevo notato prima
Dimitris

@DanielWatkins OK, non lo è.)
Denis
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.