パスワードを忘れた? アカウント作成
この議論は賞味期限が切れたので、アーカイブ化されています。 新たにコメントを付けることはできません。

「Python 3.0」リリース、仕様変更多数」記事へのコメント

  • by T.Sawamoto (4142) on 2008年12月05日 14時37分 (#1468089)
    2to3.py(非互換チェックツール)を使って少しずつスクリプトを修正しているのですが、少々トラブルに出くわしました。
    zipファイル形式の圧縮/伸張ライブラリzipfile.ZipFileで、ファイル名エンコーディングがUTF-8決めうちになってしまったようです。

    従来のzipfile.ZipFile.writeは、zipに格納されるファイル名に8ビット文字列をそのまま使っていたため、エンコーディングを自分で選ぶことができました。
    (とゆーか、ライブラリ側が関知するところではなかった(^^;))
    しかし、Python3.0からはUNICODE文字列で与える形式となり、かつ今のところエンコーディングを変更することはできないみたい。

    import zipfile
    z = zipfile.ZipFile('hoge.zip', 'w', zipfile.ZIP_DEFLATED)
    z.write('はろー.txt')
    z.close()
    # ↑こうすると、Windowsでは解凍時にファイル名が文字化け。
    # WindowsではShift_JIS(CP932)が一般的なので。

    元々zipファイルはファイル名エンコーディングに問題を含んでいて、MacOSXで圧縮したzipをWindows側で解凍するとファイル名が文字化けするといったことが起きていたわけですけど、これがPython3.0で表面化してしまった感じですねー。

    仕方ないので、zipfileを使っていたところをtarfileに置き換えて修正を進めてます。
    標準がUNICODE文字列であることは実際便利で、今までエンコーディング変換で手間をかけていたところがすっきり書けるのは良いんですけど、しばらくは様々な問題が頻出しそうな予感がします。
    • zipfileはマルチバイト圏だけでなく、Latin-1圏でも問題になりそうな仕様変更ですね。
      親コメント
    • by Anonymous Coward
      リファレンス [python.org]には

      Note
      There is no official file name encoding for ZIP files. If you have unicode file names, you must convert them to byte strings in your desired encoding before passing them to write(). WinZip interprets all file names as encoded in CP437, also known as DOS Latin.

      って書いてあるけど、これってファイルネームを自分でエンコードしたbytesで渡せってことだよね。
      未確認だけどこれでできないの?
      • by T.Sawamoto (4142) on 2008年12月05日 17時45分 (#1468247)
        #1468217さんがおっしゃるとおり、2.6以前のときの使い方ですね。

        3.0の zipfile.py を参照すると、圧縮時はまず 'ascii' でエンコードしてみて、例外が出るようなら 'utf-8' で再度エンコードします。(zipfile.py:335)

        # このときフラグ(0x800)を立てているようですが、Vistaエクスプローラ/Lhplus/+Lhaca等の解凍ツールはチェックしてないみたいで、いずれもUTF-8は文字化けしちゃいます。

        解凍時はフラグを見て、0x800が立っていたら 'utf-8'、そうでなければ 'cp437' でファイル名をデコードします。(zipfile.py:759)
        つまり、cp437(DOS-US)だけは正常に解凍できるという極悪仕様(^^;)
        親コメント
        • by Anonymous Coward
          おっとこっち [python.org]が3.0ですね。
          いや、3.0でも同じですね。
          ということは、仕様というよりは実装の問題ですから、言えばすぐに直されるんじゃないでしょうか。
          • by T.Sawamoto (4142) on 2008年12月05日 20時59分 (#1468360)
            3.0の方もそう記述されてましたか。
            ただ、現実問題として「すぐに直せる」レベルかどうかは少し微妙かもです。

            ちょっと今は試せないんですが(作業が残っているため、まだ自宅マシンには導入できない(^^;))、3.0からは文字列がUNICODEになってしまった(8ビットの方はバイト列扱いのbytes型になった)ので、多分ですが中でエンコード/デコードが必要になってくると思われます。
            現状だとファイル名エンコーディングを明示するインターフェースがZipFileに存在しないため、仕様を追加しないと対応は難しいんじゃないかと。

            そもそもは現状の、「zip内ファイル名エンコーディングが環境依存」という状況が良くないんですよね……。
            どのみちOSX等とのやり取りで支障を来しているので、私はこの際zipを捨ててtar.gzに移行する予定です(^^;)
            親コメント
            • by Anonymous Coward
              zipfile.pyで勝手にencode、decodeされているのは6ヶ所くらいなので、さほどの手間では
              ないでしょうが、この手の問題は他にもいろいろな場面ででてきそうですね。

              一つの対処法としてstrやbytesのメンバーとしてencodingを設定できるようにするのは
              どうだろう。これなら意図したとおりに変換できるし、自動変換させればstrとbytesを
              演算可能にもできる。
              • by Anonymous Coward on 2008年12月06日 14時51分 (#1468698)
                とりあえず次のパッチは多分必須

                --- zipfile2.py 2008-12-06 12:35:52.000000000 +0900
                +++ Python-3.0/root/usr/local/lib/python3.0/zipfile.py 2008-12-06 10:50:20.000000000 +0900
                @@ -877,8 +877,7 @@
                                  if fheader[_FH_EXTRA_FIELD_LENGTH]:
                                          zef_file.read(fheader[_FH_EXTRA_FIELD_LENGTH])

                -# if fname != zinfo.orig_filename.encode("utf-8"):
                - if fname != zinfo.orig_filename.encode("utf-8" if zinfo.flag_bits & 0x800 else "cp437"):
                + if fname != zinfo.orig_filename.encode("utf-8"):
                                          raise BadZipfile(
                                                      'File name in directory %r and header %r differ.'
                                                      % (zinfo.orig_filename, fname))
                別の場所で'utf-8'と'cp437'を分けてエンコードしてるのに、ここでは'utf-8'決め打ちという
                のは完全なミスでしょう。

                あとはファイル名に関しては

                if info.flag_bits & 0x800:
                    filename = info.filename
                else:
                    filename = info.filename.encode('cp437').decode('cp932')
                という感じでやれば、cp437とcp932の文字コードの共通の範囲内ならば、変換可能。

                今回3.0を使ってみて、bytesとstrの互換性の無さが気になった。
                'abc'+b'123'やb'asd'.index('s')がエラーになるのは使いにくい。
                親コメント
        • by Anonymous Coward

          解凍時はフラグを見て、0x800が立っていたら 'utf-8'、そうでなければ 'cp437' でファイル名をデコードします。(zipfile.py:759)
          つまり、cp437(DOS-US)だけは正常に解凍できるという極悪仕様(^^;)

          appnote [pkware.com]のAPPENDIX D - Language Encoding (EFS)に従っただけですね。確かに cp437 だけってのは互換性に対する配慮が足りないという気はしますが、Java の java.util.zip みたいにフラグも立てずに utf-8 で出力するよりはマシだと思います。
      • by Anonymous Coward
        このドキュメントはPython2.6のものですね。
    • by Anonymous Coward
      >zipファイル形式の圧縮/伸張ライブラリzipfile.ZipFileで、
      >ファイル名エンコーディングがUTF-8決めうちになってしまったようです。

      それってjarファイルじゃないですか。

      しかし、思ったのはやっぱりリリースされてから
      試さない人がいるから、それなりの品質でもリリースしてしまった方がいいんだなぁ、ということ。

      # MSはよく分かってる

犯人は巨人ファンでA型で眼鏡をかけている -- あるハッカー

処理中...