IronPythonとShift_JIS

鉄のヘビさん

オレオレツールを作った際にCPythonとIronPythonの違いに詰まった時のお話です。

弊社はなんちゃってマカーなので、自宅ではMacを使用しています。 同じPython2.7系だし、基本的に同じコードでMac OS 10.10 + CPython 2.7.6とWindows 8.1 + IronPython 2.7.5の両方で動作するだろうと考え、まずはMac上で開発を行い、完成したらVM上のWindowsでテストすれば大丈夫だろうと踏んだ訳です。ハイ

Shift_JISエンコードされたテキストファイルを読み込みあーんな処理やこーんな処理を行った後、結果をShift_JISでテキストファイルに書き出すプログラムを作り、いざWindows上のIronPython 2.7.5で動作させた所ファイルを読み込む段階で『Shift_JIS?知らない子ですね。』と言われてしまいました。

# -*- coding: utf-8 -*-

import io

def main():
    fin = io.open('in.txt', encoding='Shift_JIS')
    fout = io.open('out.txt', mode='w', encoding='Shift_JIS')
    with fin, fout:
        for it in fin:
            fout.write(it.strip() + u'世界' + '\n')

if __name__ == '__main__':
    main()

無慈悲なスタックトレース

Traceback (most recent call last):
  File "readfile.py", line 14, in <module>
  File "readfile.py", line 10, in main
LookupError: unknown encoding: Shift_JIS

ちなみにin.txtの中身がコレ(エンコードはちゃんとShift_JIS(さらに正確に言うとCP932))

こんにちは
さようなら

ググってみると似たような問題にぶつかっている人のブログが幾つか見つかりましたが、

IronPythonなんだし、.NETのStreamReaderとか使えばいいんじゃね?(超意訳)

とのことでした。

弊社としてはPythonで書くならできればどこでも動くコードを書きたいと思っておりますが故、StreamReaderなどは使いたくないと言うのが本音です。 そんだったらWindowsにCPythonをインストールしろよデコ助野郎などのお言葉が聞こえてきそうですが。

読んでからエンコード

『読み込み時にUnicodeへ変換できないなら読み込んでから変換すれば良いじゃない』とマリーアントワネットな発想に至りまして、とりあえず以下のコードを試してみました。

# -*- coding: utf-8 -*-

def main():
    fin = open('in.txt')
    with fin:
        for it in fin:
            print type(it)
            print repr(it)
            uni = unicode(it, encoding='Shift_JIS')
            print type(uni)
            print repr(uni)
            print

if __name__ == '__main__':
    main()

んで、実行結果がコレ

CPython

<type 'str'>
'\x82\xb1\x82\xf1\x82\xc9\x82\xbf\x82\xcd\n'
<type 'unicode'>
u'\u3053\u3093\u306b\u3061\u306f\n'

<type 'str'>
'\x82\xb3\x82\xe6\x82\xa4\x82\xc8\x82\xe7\n'
<type 'unicode'>
u'\u3055\u3088\u3046\u306a\u3089\n'

IronPython

<type 'str'>
u'\x82\xb1\x82\xf1\x82\xc9\x82\xbf\x82\xcd\n'
<type 'str'>
u'\u3053\u3093\u306b\u3061\u306f\n'

<type 'str'>
u'\x82\xb3\x82\xe6\x82\xa4\x82\xc8\x82\xe7'
<type 'str'>
u'\u3055\u3088\u3046\u306a\u3089'

なんかちがう。なんかちがう。

IronPythonですと、strクラスでUnicodeを表現しているっぽい。

.NETのSystem.Stringは内部ではUnicodeで表現されていますから、.NETとの連携を考えたら文字列を表すクラスはUnicodeで統一したほうが何かと都合が良いのでしょうか。

いずれにしろ、バイナリで読み込んでからUnicodeへ変換する方法ならなんとかShift_JISのファイルも読み込めそうです。(これが正しい処理方法かどうかは分かりませんが)

ということでこんな感じなら一応

# -*- coding: utf-8 -*-

def main():
    with open('in.txt') as fin:
        lines = [unicode(it, encoding='Shift_JIS').strip() for it in fin]

    with open('out.txt', mode='w') as fout:
        for it in lines:
            fout.write((it + u'世界' + u'\n').encode('Shift_JIS'))

if __name__ == '__main__':
    main()

ファイルを読み込みつつリスト内包表記でUnicodeに変換しつつ余計な改行を削除してリストに追加しています。 書き出す時も自分で明示的にShift_JISに変換してそれを書き出させています。

この書き方ですとファイルの中身が全部メモリに載ってしまうので膨大なサイズのファイルを扱う際は別のやり方を行う必要がありますが、常識的なサイズのファイルなら大丈夫でしょう。

CPythonとIronPythonで動作確認を行い、どちらも正しく処理できているみたいです。

おまけ

コードの書き方とメンテナンスコスト

ただ、まぁ、なんというか、ここまで書いておいてアレですが、今回の場合の様な一部の環境のためにコードをエキセントリックな書き方をするくらいなら問題の領域を動作環境ごとに作ったほうが後々のメンテナンスでは有利なんじゃないかなと思います。

いくら行数が短いとはいえ定石から外れたコードは理解するのに多少なりとも時間がかかりますし、定石から外れるコードを書かなくてはならない場合はドキュメントとしてしっかり説明文を書いておくべきでしょう。

Pythonと日本語

よくホームページや書籍などで『Pythonは初心者向け』などといった謳い文句をよく目にします。 私はどんな特徴があれば初心者向け言語になるのかよく分かりませんが、確かにPythonは書き易いと思います。

ただ、Pythonは日本語が絡むと話は変わります。 ファイルを読み込もうとしたら例外、書き出そうとしたら例外、コンソールに表示しようとしたら例外。 なんかこの辺りが日本でPythonユーザが少ないと言われる原因になってる気がします。

しかし、キチンと処理すれば問題なく日本語も扱えますし、3.x系なら標準でUnicodeとして扱われるので初心者の方も怖がらすPythonを使っていただきたいとおもいます。