問題設定
ファイル/ディレクトリ名の列挙は、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) )
コメント