Urutan daftar non-alfanumerik dari os.listdir ()

109

Saya sering menggunakan python untuk memproses direktori data. Baru-baru ini, saya memperhatikan bahwa urutan default daftar telah berubah menjadi sesuatu yang hampir tidak masuk akal. Misalnya, jika saya berada di direktori saat ini yang berisi subdirektori berikut: run01, run02, ... run19, run20, lalu saya membuat daftar dari perintah berikut:

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

maka saya biasanya mendapatkan daftar dalam urutan ini:

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

dan seterusnya. Urutannya dulu alfanumerik. Tetapi tatanan baru ini tetap ada pada saya untuk sementara waktu sekarang.

Apa yang menentukan urutan (ditampilkan) dari daftar ini?

marshall.ward
sumber
Urutan dalam daftar python sebenarnya relevan (mis. Daftar dipesan). Saya setuju dengan Nowayz: urutan aneh yang Anda lihat mungkin adalah fungsi sistem file. Saya melihat ini terjadi beberapa tahun yang lalu dengan sistem file jaringan pihak ketiga yang terpasang ke mac.
David P Simons
Terima kasih atas infonya, saya sudah hapus komentar urutan daftar.
marshall.ward
@ shog9 Ok, sekarang saya dapat melihat bahwa pertanyaan telah ditanyakan, dan jenis jawaban (cara menyortir data tidak pernah diberikan dalam jawaban yang ditautkan) tetapi topik pertanyaan tidak terlalu jelas (melakukan pencarian sehingga jawaban tidak muncul) dan tagnya tidak terlalu membantu
Dimitris
@Dimitris: itu kritik yang adil - Saya telah memberi judul ulang yang satu ini dan menggabungkan dua pertanyaan, jadi sekarang kedua rangkaian jawaban dapat ditemukan di sini dan jawaban Anda tetap mengarah ke sana.
Shog9
BTW, jika ada orang lain yang sama bingungnya dengan saya tentang jawaban di sini, itu karena pertanyaan saya digabungkan dengan pertanyaan lain yang meminta listdirkeluaran yang diurutkan . Saya tidak yakin mengapa pertanyaan itu digabungkan.
marshall.ward

Jawaban:

63

Saya pikir urutannya ada hubungannya dengan cara file diindeks di FileSystem Anda. Jika Anda benar-benar ingin membuatnya mematuhi beberapa urutan, Anda selalu dapat mengurutkan daftar setelah mendapatkan file.

Nowayz
sumber
128

Anda dapat menggunakan sortedfungsi bawaan untuk mengurutkan string sesuai keinginan Anda. Berdasarkan apa yang Anda gambarkan,

sorted(os.listdir(whatever_directory))

Atau, Anda dapat menggunakan .sortmetode daftar:

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

Saya pikir harus melakukan trik.

Perhatikan bahwa urutan os.listdirmendapatkan nama file mungkin sepenuhnya bergantung pada sistem file Anda.

mgilson.dll
sumber
1
Tidak mengubah urutan jika berhadapan dengan nama file nomor pertama (yaitu 59.9780radps-0096 masih sebelum 9.9746radps-0082). Saya pikir itu karena semuanya adalah string, jadi desimal tidak diperlakukan dengan benar.
Elliot
2
Atau gunakan perpustakaan natsort, yang baru saja saya temukan.
Elliot
5
Hanya sorted(listdir)bekerja untuk saya. listdir.sort()memberi saya: TypeError: Objek 'NoneType' tidak dapat
diulang
1
@AlexB - tentu ... lulus saja reverse=Trueuntuk membuatnya agak menurun.
mgilson
1
@ user3895596 - Saya pikir sortedhal yang ditulis pertama kali melakukannya dalam satu baris OK?
mgilson
43

Sesuai dokumentasi :

os.listdir (jalur)

Kembalikan daftar yang berisi nama-nama entri dalam direktori yang diberikan oleh jalur. Daftar ini dalam urutan yang sewenang-wenang . Ini tidak termasuk entri khusus '.' dan '..' meskipun mereka ada di direktori.

Urutan tidak dapat diandalkan dan merupakan artefak dari sistem file.

Untuk mengurutkan hasil, gunakan sorted(os.listdir(path)).

Mark Tolonen
sumber
27

Python untuk alasan apa pun tidak dilengkapi dengan cara bawaan untuk memiliki pengurutan alami (artinya 1, 2, 10, bukan 1, 10, 2), jadi Anda harus menulisnya sendiri:

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)

Sekarang Anda dapat menggunakan fungsi ini untuk mengurutkan daftar:

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

MASALAH: Jika Anda menggunakan fungsi di atas untuk mengurutkan string (misalnya nama folder) dan ingin mereka diurutkan seperti yang dilakukan Windows Explorer, ini tidak akan berfungsi dengan baik dalam beberapa kasus edge.
Fungsi pengurutan ini akan mengembalikan hasil yang salah pada Windows, jika Anda memiliki nama folder dengan karakter 'khusus' tertentu di dalamnya. Misalnya fungsi ini akan mengurutkan 1, !1, !a, a, sedangkan Windows Explorer akan mengurutkan !1, 1, !a, a.

Jadi jika Anda ingin mengurutkan persis seperti yang dilakukan Windows Explorer dengan Python, Anda harus menggunakan fungsi bawaan Windows bawaan StrCmpLogicalW melalui ctypes (ini tentu saja tidak akan berfungsi pada 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))

Fungsi ini sedikit lebih lambat dari sorted_alphanumeric().

Bonus: winsort juga dapat mengurutkan jalur lengkap di Windows .

Alternatifnya, terutama jika Anda menggunakan Unix, Anda dapat menggunakan natsort library ( pip install natsort) untuk mengurutkan menurut jalur lengkap dengan cara yang benar (artinya subfolder di posisi yang benar).

Anda dapat menggunakannya seperti ini untuk mengurutkan jalur lengkap:

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

Jangan menggunakannya untuk pengurutan normal hanya nama folder (atau string secara umum), karena ini sedikit lebih lambat dari sorted_alphanumeric()fungsi di atas.
natsortedperpustakaan akan memberi Anda hasil yang salah jika Anda mengharapkan pengurutan Windows Explorer, jadi gunakan winsort()untuk itu.

pengguna136036
sumber
Bekerja dengan baik. print( sorted_aphanumeric(["1", "10", "2", "foo_10", "foo_8"]) )-> ['1', '2', '10', 'foo_8', 'foo_10']. Persis seperti yang diharapkan.
pengguna136036
Ada masalah terbuka yang sudah lama terjadi natsorteduntuk menerapkan fungsionalitas pencocokan Windows Explorer. Mungkin Anda harus memberikan solusi? github.com/SethMMorton/natsort/issues/41
SethMMorton
8

Saya pikir secara default urutan ditentukan dengan nilai ASCII. Solusi untuk masalah ini adalah ini

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

Mungkin hanya urutan C yang readdir()kembali. Coba jalankan program C ini:

#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;
}

Garis build harus seperti ini gcc -o foo foo.c .

PS Hanya menjalankan ini dan kode Python Anda, dan mereka berdua memberi saya keluaran yang diurutkan, jadi saya tidak dapat mereproduksi apa yang Anda lihat.

Mike DeSimone
sumber
1
Alasan Anda melihat keluaran yang dipisahkan mungkin bergantung pada banyak faktor, seperti OS, sistem file, waktu pembuatan file, tindakan selama defragmentasi terakhir, ...
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]))

Sebagai Dalam kasus kebutuhan saya, saya memiliki kasus seperti di row_163.pklsini os.path.splitext('row_163.pkl')akan membobolnya('row_163', '.pkl') jadi perlu membaginya berdasarkan '_' juga.

tetapi dalam kasus kebutuhan Anda, Anda dapat melakukan sesuatu seperti

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

dimana

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

dan juga untuk pengambilan direktori dapat Anda lakukan sorted(os.listdir(path))

dan untuk kasus sejenis 'run01.txt' atau 'run01.csv'bisa kamu lakukan seperti ini

sorted(files, key=lambda x : int(os.path.splitext(x)[0]))
rajeshcis
sumber
Jawaban terbaik tak terbantahkan di sini.
Amit Amola
2

Saya menemukan "sort" tidak selalu melakukan apa yang saya harapkan. misalnya, saya memiliki direktori seperti di bawah ini, dan "sort" memberi saya hasil yang sangat aneh:

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

Sepertinya itu membandingkan karakter pertama terlebih dahulu, jika itu yang terbesar, itu akan menjadi yang terakhir.

Jue
sumber
2
Ini adalah perilaku yang diharapkan. ('5' > '403') is True.
AXO
2
@AXO benar, karena pada titik ini Anda membandingkan urutan alfanumerik, bukan nilai kuantitatif dari angka tersebut. Untuk mendapatkan urutan yang mirip dengan harapan Anda, Anda mungkin ingin menggunakan padding angka pada folder Anda ... ['002', '003', '004', '005', '403', '404', ' 405 ',' 406 ']
Andrew
2

Dari dokumentasi :

Daftar ini dalam urutan sewenang-wenang , dan tidak termasuk entri khusus '.' dan '..' meskipun mereka ada di direktori.

Ini berarti bahwa urutannya mungkin bergantung pada OS / filesystem, tidak memiliki urutan yang berarti, dan oleh karena itu tidak dijamin menjadi sesuatu secara khusus. Seperti banyak jawaban yang disebutkan: jika disukai, daftar yang diambil dapat diurutkan.

Bersulang :)

Kode Elegan
sumber
2

Jawaban Elliot menyelesaikannya dengan sempurna tetapi karena ini adalah sebuah komentar, ia luput dari perhatian sehingga dengan tujuan membantu seseorang, saya mengulanginya sebagai solusi.

Gunakan perpustakaan natsort:

Instal pustaka dengan perintah berikut untuk Ubuntu dan versi Debian lainnya

Python 2

sudo pip install natsort

Python 3

sudo pip3 install natsort

Detail tentang cara menggunakan perpustakaan ini ditemukan di sini

berbatu-batu
sumber
1
Itu lebih akurat dari sorted()! Terima kasih
Färid Alijani
1

Kombinasi yang diusulkan os.listdirdan sortedperintah menghasilkan hasil yang sama seperti ls -lperintah di Linux. Contoh berikut memverifikasi asumsi ini:

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$ 

Jadi, untuk seseorang yang ingin mereproduksi hasil dari ls -lperintah terkenal di kode python mereka, sorted( os.listdir( DIR ) )bekerja dengan cukup baik.

pengecut
sumber
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
sumber
1
Ini menjelaskan mengapa mereka melihat perilaku tersebut, tanpa menawarkan solusi.
Daniel Watkins
1
OP hanya ingin tahu kenapa, bukan bagaimana.
Denis
@Denis terima kasih telah menunjukkan hal ini - Saya tidak menyadarinya sebelumnya
Dimitris
@DanielWatchkins OK, Bukan itu isnt.)
Denis