Python3で、文字コードがばらばらなファイル名たちを一緒に列挙する方法

プログラミングor技術
結論からいうとbytes型を介します。

問題設定

ファイル/ディレクトリ名の列挙は、 os.walk() os.listdir() や、 glob.glob() などによって行いますよね。
これらは、列挙したいパスとしてstr型を渡すとファイル/ディレクトリ名などをstr型で返してきます。 str型で返すところがクセモノで、たとえばOSは標準でUTF-8を使ってファイル名をエンコーディングしている場合に、一部のファイルでCP932 (or Sift_JIS)でエンコーディングしたファイル名を使用していたときなど、str型への変換ができずにエラーが出たり文字化けしたりします。(だったはず. ちゃんと検証してないけど) 例として、通常はLinuxのファイル名はUTF-8なことが多いです。
この中にWindowsから持ってきたZipの展開とかでCP932などのファイル名が紛れてしまっていたりすると、面倒くさいことになります。
import os

target_path_str = "/hoge/fuga/"
for filename_str in os.listdir(target_path_str):
  # ファイル名が標準的でない(そのシステムのデフォルトはUTF-8だがcp932のものが混じってるなど)場合に、文字化けorデコードエラーが発生
    print(filename_str)

解決法

列挙対象のパスをstr型でなくbytes型os.walk() os.listdir() や、 glob.glob() に渡せばOKです。
すると、str型でなくbytes型で結果が返ります。 それを str.decode() で好きに文字コードを指定してデコードすればok.
import os

def decode_to_str(target_bytes):
    try:
        return target_bytes.decode('utf8')
    # UTF8でデコードできないバイト列の場合、他の文字コードを試す
    except UnicodeDecodeError:
        return target_bytes.decode('cp932')

target_path_str = "/hoge/fuga/"
for filename_bytes in os.listdir(target_path_str.encode('utf8')):
    # ファイル名の文字コードがUTF-8以外でもstr型に直して表示できる
    print( decode_to_str(filename_bytes) )
決め打ちでなく、chardet を使って文字コードを判定してやりたい場合は以下のようにします。
(ただし、短めの文字列なんかでは誤判定もしやすいので慎重に。)
import os
import chardet

def decode_to_str(target_bytes):
    char_encoding = chardet.detect(target_bytes)["encoding"]
    return target_bytes.decode(char_encoding)

target_path_str = "/hoge/fuga/"
for filename_bytes in os.listdir(target_path_str.encode('utf8')):
    # ファイル名の文字コードがUTF-8以外でもstr型に直して表示できる
    print( decode_to_str(filename_bytes) )

コメント