Niealfanumeryczna kolejność list z os.listdir ()


109

Często używam Pythona do przetwarzania katalogów danych. Ostatnio zauważyłem, że domyślna kolejność list zmieniła się na coś prawie bezsensownego. Na przykład, jeśli jestem w bieżącym katalogu zawierającym następujące podkatalogi: run01, run02, ... run19, run20, a następnie generuję listę za pomocą następującego polecenia:

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

wtedy zwykle otrzymuję listę w następującej kolejności:

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

i tak dalej. Porządek był alfanumeryczny. Ale ten nowy porządek pozostał we mnie już od jakiegoś czasu.

Co decyduje o (wyświetlanej) kolejności tych list?


Porządek na listach Pythona jest faktycznie istotny (tj. Listy są uporządkowane). Zgadzam się z Nowayzem: dziwna kolejność, którą widzisz, jest prawdopodobnie funkcją systemu plików. Widziałem to kilka lat temu z sieciowym systemem plików innej firmy podłączonym do komputera Mac.
David P Simons

Dziękuję za informację, usunąłem komentarz do kolejności listy.
marshall.ward

@ shog9 Ok, teraz widzę, że pytanie zostało zadane i rodzaj odpowiedzi (sposób sortowania danych nigdy nie został podany w powiązanej odpowiedzi), ale temat pytania nie był zbyt jasny (wyszukanie odpowiedzi nie pojawiło się) a tagi nie były zbyt pomocne
Dimitris,

@Dimitris: to uczciwa krytyka - zmieniłem tytuł tego pytania i połączyłem dwa pytania, więc teraz oba zestawy odpowiedzi można znaleźć tutaj, a twoja nadal wskazuje na to.
Shog9

BTW, jeśli ktoś jest tak zdezorientowany, jak ja, jeśli chodzi o odpowiedzi tutaj, to dlatego, że moje pytanie zostało połączone z innym pytaniem z prośbą o posortowane listdirwyniki. Nie jestem pewien, dlaczego pytania zostały połączone.
marshall.ward

Odpowiedzi:


63

Myślę, że kolejność ma związek ze sposobem indeksowania plików w systemie plików. Jeśli naprawdę chcesz, aby zachowała się w jakiejś kolejności, zawsze możesz posortować listę po pobraniu plików.


128

Możesz użyć wbudowanej sortedfunkcji do sortowania łańcuchów w dowolny sposób. Na podstawie tego, co opisujesz,

sorted(os.listdir(whatever_directory))

Alternatywnie możesz użyć .sortmetody listy:

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

Myślę, że powinienem załatwić sprawę.

Zwróć uwagę, że kolejność os.listdirpobierania nazw plików jest prawdopodobnie całkowicie zależna od twojego systemu plików.


1
Nie zmienia kolejności, jeśli mamy do czynienia z nazwami plików zaczynającymi się od numeru (tj. 59.9780radps-0096 jest nadal przed 9.9746radps-0082). Myślę, że to dlatego, że wszystko jest ciągiem, więc ułamek dziesiętny nie jest traktowany prawidłowo.
Elliot,

2
Lub skorzystaj z biblioteki natsort, którą właśnie znalazłem.
Elliot,

5
sorted(listdir)Pracował tylko dla mnie. listdir.sort()dał mi: TypeError: Obiekt 'NoneType' nie jest
iterowalny

1
@AlexB - jasne ... po prostu przejdź, reverse=Trueaby posortować malejąco.
mgilson

1
@ user3895596 - Myślę, że sortedrzecz napisana jako pierwsza, czy to w jednej linii OK?
mgilson

43

Zgodnie z dokumentacją :

os.listdir (ścieżka)

Zwraca listę zawierającą nazwy wpisów w katalogu podanym przez ścieżkę. Lista jest w dowolnej kolejności . Nie obejmuje wpisów specjalnych ”. i „..”, nawet jeśli znajdują się w katalogu.

Nie można polegać na porządku i jest on artefaktem systemu plików.

Aby posortować wynik, użyj sorted(os.listdir(path)).


28

Z jakiegoś powodu Python nie ma wbudowanego sposobu na naturalne sortowanie (czyli 1, 2, 10 zamiast 1, 10, 2), więc musisz napisać to sam:

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)

Możesz teraz użyć tej funkcji do sortowania listy:

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

PROBLEMY: Jeśli użyjesz powyższej funkcji do sortowania ciągów (na przykład nazw folderów) i chcesz, aby były posortowane tak, jak robi to Eksplorator Windows, nie będzie ona działać poprawnie w niektórych skrajnych przypadkach.
Ta funkcja sortowania zwróci nieprawidłowe wyniki w systemie Windows, jeśli masz nazwy folderów z określonymi znakami „specjalnymi”. Na przykład ta funkcja posortuje 1, !1, !a, a, podczas gdy Eksplorator Windows posortuje !1, 1, !a, a.

Więc jeśli chcesz sortować dokładnie tak, jak robi to Eksplorator Windows w Pythonie , musisz użyć wbudowanej funkcji Windows StrCmpLogicalW poprzez ctypes (to oczywiście nie zadziała na Uniksie):

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))

Ta funkcja jest nieco wolniejsza niż sorted_alphanumeric().

Bonus: winsortmożna również sortować pełne ścieżki w systemie Windows .

Alternatywnie, zwłaszcza jeśli używasz Uniksa, możesz użyć natsortbiblioteki ( pip install natsort) do sortowania według pełnych ścieżek w prawidłowy sposób (co oznacza podfoldery we właściwej pozycji).

Możesz go użyć w ten sposób do sortowania pełnych ścieżek:

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

Nie używaj go do normalnego sortowania tylko nazw folderów (lub ogólnie ciągów znaków), ponieważ jest to nieco wolniejsze niż sorted_alphanumeric()funkcja powyżej.
natsortedbiblioteka da nieprawidłowe wyniki, jeśli spodziewasz się sortowania w Eksploratorze Windows, więc użyj winsort()do tego.


Działa doskonale. print( sorted_aphanumeric(["1", "10", "2", "foo_10", "foo_8"]) )-> ['1', '2', '10', 'foo_8', 'foo_10']. Dokładnie tak, jak oczekiwano.
user136036

Istnieje od dawna otwarty problem z natsortedzaimplementowaniem funkcji dopasowywania Eksploratora Windows. Może powinieneś wnieść rozwiązanie? github.com/SethMMorton/natsort/issues/41
SethMMorton

8

Myślę, że domyślnie kolejność jest określana za pomocą wartości ASCII. Rozwiązanie tego problemu jest następujące

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

5

Prawdopodobnie jest to tylko kolejność, z której readdir()zwraca C. Spróbuj uruchomić ten program w 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;
}

Linia budowania powinna być podobna gcc -o foo foo.c.

PS Właśnie uruchomiłem to i twój kod w Pythonie i oba dały mi posortowane dane wyjściowe, więc nie mogę odtworzyć tego, co widzisz.


1
Powód, dla którego widzisz osłabione dane wyjściowe, może zależeć od wielu czynników, takich jak system operacyjny, system plików, czas tworzenia plików, działania podczas ostatniej defragmentacji, ...
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]))

Ponieważ w przypadku mojego wymagania mam przypadek taki jak row_163.pkltutaj os.path.splitext('row_163.pkl'), ('row_163', '.pkl')więc podzielę go również na podstawie '_'.

ale w przypadku twojego wymagania możesz zrobić coś takiego

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

gdzie

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

a także do pobierania katalogów, które możesz zrobić sorted(os.listdir(path))

a na wypadek jak 'run01.txt'lub 'run01.csv'możesz to zrobić

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

Bezsprzecznie najlepsza odpowiedź tutaj.
Amit Amola

2

Odkryłem, że „sort” nie zawsze robi to, czego się spodziewałem. np. mam katalog jak poniżej, a "sort" daje mi bardzo dziwny wynik:

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

Wygląda na to, że najpierw porównuje pierwszą postać, jeśli to jest największa, to będzie ostatnia.


2
Jest to oczekiwane zachowanie. ('5' > '403') is True.
AXO

2
@AXO jest poprawne, ponieważ w tym momencie porównujesz sortowanie alfanumeryczne, a nie ilościowe wartości liczb. Aby uzyskać sortowanie podobne do twoich oczekiwań, możesz użyć dopełnienia numerami w swoich folderach ... ['002', '003', '004', '005', '403', '404', ' 405 ',' 406 ']
Andrew,

2

Z dokumentacji :

Lista jest uporządkowana w dowolnej kolejności i nie zawiera wpisów specjalnych ”. i „..”, nawet jeśli znajdują się w katalogu.

Oznacza to, że kolejność jest prawdopodobnie zależna od systemu operacyjnego / systemu plików, nie ma szczególnie znaczącej kolejności i dlatego nie gwarantuje się, że będzie to coś szczególnego. Jak wspomniano w wielu odpowiedziach: jeśli jest to preferowane, pobrana lista może być sortowana.

Twoje zdrowie :)


2

Odpowiedź Elliota rozwiązuje to doskonale, ale ponieważ jest to komentarz, pozostaje niezauważony, więc aby komuś pomóc, powtarzam to jako rozwiązanie.

Użyj biblioteki natsort:

Zainstaluj bibliotekę za pomocą następującego polecenia dla Ubuntu i innych wersji Debiana

Python 2

sudo pip install natsort

Python 3

sudo pip3 install natsort

Szczegóły dotyczące korzystania z tej biblioteki można znaleźć tutaj


1
To jest dokładniejsze niż sorted()! Dzięki
Färid Alijani

1

Proponowana kombinacja poleceń os.listdiri sorteddaje taki sam wynik jak ls -lpolecenie w systemie Linux. Poniższy przykład weryfikuje to założenie:

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$ 

Tak więc, dla kogoś, kto chce odtworzyć wynik dobrze znanego ls -lpolecenia w swoim kodzie Pythona, sorted( os.listdir( DIR ) )działa całkiem dobrze.


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
To wyjaśnia, dlaczego widzą takie zachowanie, nie oferując rozwiązania.
Daniel Watkins,

1
OP chcą tylko wiedzieć, dlaczego, a nie jak.
Denis

@Denis dzięki za wskazanie tego - wcześniej tego nie zauważyłem
Dimitris,

@DanielWatkins OK, nie jest)
Denis,
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.