Порядок не алфавітно-цифрових списків від os.listdir ()


108

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

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

тоді я зазвичай отримую список у такому порядку:

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

і так далі. Порядок був буквено-цифровим. Але цей новий порядок залишився зі мною на деякий час.

Що визначає (відображається) порядок цих списків?


Порядок в списках python насправді актуальний (тобто списки впорядковані). Я погоджуюся з Nowayz: дивний порядок, який ви бачите, - це, мабуть, функція файлової системи. Я бачив, що це відбувається кілька років тому, коли файлова система сторонніх мереж приєднана до Mac.
Девід П Сімонс

Дякую за інформацію, я видалив коментар до списку замовлень.
marshall.ward

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

@Dimitris: це справедлива критика - я переказав це питання і об'єднав два запитання, тож тепер тут можна знайти обидва набори відповідей, а ваші залишаються на нього.
Shog9

До речі, якщо хтось інший настільки розгублений, як і я, щодо відповідей тут, це тому, що моє запитання було об'єднано з іншим запитом із запитом відсортованого listdirвиводу. Я не впевнений, чому питання були об'єднані.
marshall.ward

Відповіді:


63

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


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
@ Алекс - впевнений ... просто пройди, reverse=Trueщоб зробити його низхідним.
mgilson

1
@ user3895596 - Я думаю, що те, sortedщо написано спочатку, робить це в одному рядку?
mgilson

43

Відповідно до документації :

os.listdir (шлях)

Поверніть список, що містить імена записів у каталозі, заданому шляхом. Список складається у довільному порядку . Він не включає спеціальні записи "." та "..", навіть якщо вони є в каталозі.

На порядок не можна покладатися і є артефактом файлової системи.

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


27

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 Explorer буде сортувати !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']. Точно так, як очікувалося.
користувач136036

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

8

Я думаю, що за замовчуванням порядок визначається зі значенням ASCII. Рішення цієї проблеми - це

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

5

Це, мабуть, лише порядок 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.

PS Тільки запустив цей і ваш код Python, і вони обидва дали мені відсортований вихід, тому я не можу відтворити те, що ви бачите.


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

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

Оскільки у випадку з моєю вимогою у мене є випадок, як 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

Пітон 2

sudo pip install natsort

Пітон 3

sudo pip3 install natsort

Деталі використання цієї бібліотеки ви знайдете тут


1
Це точніше ніж sorted()! Спасибі
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.

Це пояснює, чому вони бачать поведінку, не пропонуючи рішення.
Даніель Уоткінс

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

@ Денью дякую, що вказав на це - я цього не помічав
Димитріс,

@DanielWatkins OK, Not it is.)
Денис

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 ) )працює досить добре.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.