Ordem da lista não alfanumérica de os.listdir ()

108

Costumo usar python para processar diretórios de dados. Recentemente, percebi que a ordem padrão das listas mudou para algo quase sem sentido. Por exemplo, se estou em um diretório atual contendo os seguintes subdiretórios: run01, run02, ... run19, run20 e, a seguir, gero uma lista a partir do seguinte comando:

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

então geralmente consigo uma lista nesta ordem:

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

e assim por diante. A ordem costumava ser alfanumérica. Mas esta nova ordem permaneceu comigo por um tempo agora.

O que está determinando a ordem (exibida) dessas listas?

marshall.ward
fonte
A ordem nas listas python é realmente relevante (ou seja, as listas são ordenadas). Eu concordo com Nowayz: a ordem estranha que você está vendo provavelmente é uma função do sistema de arquivos. Eu vi isso acontecer alguns anos atrás com um sistema de arquivos de rede de terceiros anexado a um mac.
David P Simons
Obrigado pela informação, removi o comentário sobre a ordem da lista.
marshall.ward
@ shog9 Ok, agora posso ver que a pergunta foi feita, e meio que respondida (a forma de classificar os dados nunca foi fornecida na resposta vinculada), mas o tópico da pergunta não estava muito claro (fazendo uma pesquisa essa resposta não apareceu) e as marcas não foram muito úteis
Dimitris
@Dimitris: essa é uma crítica justa - mudei o título desta e juntei as duas perguntas, então agora os dois conjuntos de respostas podem ser encontrados aqui e o seu continua apontando para elas.
Shog9
Aliás, se alguém está tão confuso quanto eu sobre as respostas aqui, é porque minha pergunta foi mesclada com outra pergunta solicitando listdirsaída classificada . Não sei por que as perguntas foram mescladas.
marshall.ward

Respostas:

63

Acho que a ordem tem a ver com a forma como os arquivos são indexados em seu FileSystem. Se você realmente deseja que ela siga alguma ordem, você pode sempre ordenar a lista depois de obter os arquivos.

Nowayz
fonte
128

Você pode usar a sortedfunção interna para classificar as strings como quiser. Com base no que você descreve,

sorted(os.listdir(whatever_directory))

Como alternativa, você pode usar o .sortmétodo de uma lista:

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

Eu acho que deve fazer o truque.

Observe que a ordem que os.listdirobtém os nomes dos arquivos provavelmente depende completamente do seu sistema de arquivos.

mgilson
fonte
1
Não altera a ordem se estiver lidando com nomes de arquivo com o primeiro número (ou seja, 59,9780radps-0096 ainda é anterior a 9,9746radps-0082). Acho que é porque tudo é uma string, então o decimal não é tratado corretamente.
Elliot
2
Ou use a biblioteca natsort, que acabei de encontrar.
Elliot
5
sorted(listdir)funcionou para mim. listdir.sort()me deu: TypeError: o objeto 'NoneType' não é iterável
paul_h
1
@AlexB - claro ... apenas passe reverse=Truepara torná-lo tipo descendente.
mgilson
1
@ user3895596 - Acho que a sortedcoisa escrita primeiro faz em uma única linha, OK?
mgilson
43

Pela documentação :

os.listdir (caminho)

Retorne uma lista contendo os nomes das entradas no diretório fornecido por caminho. A lista está em ordem arbitrária . Não inclui as entradas especiais '.' e '..' mesmo se estiverem presentes no diretório.

A ordem não é confiável e é um artefato do sistema de arquivos.

Para classificar o resultado, use sorted(os.listdir(path)).

Mark Tolonen
fonte
26

Python, por qualquer motivo, não vem com uma maneira embutida de ter classificação natural (ou seja, 1, 2, 10 em vez de 1, 10, 2), então você mesmo deve escrever:

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)

Agora você pode usar esta função para classificar uma lista:

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

PROBLEMAS: Se você usar a função acima para classificar strings (por exemplo, nomes de pastas) e quiser classificá-las como o Windows Explorer faz, isso não funcionará corretamente em alguns casos extremos.
Esta função de classificação retornará resultados incorretos no Windows, se você tiver nomes de pastas com certos caracteres 'especiais'. Por exemplo, esta função irá classificar 1, !1, !a, a, enquanto o Windows Explorer irá classificar !1, 1, !a, a.

Portanto, se você deseja classificar exatamente como o Windows Explorer faz no Python, você deve usar a função integrada do Windows StrCmpLogicalW via ctypes (isso é claro não funcionará no 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 função é ligeiramente mais lenta do que sorted_alphanumeric().

Bônus: winsorttambém pode classificar caminhos completos no Windows .

Alternativamente, especialmente se você usar Unix, você pode usar a natsortbiblioteca ( pip install natsort) para classificar por caminhos completos de maneira correta (significando subpastas na posição correta).

Você pode usá-lo assim para classificar caminhos completos:

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

Não o use para classificação normal apenas de nomes de pastas (ou strings em geral), pois é um pouco mais lento do que a sorted_alphanumeric()função acima.
natsortedbiblioteca fornecerá resultados incorretos se você espera a classificação do Windows Explorer, então use winsort()para isso.

user136036
fonte
Funciona perfeitamente bem. print( sorted_aphanumeric(["1", "10", "2", "foo_10", "foo_8"]) )-> ['1', '2', '10', 'foo_8', 'foo_10']. Exatamente como esperado.
user136036
Há um antigo problema em aberto natsortedpara implementar a funcionalidade de correspondência do Windows Explorer. Talvez você deva contribuir com uma solução? github.com/SethMMorton/natsort/issues/41
SethMMorton
8

Acho que por padrão a ordem é determinada com o valor ASCII. A solução para este problema é esta

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

Provavelmente é apenas a ordem que C readdir()retorna. Tente executar este programa 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;
}

A linha de construção deve ser semelhante gcc -o foo foo.c.

PS Acabei de executar isso e seu código Python, e ambos me deram uma saída ordenada, então não posso reproduzir o que você está vendo.

Mike DeSimone
fonte
1
O motivo pelo qual você está vendo a saída armazenada pode depender de vários fatores, como sistema operacional, sistema de arquivos, hora de criação dos arquivos, ações durante a última desfragmentação, ...
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 no caso do meu requisito, tenho um caso como o row_163.pklaqui os.path.splitext('row_163.pkl'), ('row_163', '.pkl')então preciso dividi-lo com base em '_' também.

mas no caso de sua necessidade, você pode fazer algo como

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

Onde

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

e também para recuperação de diretório, você pode fazer sorted(os.listdir(path))

e para o caso de gostar 'run01.txt'ou 'run01.csv'você pode fazer assim

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

Descobri que "classificar" nem sempre faz o que eu esperava. por exemplo, eu tenho um diretório como o abaixo, e o "sort" me dá um resultado muito estranho:

>>> 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 primeiro o primeiro personagem, se for o maior, seria o último.

Jue
fonte
2
Este é o comportamento esperado. ('5' > '403') is True.
AXO
2
@AXO está correto, porque neste ponto você está comparando a classificação alfanumérica, não os valores quantitativos dos números. Para obter uma classificação semelhante à sua expectativa, você pode usar o preenchimento numérico em suas pastas ... ['002', '003', '004', '005', '403', '404', ' 405 ',' 406 ']
André,
2

Da documentação :

A lista está em ordem arbitrária e não inclui as entradas especiais '.' e '..' mesmo se estiverem presentes no diretório.

Isso significa que a ordem provavelmente depende do sistema operacional / de arquivos, não tem uma ordem particularmente significativa e, portanto, não é garantido que seja algo em particular. Quantas respostas mencionadas: se preferir, a lista recuperada pode ser classificada.

Felicidades :)

Código Elegante
fonte
2

A resposta de Elliot resolve perfeitamente, mas por se tratar de um comentário, passa despercebido então com o intuito de ajudar alguém, estou reiterando como solução.

Use a biblioteca natsort:

Instale a biblioteca com o seguinte comando para Ubuntu e outras versões do Debian

Python 2

sudo pip install natsort

Python 3

sudo pip3 install natsort

Os detalhes de como usar esta biblioteca podem ser encontrados aqui

Rocksyne
fonte
1
Isso é mais preciso do que sorted()! Obrigado
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
fonte
Isso explica porque estão vendo o comportamento, sem oferecer uma solução.
Daniel Watkins
1
OP só quer saber por quê, não como.
Denis
@Denis obrigado por apontar isso - eu não percebi antes
Dimitris
@DanielWatkins OK, não é.)
Denis
0

A combinação proposta de os.listdir e comandos classificados gera o mesmo resultado que o comando ls -l no Linux. O exemplo a seguir verifica essa suposição:

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$ 

Portanto, para alguém que deseja reproduzir o resultado do conhecido comando ls -l em seu código Python, o ordenado (os.listdir (DIR)) funciona muito bem.

funk
fonte