名前順でソート(XPスタイル)

glob.glob()で得られるファイル名の順番は、

名前順ではないことに注意。

001.jpg, ... , 100.jpgの画像フォルダ内を

glob.glob()して得たリストの順番は名前順ではない。

list.sort()すればいいけれど。



peroonの日記


しかし、list.sort()すると、1.jpg, 10.jpg, 11.jpg, ... 2.jpg となる事に注意。


WinXP以降のように、数字を認識してソートするには、一手間必要です。



まず、Pythonクックブックの、正規表現を使って自力で書く方法。
OSを気にせず使えます。

alist = ["{0}.jpg".format(i) for i in [10, 11, 0, 1, 2, 12]]

def embedded_numbers(name):
    import re
    pieces = re.split(r"(\d+)", name)
    pieces[1::2] = map(int, pieces[1::2])
    return tuple(pieces)

print(sorted(alist, key=embedded_numbers)) 
# => ['0.jpg', '1.jpg', '2.jpg', '10.jpg', '11.jpg', '12.jpg']



次にctypesを使ってWinAPIを呼ぶ方法。


当然Windows限定ですが、Windowsのソート方法を確実に再現できます(多分バグも)。


ちなみに、StrCmpLogicalWにANSI版は無いようです。

alist = ["{0}.jpg".format(i) for i in [10, 11, 0, 1, 2, 12]]

import ctypes
SHLWAPI = ctypes.windll.LoadLibrary("SHLWAPI.dll")
def cmp_filename_logical(f1, f2):
    return SHLWAPI.StrCmpLogicalW(unicode(f1), unicode(f2))

print(sorted(alist, cmp=cmp_filename_logical)) 
# => ['0.jpg', '1.jpg', '2.jpg', '10.jpg', '11.jpg', '12.jpg']



最後に、ctypesを使う方法の変形。

list.sortやsortedのcmp引数は、Python3xでは廃止されています。


よって、上の方法は3xでは使えません。


ドキュメントにもあるとおり、
ASPN cookbookのレシピ
を使います。

alist = ["{0}.jpg".format(i) for i in [10, 11, 0, 1, 2, 12]]

import ctypes
SHLWAPI = ctypes.windll.LoadLibrary("SHLWAPI.dll")
def cmp_filename_logical(f1, f2):
    return SHLWAPI.StrCmpLogicalW(unicode(f1), unicode(f2))

def CmpToKey(mycmp):
    'Convert a cmp= function into a key= function'
    class K(object):
        def __init__(self, obj, *args):
            self.obj = obj
        def __lt__(self, other):
            return mycmp(self.obj, other.obj) == -1
        def __gt__(self, other):
            return mycmp(self.obj, other.obj) == 1
        def __eq__(self, other):
            return mycmp(self.obj, other.obj) == 0
        def __le__(self, other):
            return mycmp(self.obj, other.obj) != 1  
        def __ge__(self, other):
            return mycmp(self.obj, other.obj) != -1
        def __ne__(self, other):
            return mycmp(self.obj, other.obj) != 0
    return K
    
print(sorted(alist, key=CmpToKey(cmp_filename_logical))) 
# => ['0.jpg', '1.jpg', '2.jpg', '10.jpg', '11.jpg', '12.jpg']



上に挙げた3つの方法の速度を比較します。

def sorted1(alist):
    return sorted(alist, key=embedded_numbers)

def sorted2(alist):
    return sorted(alist, cmp=cmp_filename_logical)

def sorted3(alist):
    return sorted(alist, key=CmpToKey(cmp_filename_logical))

def main():
    TestList = ["{0}.jpg".format(random.randrange(100)) for i in xrange(100)]
    
    from timeit import Timer
    for f in [sorted, sorted1, sorted2, sorted3]:
        t = Timer(lambda :f(TestList))
        print(f.__name__, "->", t.timeit(1000))

#結果
#sorted  -> 0.0443061897532
#sorted1 -> 1.18444467104
#sorted2 -> 1.52561428897
#sorted3 -> 2.14697398692


ctypesを使った方が若干遅いようですが、
ファイル名ソートなんてそんなにたくさんするものでもないでしょうから、
StrCmpLogicalWを使ったパターンの利用にためらいは不要でしょう。
もちろん、MacやUnixなら、embedded_numbers一択ですが。