Порядок не буквенно-цифровых списков из os.listdir ()


110

Я часто использую Python для обработки каталогов данных. Недавно я заметил, что порядок списков по умолчанию изменился на что-то почти бессмысленное. Например, если я нахожусь в текущем каталоге, содержащем следующие подкаталоги: run01, run02, ... run19, run20, а затем я создаю список с помощью следующей команды:

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

то я обычно получаю список в таком порядке:

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

и так далее. Раньше порядок был буквенно-цифровым. Но этот новый порядок остался со мной на некоторое время.

Что определяет (отображаемый) порядок этих списков?


Порядок в списках Python действительно имеет значение (т.е. списки упорядочены). Я согласен с Новайзом: странный порядок, который вы видите, вероятно, является функцией файловой системы. Я видел, как это произошло несколько лет назад, когда к Mac была подключена сетевая файловая система стороннего производителя.
Дэвид П. Саймонс

Спасибо за информацию, я удалил комментарий к порядку рассылки.
marshall.ward

@ shog9 Хорошо, теперь я вижу, что вопрос был задан, и на него был дан ответ (способ сортировки данных никогда не был указан в связанном ответе), но тема вопроса была не очень ясной (выполняя поиск, ответ не появлялся) и теги не очень помогли
Димитрис

@Dimitris: это справедливая критика - я переименовал этот и объединил два вопроса, так что теперь здесь можно найти оба набора ответов, а ваш по-прежнему указывает на него.
Shog9

Кстати, если кто-то еще так же смущен ответами, как я, это потому, что мой вопрос был объединен с другим вопросом, запрашивающим отсортированный listdirвывод. Я не уверен, почему вопросы были объединены.
marshall.ward

Ответы:


63

Я думаю, что порядок связан с тем, как файлы индексируются в вашей файловой системе. Если вы действительно хотите, чтобы он придерживался определенного порядка, вы всегда можете отсортировать список после получения файлов.


128

Вы можете использовать встроенную sortedфункцию для сортировки строк, как хотите. Исходя из того, что вы описываете,

sorted(os.listdir(whatever_directory))

В качестве альтернативы вы можете использовать .sortметод списка:

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

Я думаю, это должно помочь.

Обратите внимание, что порядок os.listdirполучения имен файлов, вероятно, полностью зависит от вашей файловой системы.


1
Не меняет порядок при работе с именами файлов, начинающимися с номера (например, 59.9780radps-0096 все еще раньше 9.9746radps-0082). Я думаю, это потому, что все является строкой, поэтому десятичная дробь не обрабатывается должным образом.
Эллиот

2
Или воспользуйтесь библиотекой natsort, которую я только что нашел.
Эллиот

5
sorted(listdir)Работал только у меня. listdir.sort()дал мне: TypeError: объект 'NoneType' не повторяется
paul_h

1
@AlexB - конечно ... просто пройдите, reverse=Trueчтобы сделать сортировку по убыванию.
mgilson

1
@ user3895596 - Я думаю, что то, sortedчто написано первым, делает это одной строкой ОК?
mgilson

43

Согласно документации :

os.listdir (путь)

Вернуть список, содержащий имена записей в каталоге, заданном путем. Список в произвольном порядке . Он не включает специальные записи '.' и "..", даже если они присутствуют в каталоге.

На порядок нельзя полагаться, это артефакт файловой системы.

Чтобы отсортировать результат, используйте sorted(os.listdir(path)).


28

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.

Поэтому, если вы хотите выполнить сортировку точно так же, как Windows Explorer в Python, вам нужно использовать встроенную функцию Windows StrCmpLogicalW через ctypes (это, конечно, не будет работать в 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)

Не используйте его для обычной сортировки только имен папок (или строк в целом), так как это немного медленнее, чем sorted_alphanumeric()указанная выше функция.
natsortedбиблиотека даст вам неверные результаты, если вы ожидаете сортировки Windows Explorer, поэтому используйте winsort()для этого.


Работает отлично. print( sorted_aphanumeric(["1", "10", "2", "foo_10", "foo_8"]) )-> ['1', '2', '10', 'foo_8', 'foo_10']. Как и ожидалось.
user136036

Существует давняя нерешенная проблема, natsortedсвязанная с реализацией функции сопоставления Windows Explorer. Возможно, вам стоит предложить решение? github.com/SethMMorton/natsort/issues/41
SethMMorton,

9

Я думаю, что по умолчанию порядок определяется значением ASCII. Решение этой проблемы - это

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

5

Вероятно, это просто порядок readdir()возврата C. Попробуйте запустить эту программу на 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.

PS Просто запустил это и свой код Python, и оба они дали мне отсортированный вывод, поэтому я не могу воспроизвести то, что вы видите.


1
Причина, по которой вы видите вывод soted, может зависеть от множества факторов, таких как ОС, файловая система, время создания файлов, действия во время последней дефрагментации, ...
Иоахим Зауэр

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

Поскольку в случае моего требования у меня есть случай, как 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]))

Бесспорно лучший ответ здесь.
Амит Амола,

2

Я обнаружил, что «сортировка» не всегда делает то, что я ожидал. например, у меня есть каталог, как показано ниже, и «сортировка» дает мне очень странный результат:

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

Кажется, сначала он сравнивает первый символ, если он самый большой, то он будет последним.


2
Это ожидаемое поведение. ('5' > '403') is True.
AXO

2
@AXO правильно, потому что на этом этапе вы сравниваете буквенно-цифровую сортировку, а не количественные значения чисел. Чтобы сортировка соответствовала вашим ожиданиям, вы можете использовать числовые поля в своих папках ... ['002', '003', '004', '005', '403', '404', ' 405 ',' 406 ']
Эндрю

2

Из документации :

Список составлен в произвольном порядке и не включает специальные записи "." и "..", даже если они присутствуют в каталоге.

Это означает, что порядок, вероятно, зависит от ОС / файловой системы, не имеет особо значимого порядка и, следовательно, не может быть каким-либо конкретным. Как упоминалось во многих ответах: при желании полученный список можно отсортировать.

Ура :)


2

Ответ Эллиота отлично решает эту проблему, но поскольку это комментарий, он остается незамеченным, поэтому с целью помочь кому-то я повторяю его как решение.

Используйте библиотеку natsort:

Установите библиотеку с помощью следующей команды для Ubuntu и других версий Debian

Python 2

sudo pip install natsort

Python 3

sudo pip3 install natsort

Подробная информация о том, как использовать эту библиотеку, находится здесь.


1
Это точнее, чем sorted()! Спасибо
Ферид Алиджани

1

Предлагаемая комбинация команд os.listdirи sortedдает тот же результат, что и ls -lкоманда в Linux. Следующий пример подтверждает это предположение:

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$ 

Итак, для тех, кто хочет воспроизвести результат хорошо известной ls -lкоманды в своем коде Python, sorted( os.listdir( DIR ) )работает довольно хорошо.


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
Это объясняет, почему они видят поведение, не предлагая решения.
Дэниел Уоткинс,

1
ОП просто хочет знать, почему, а не как.
Денис

@Denis, спасибо, что указал на это - я этого раньше не замечал
Димитрис

@DanielWatkins: Хорошо, это не так.)
Денис
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.