os.listdir()返回的非字母数字列表顺序

202
我经常使用Python处理数据目录。最近,我注意到列表的默认顺序变得几乎毫无意义。例如,如果我在当前目录中包含以下子目录:run01,run02,... run19,run20,然后我从以下命令生成一个列表:
dir = os.listdir(os.getcwd())

通常我会按照以下顺序得到一个列表:
dir = ['run01', 'run18', 'run14', 'run13', 'run12', 'run11', 'run08', ... ]

等等。顺序过去是按字母数字顺序排列的。但是这个新的顺序现在已经保持了一段时间。
决定这些列表的(显示)顺序是什么?
14个回答

239
你可以使用内置的sorted函数按照任意顺序对字符串进行排序。根据你所描述的。
sorted(os.listdir(whatever_directory))

或者,你可以使用列表的.sort方法:

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

我认为这应该解决问题。
请注意,os.listdir 获取文件名的顺序可能完全取决于您的文件系统。

2
如果处理以数字开头的文件名(例如59.9780radps-0096仍然在9.9746radps-0082之前),则不更改顺序。我认为这是因为所有内容都是字符串,所以小数没有得到正确处理。 - Elliot
2
或者使用我刚找到的natsort库。 - Elliot
13
只有sorted(listdir)对我起作用。listdir.sort()出现了TypeError: 'NoneType' object is not iterable的错误提示。 - paul_h
3
@paul_h -- listdir.sort()不适用于像for i in listdir.sort()这样的语句,因为list.sort()方法会原地改变列表中项目的顺序,这意味着处理列表本身但不返回任何东西,只返回None。所以你需要使用a_list = listdir('some_path'); a_list.sort()然后执行for i in a_list - Sean_Syue
1
@user3895596 -- 我认为首先写的 sorted 可以在一行内完成,没问题吧? - mgilson
显示剩余5条评论

88

我认为顺序与文件系统中文件索引的方式有关。 如果您真的想让它遵循某种顺序,您可以在获取文件后对列表进行排序。


62
根据文档的说明:
os.listdir(路径)
返回一个包含给定路径目录中条目名称的列表。 列表没有特定顺序。 即使目录中存在'.'和'..' ,它们也不在列表中。
顺序不能被依赖,这是文件系统的副作用。
要对结果进行排序,请使用sorted(os.listdir(path))

61

出于某些原因,Python没有内置的方式实现自然排序(即按照1、2、10的顺序排列,而不是1、10、2),所以你需要自己编写代码来实现:

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)

现在您可以使用此函数对列表进行排序:

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

问题: 如果您使用上述函数对字符串进行排序(例如文件夹名称),并且想要按照Windows资源管理器的方式进行排序,那么在某些边缘情况下它将不能正常工作。
如果您在Windows上拥有包含某些“特殊”字符的文件夹名称,则此排序函数将返回错误的结果。例如,此函数将对1,!1,!a,a进行排序,而Windows资源管理器将对!1,1,!a,a进行排序。

因此,如果您想要在Python中像Windows资源管理器一样精确排序,则必须通过ctypes使用Windows内置函数StrCmpLogicalW(当然这在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))

这个函数比sorted_alphanumeric()稍微慢一些。

附加福利: winsort还可以在Windows上对完整路径排序。

或者,如果您使用Unix系统,可以使用natsort库(pip install natsort)以正确的方式按完整路径排序(意味着子文件夹位于正确位置)。

您可以像这样使用它来对完整路径进行排序:

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

从版本7.1.0开始,natsort支持os_sorted,它在内部使用之前提到的Windows API或Linux排序,应该替代natsorted()使用。


完美运行。print( sorted_aphanumeric(["1", "10", "2", "foo_10", "foo_8"]) ) -> ['1', '2', '10', 'foo_8', 'foo_10']。正如预期的那样。 - user136036
natsorted一直存在一个长期未解决的问题,即实现Windows Explorer匹配功能。也许您应该贡献一个解决方案?https://github.com/SethMMorton/natsort/issues/41 - SethMMorton
winsort函数正是我所需要的 :) - Amin Guermazi
在文件名以 _1、_2、..._10、_11 等结尾的文件上运行得非常好。 - Antony P.
这真的很有帮助! - undefined

22

我认为默认情况下,顺序是由ASCII值确定的。解决这个问题的方法是这样的:

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

2
以上方法都对我没用,似乎“key-len”是最后一个有效的技巧,非常感谢。 - Spider999

12

使用 natsort 库:

在 Ubuntu 和其他 Debian 版本中使用以下命令安装库:

Python 2

sudo pip install natsort

Python 3

->

Python 3

sudo pip3 install natsort

如何使用此库的详细信息可在此处找到

from natsort import natsorted

files = ['run01', 'run18', 'run14', 'run13', 'run12', 'run11', 'run08']
natsorted(files)

[out]:
['run01', 'run08', 'run11', 'run12', 'run13', 'run14', 'run18']
  • 这不是答案的重复。 natsort 是在2020-01-27进行的编辑

2
这比 sorted() 更准确!谢谢 - Farid Alijani

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

针对我的需求,我有以下情况:row_163.pkl,使用os.path.splitext('row_163.pkl')会将其拆分为('row_163', '.pkl'),因此需要基于下划线进行拆分。

但是对于您的需求,您可以做如下操作:

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

在哪里

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

同时,如果您需要检索目录,则可以执行以下操作:sorted(os.listdir(path))

对于像'run01.txt''run01.csv'这样的情况,您可以按照以下方式操作:

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

毫无疑问,这是最好的答案。 - Amit Amola

5

很可能只是C语言中readdir()返回的顺序问题。尝试运行以下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;
}

构建命令应该类似于gcc -o foo foo.c
附注:我刚刚运行了您的Python代码和我的代码,它们都给出了排序后的输出,所以我无法重现您看到的情况。

1
你看到排序输出的原因可能取决于许多因素,例如操作系统、文件系统、文件创建时间、上次碎片整理期间的操作等等。 - Joachim Sauer

3

来自文档

列表是任意排序的,即使目录中有'.'和'..'这两个特殊条目,它们也不会出现在列表中。

这意味着顺序可能与操作系统/文件系统有关,没有特别有意义的顺序,因此不能保证具体顺序。正如许多答案提到的那样:如果需要,可以对检索到的列表进行排序。

谢谢 :)


3
提议使用os.listdirsorted命令的组合,可以生成与Linux下的ls -l命令相同的结果。以下示例验证了这个假设:
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$ 

所以,对于想要在python代码中复制著名的ls -l命令结果的人来说,sorted( os.listdir( DIR ) )非常有效。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接