福冨諭の福冨論

RSSリーダーではこちらをどうぞ→https://feeds.feedburner.com/fuktommy

Python2のレガシーコードをPython3に対応させた

レガシーコードってというP2P掲示板「新月」の実装のことなのですけどね。なにしろ2005年に作り始めてて、その頃はGUIアプリのMVCの概念は知ってたけど、webアプリのMVCの概念は知らなかったので、普通にコントローラ内のprint文でHTMLの出力をしてたりとか、そういうやつです。まあPython2.7のサポートも2020年まで伸びたし、そんなに急ぐこともないというのもあるのですけど、まあやってみるかなという感じで対応しました。

    1. Python2で -3 オプションをつけて警告を出して、警告がなくなるまで修正。ユニットテストがあれば全部警告が出せるんですけど、動かしてみて警告がなくなるまでという感じだったような。それとも構文解析した時点で警告が出るんだったかな、よく覚えてないです。ただ何回かに分けてコミットされてるので、警告が出る度に直してたんじゃないかな。
      • 比較関数を指定してのソート → 最初に比較基準を数値で出してソート(効率化)
      • __getslice__()がなくなって__getitem__()を都度呼ぶようになったっぽい
      • 整数の割り算が / から // に変更
      • urllib → urllib2
      • dircacheモジュール廃止。dircacheはWindowsだとファイル更新しても反映されてなくてデバッグに苦労した覚えがあります。
    2. テンプレートエンジンとしてCheetahを使ってたのですが、まだPython3に対応してないので、Jinja2に変更しました。なおCheetahはYelpがフォークして盛んにコミットしてるので、社内で使ってるんじゃないですかね。
      • CygwinPythonが3.2でJinja2は2.7は3.2に非対応、2.6なら対応なので注意。
      • Jinja2では変数にユニコード文字列を入れる必要があって、Cheetahは普通の文字列でも動いちゃうので、適宜変換が必要です。このときはまだテンプレートにアサインするところで変換かけてました。
      • CheetahのテンプレートをJinja2で表示しても、特にエラーは出なくて、普通にテンプレートがそのまま表示されるので、1つずつ直していき、少しずつ期待した表示に近づいていきます。
    3. 2to3をかけます。これで自動的にPython3対応のコードが生成されます。あとはエラーが出るところを探して修正していくだけです。ユニットテストがあれば…
    4. http.server.CGIHTTPRequestHandler を継承して、forkしてるところをマルチスレッドに書き換えてるのですが、親クラスが変わったのでその変更点を取り込みます。こういう継承の使い方はよくないです。
    5. ファイルを開くときに文字コードを指定して、ユニコード文字列で開くようにします。これもけっこう辛い戦いでした。
      • もともとは改行コードの自動変換を防ぐためにバイナリモードで開いてたファイルが多かったのです。なのでwriteのタイミングで明示的にユニコードからビット列にしてました。
      • それも微妙なので、テキストモードで開くときに、改行コードと文字コードを指定するようにしました。
      • もともとテキストモードで開いてたファイルは、シェルからプログラムを起動したときは環境変数がLANG=ja_JP.UTF-8なので自動的に文字コードUTF-8となってうまく動くのですが、サーバーを再起動するとinit経由でLANG未指定で起動するので動かなくなるという罠があります。これもエラーが出る度に直すという感じでした。
    6. クライアントからHTTPで受け取った文字列や、サーバー間通信で取得した文字列も、同じようにユニコード文字列にする必要があります。アスキーのfooとユニコードのfooは == で比較すると別物なので、なぜか通信がうまくいかないということになってちょっと気がつくのに時間がかかりました。
    7. MD5のハッシュを作るとか、base64の変換をするとかのところも、ユニコード文字列なのかバイト列なのかに注意が必要です。
    8. バイト列とユニコード文字列の足し算もうっかりやっちゃいます。そこを通るとエラーになる系ですね。
    9. バイナリを使うときはStringIOがBytesIOに変わるとか、まあ言われてみればその通りなんですけどね。
    10. iter(foo).next() → next(iter(res)) とか、これは2to3が見落してたんですかね。
    11. サムネイルを作るのにPILという画像処理ライブラリを使ってたのですが、Python3だと(とりあえずは?)Pollowの方がよいらしくて、ライブラリ群はaptitudeでインストールする方針だったのですが、止むを得ずpipでインストールすることにしました。
    12. LANG=ja_JP.UTF-8とかの環境変数で、os.environへのアクセスが変わってくるという問題があって、これもけっこう辛かったですね。最終的にはos.environを普通の辞書にコピーすることで解決しましたが、見た目は辞書なのに中身は違うというのはけっこう罠です。このときは数日間公式ゲートウェイが落ちてたのですが、普段から403/404エラーがたくさん出てたので、携帯からlogwatchのメールを見ても見逃がしてたという恥ずかしい話が。