Orden de lista no alfanumérico de os.listdir ()

108

A menudo uso Python para procesar directorios de datos. Recientemente, he notado que el orden predeterminado de las listas ha cambiado a algo casi sin sentido. Por ejemplo, si estoy en un directorio actual que contiene los siguientes subdirectorios: run01, run02, ... run19, run20, y luego genero una lista con el siguiente comando:

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

entonces normalmente obtengo una lista en este orden:

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

y así. El orden solía ser alfanumérico. Pero este nuevo orden ha permanecido conmigo por un tiempo.

¿Qué está determinando el orden (mostrado) de estas listas?

Marshall.ward
fuente
El orden dentro de las listas de Python es realmente relevante (es decir, las listas están ordenadas). Estoy de acuerdo con Nowayz: el extraño orden que está viendo probablemente sea una función del sistema de archivos. Vi que esto sucedía hace unos años con un sistema de archivos de red de terceros adjunto a una Mac.
David P Simons
Gracias por la información, eliminé el comentario de orden de lista.
marshall.ward
@ shog9 Ok, ahora puedo ver que se hizo la pregunta y que se respondió (la forma de ordenar los datos nunca se proporcionó en la respuesta vinculada) pero el tema de la pregunta no estaba muy claro (al hacer una búsqueda, la respuesta no apareció) y las etiquetas no fueron muy útiles
Dimitris
@Dimitris: esa es una crítica justa. He retitulado esta y he fusionado las dos preguntas, por lo que ahora ambos conjuntos de respuestas se pueden encontrar aquí y el suyo sigue apuntando a ello.
Shog9
Por cierto, si alguien más está tan confundido como yo acerca de las respuestas aquí, es porque mi pregunta se fusionó con otra pregunta que solicita una listdirsalida ordenada . No estoy seguro de por qué se fusionaron las preguntas.
marshall.ward

Respuestas:

63

Creo que el orden tiene que ver con la forma en que se indexan los archivos en su sistema de archivos. Si realmente desea que se adhiera a algún orden, siempre puede ordenar la lista después de obtener los archivos.

Nowayz
fuente
128

Puede utilizar la sortedfunción incorporada para ordenar las cadenas como desee. Basado en lo que describe,

sorted(os.listdir(whatever_directory))

Alternativamente, puede utilizar el .sortmétodo de una lista:

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

Creo que debería hacer el truco.

Tenga en cuenta que el orden en el que se os.listdirobtienen los nombres de archivo probablemente dependa por completo de su sistema de archivos.

mgilson
fuente
1
No cambia el orden si se trata de nombres de archivo con el número primero (es decir, 59.9780radps-0096 todavía es anterior a 9.9746radps-0082). Creo que es porque todo es una cadena, por lo que el decimal no se trata correctamente.
Elliot
2
O use la biblioteca natsort, que acabo de encontrar.
Elliot
5
Solo sorted(listdir)funcionó para mí. listdir.sort()me dio: TypeError: el objeto 'NoneType' no es iterable
paul_h
1
@AlexB - claro ... solo pasa reverse=Truepara que sea de orden descendente.
mgilson
1
@ user3895596 - Creo que lo sortedque está escrito primero lo hace en una sola línea, ¿de acuerdo?
mgilson
43

Según la documentación :

os.listdir (ruta)

Devuelve una lista que contiene los nombres de las entradas en el directorio proporcionado por la ruta. La lista está en orden arbitrario . No incluye las entradas especiales '.' y '..' incluso si están presentes en el directorio.

No se puede confiar en el orden y es un artefacto del sistema de archivos.

Para ordenar el resultado, use sorted(os.listdir(path)).

Mark Tolonen
fuente
26

Python, por la razón que sea, no viene con una forma incorporada de tener una clasificación natural (es decir, 1, 2, 10 en lugar de 1, 10, 2), por lo que debe escribirlo usted mismo:

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)

Ahora puede usar esta función para ordenar una lista:

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

PROBLEMAS: En caso de que utilice la función anterior para ordenar cadenas (por ejemplo, nombres de carpetas) y desee ordenarlas como lo hace el Explorador de Windows, no funcionará correctamente en algunos casos extremos.
Esta función de clasificación devolverá resultados incorrectos en Windows, si tiene nombres de carpeta con ciertos caracteres 'especiales' en ellos. Por ejemplo, esta función ordenará 1, !1, !a, a, mientras que el Explorador de Windows ordenará !1, 1, !a, a.

Entonces, si desea ordenar exactamente como lo hace el Explorador de Windows en Python , debe usar la función incorporada de Windows StrCmpLogicalW a través de ctypes (esto, por supuesto, no funcionará en 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))

Esta función es un poco más lenta que sorted_alphanumeric().

Bonificación: winsorttambién puede ordenar rutas completas en Windows .

Alternativamente, especialmente si usa Unix, puede usar la natsortbiblioteca ( pip install natsort) para ordenar por rutas completas de manera correcta (es decir, subcarpetas en la posición correcta).

Puede usarlo así para ordenar rutas completas:

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

No lo use para la clasificación normal de solo nombres de carpetas (o cadenas en general), ya que es un poco más lento que la sorted_alphanumeric()función anterior.
natsortedla biblioteca le dará resultados incorrectos si espera la clasificación del Explorador de Windows, así que úselo winsort()para eso.

user136036
fuente
Funciona perfectamente bien. print( sorted_aphanumeric(["1", "10", "2", "foo_10", "foo_8"]) )-> ['1', '2', '10', 'foo_8', 'foo_10']. Exactamente como se esperaba.
user136036
Existe un problema abierto de larga data natsortedpara implementar la funcionalidad de coincidencia del Explorador de Windows. ¿Quizás deberías aportar una solución? github.com/SethMMorton/natsort/issues/41
SethMMorton
8

Creo que, de forma predeterminada, el orden se determina con el valor ASCII. La solución a este problema es esta

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

Probablemente sea solo el orden en que readdir()regresa C. Intente ejecutar este programa en 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 línea de construcción debería ser algo así como gcc -o foo foo.c.

PD: Acabo de ejecutar esto y su código Python, y ambos me dieron una salida ordenada, por lo que no puedo reproducir lo que está viendo.

Mike DeSimone
fuente
1
La razón por la que está viendo una salida soted puede depender de muchos factores, como el sistema operativo, el sistema de archivos, el tiempo de creación de los archivos, las acciones durante la última desfragmentación, ...
Joachim Sauer
3
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]))

Como en el caso de mi requisito, tengo el caso como row_163.pklaquí, os.path.splitext('row_163.pkl')lo dividiremos en, ('row_163', '.pkl')por lo que también es necesario dividirlo en función de '_'.

pero en caso de que lo requiera, puede hacer algo como

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

dónde

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

y también para la recuperación de directorios puede hacer sorted(os.listdir(path))

y para el caso de me gusta 'run01.txt'o 'run01.csv'puedes hacer así

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

Encontré que "sort" no siempre hace lo que esperaba. por ejemplo, tengo un directorio como el siguiente, y el "sort" me da un resultado muy extraño:

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

Parece que compara el primer carácter primero, si ese es el más grande, sería el último.

Jue
fuente
2
Este es el comportamiento esperado. ('5' > '403') is True.
AXO
2
@AXO tiene razón, porque en este punto está comparando el orden alfanumérico, no los valores cuantitativos de los números. Para obtener una clasificación similar a sus expectativas, es posible que desee utilizar el relleno numérico en sus carpetas ... ['002', '003', '004', '005', '403', '404', ' 405 ',' 406 ']
Andrew
2

De la documentación :

La lista está en orden arbitrario y no incluye las entradas especiales '.' y '..' incluso si están presentes en el directorio.

Esto significa que el orden probablemente depende del sistema de archivos / sistema operativo, no tiene un orden particularmente significativo y, por lo tanto, no se garantiza que sea algo en particular. Como se mencionaron muchas respuestas: si lo prefiere, la lista recuperada se puede ordenar.

Salud :)

Código elegante
fuente
2

La respuesta de Elliot lo resuelve a la perfección pero como es un comentario pasa desapercibido así que con el objetivo de ayudar a alguien lo estoy reiterando como solución.

Utilice la biblioteca natsort:

Instale la biblioteca con el siguiente comando para Ubuntu y otras versiones de Debian

Python 2

sudo pip install natsort

Python 3

sudo pip3 install natsort

Los detalles sobre cómo usar esta biblioteca se encuentran aquí

Rocksyne
fuente
1
¡Eso es más exacto que sorted()! Gracias
Färid Alijani
1
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
fuente
Esto explica por qué están viendo el comportamiento, sin ofrecer una solución.
Daniel Watkins
1
OP solo quiere saber por qué, no cómo.
Denis
@Denis gracias por señalar esto - no lo había notado antes
Dimitris
@DanielWatkins OK, no, no lo es.)
Denis
0

La combinación propuesta de os.listdir y comandos ordenados genera el mismo resultado que el comando ls -l en Linux. El siguiente ejemplo verifica esta suposición:

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$ 

Entonces, para alguien que quiera reproducir el resultado del conocido comando ls -l en su código Python, sorted (os.listdir (DIR)) funciona bastante bien.

canguelo
fuente