locked
IronPythonのstr()/unicode()関数の挙動 RRS feed

  • 質問

  • 厳密にはこの会議室では無いと思うのですが、他に適当なフォーラムが見当たらないので
    こちらに投稿します。英語が満足に使えれば本家に乗り込むのですが‥‥。
    (IronPythonはマイクロソフト管理下のプロダクトですよね?)

    IronPython 2.0 Alpha (2.0.0.800)の挙動に、マルチバイト文字列固有の問題があります。

    C#で言う、obj.ToString()メソッド(オブジェクトの文字列表現)を取得するPythonの関数は
    str(obj) 、あるいはunicode(obj)と認識しています。

    しかし、objの文字解決の結果がunicode文字(\0xff以上のコードの文字)の場合、
    IronPythonでは、str(obj)、unicode(obj)ともにUnicodeEncodeErrorとなるようです。

    # ipy コンソール
    >>> str('A')
    'A'
    >>> str('あ')
    Traceback (most recent call last):
      File , line unknown, in ##22
    UnicodeEncodeError: ('unknown', '\x00', 0, 1, '')

    >>> a=unicode('あ')
    Traceback (most recent call last):
      File , line unknown, in ##30
    UnicodeEncodeError: ('unknown', '\x00', 0, 1, '')


    IronPythonで1から組むプログラムなら素直にobj.ToString()を開放すればいいんですが、
    str()はPythonの組み込み関数ですので、CPythonの資産を利用するときに問題が出る
    可能性が高いです。

    可能であれば開発チームに問題をお伝え願えますでしょうか。一応自動翻訳な文章で
    あちらの書き込んでは見たのですが、問題の重大さが伝わっているかどうか。
    内部処理がunicodeなだけに、問題を見落としているように思います。
    SilverLight 2.0がリリースされるまでに問題が解決されると良いのですが‥‥。

    また、MSDNフォーラムではDLR関係のプロダクトはどのような扱いで取り扱うのでしょうか?
    よろしければその点も教えていただけますと幸いです。

    以上です。
    2008年2月29日 1:17

回答

  • 荒井様、ご返答ありがとうございます。
    まず1点、勘違いをお詫びします。
    >#   __str__(self) or obj.ToString() が日本語などを返す場合に全滅
    指摘どおり__str__(self)は大丈夫でした。早合点しました‥‥。

    さて、本質問自体は解決(というか疑問はあるが様子見)とします。

    #### 以下長文です、興味のある方のみご覧ください‥‥。 ####

    どうも色々調べたところ、文字列に関しては処理系依存にならざるをえな
    いような気がしてきましたので‥‥。以下、色々調べた内容です。

    試験のため、以下のスクリプトを用意してコマンドラインから
    呼び出してみました。環境は以下の通りです。
      IronPython 2.0 Alpha 2.0.0.800 on .NET 2.0.0.0
      CPython    2.5.1
      Jython     2.2.1 on java1.6.0_05

    ========================================================
    # 用意したスクリプト
    ========================================================
    # -*- coding: utf-8 -*-
    # test.py

    def T(obj,func):
        s = "ENCODE_ERR"
        try:
          s = func(obj)
        except Exception,e:
          print "* ENCODE_ERR" ,e
          return
        try:
          print s
        except Exception,e:
          print "* PRINT_ERR" ,e

    def ustr(obj):
        try:
            return str(obj)
        except UnicodeEncodeError, e:
            return unicode(obj)

    def ustr2(obj):
        try:
            return obj.__str__()
        except Exception, e:
            return obj.__unicode__()

    class UNI:
        def __str__(self):
            return u"あいうえお"

    class ASCII:
        def __str__(self):
            return "ASCII"


    ========================================================
    # インタプリタ実行          #    |C|I|J|
    ========================================================
    from test import *          #  1 | | | |
    u = UNI()                   #  2 | | | |
    a = ASCII()                 #  3 | | | |
    T(a     ,unicode)           #  4 | | | |
    T(u     ,unicode)           #  5 | | |E|E 関数実行例外
    T(u'あ' ,unicode)           #  6 | |E|P|P print例外
    T('あ'  ,unicode)           #  7 |E|E|E|
    T(a     ,ustr)              #  8 | | | |
    T(u     ,ustr)              #  9 | | |?|? 印字が崩れる
    T(u'あ' ,ustr)              # 10 | |E|E|
    T('あ'  ,ustr)              # 11 | |E| |
    T(a     ,ustr2)             # 12 | | | |
    T(u     ,ustr2)             # 13 | | |P|
    T(u'あ' ,ustr2)             # 14 |E| |E|
    T('あ'  ,ustr2)             # 15 | | | |


    ここで意図しない結果となるのは、CPythonが7,14、
    IronPythonは6,7,10,11、Jythonでは5,6,7,9,10,13,14です。

    恐らく以下のようになっているかと‥‥。
     CPython   :byte文字列 | unicode文字列 の両方がある
     IronPython:unicode文字列のみ
     Jython    :byte文字列のみ?(深入りした調査はせず)

    上記ustr2()関数ですが、__str__(self)を取り出す関数にすれば
    うまくいくかと作ってみました。が、よりによってCPythonで
    例外(14)です。

    # なんで__unicode__(self)が、unicode文字列にないかな‥‥(--;


    閑話休題。

    実はここまで書いて、CPython/IronPythonで共有できる、
    obj.ToString()を実現する文法が見つかりました。

    ========================================================
    # 互換性のある、obj.ToString()と等価な関数
    ========================================================
    def ustr3(obj):
        return '%s' % obj


    この挙動が唯一、不要なエンコードを伴わない書式化処理のようです。
    ただ、この書式もPythonマニュアルでは「str()で変換します」

    # http://www.python.org/doc/current/lib/typesseq-strings.html
    # http://www.python.jp/doc/release/lib/typesseq-strings.html

    となっているため、私の意見としては変わらず、IronPythonの実装は
    「str(obj) == obj.ToString()」とするのが最も妥当と考えています。
    # unicode(obj)は現状のまま

    ‥‥と疑問は感じますが、文字列をunicode限定にした事による、
    CPythonとの互換性の歪ととらえるしかないのかもしれません。
    とりあえず逃げの手は見つかったので様子を見ることにします。

    以上です、ありがとうございました。

     

    2008年3月12日 5:03

すべての返信

  • MAz さん、コメントが遅くなってすみません。お尋ねの件について、弊社荒井からコメントがありました(本人自身の投稿に問題が生じているそうですので、代理で投稿します)。

     

    1)str関数に対するエラーについて
    >>> str('あ')
    は、Python2.4.3では、シフトJISとして解釈されますが、本来の動作ではありません。本来の動作は、ASCII 文字に対する動作です。IronPythonで同様の表記を行った場合は、str(u'あ')となります。これは、IronPythonの内部の文字コード表現がUTF-16のためであり、str関数にUNICODEを指定するとエラーになります。これはPython言語そのものの仕様に起因しています。

     

    2)unicode関数のエラーについて
    unicode("あ")は、先ほどと同じでCPythonではシフトJISに対する変換でありエラーとなり、unicode(u"あ")と記述することで動作します。しかし、IronPythonでは内部がUTF-16のため、どちらの表記でもエラーとなります。IronPythonで同様の記述を行うのであれば、unicode(u"あ".decode(“shift-jis”), “shift-jis”)とすることで動作させることができます。

    これらの問題は、Python実装系の内部の文字表現に起因します。このため明示的にUNICODE文字列である、プレフィックス”u”を記述することをお勧めします。まだ私は知りませんが、Python3.0では内部的にUNICODEを採用すると聞いていますので、このような問題に対してどのようなアプローチをするかに私は興味を持っています。

     

    3)MSDNフォーラムにおけるDLRの取扱
    Silverlight2.0に含まれるDLRとしては、Microsoftのベータ製品という扱いになります。Codeplexやrubyforgeで公開されているDLRに関しては、MS-PLに基づく製品となります。つまりオープンソース製品という位置付けになりますので、バグや改善に関してはそれぞれのプロジェクトに対する直接の働きかけになります。このためMSDNフォーラムで議論することは大切ですが、誰かがその内容をプロジェクトにフィードバックしていく必要があると思います。

     

    この投稿は現状のまま何の保証もなく掲載しているものであり、何らかの権利を許諾するものでもありません。コミュニティにおけるマイクロソフト社員による発言やコメントは、マイクロソフトの正式な見解またはコメントではありません。詳しくは http://www.microsoft.com/japan/communities/msp.mspx をご覧ください。
    2008年3月10日 2:01
  • 大野さん、ありがとうございます。

    #まだ、うまくログインできないので、別のIDでログインしています。

     

    >C#で言う、obj.ToString()メソッド(オブジェクトの文字列表現)を取得するPythonの関数は
    >str(obj) 、あるいはunicode(obj)と認識しています。

    この話を補足しますと、PythonでToStringに相当するものは、__str__(self)という特殊属性になります。これをクラスに定義されることで、print文などが文字列オブジェクトに変換した結果を出力します。

    str関数は、ascii文字からなる文字列オブジェクトを生成するためのもです。そしてunicode関数は、文字列オブジェクトをunicodeで構成される文字列オブジェクトに変換するためのものです。

    このためCPythonで書かれたスクリプトとの互換性を保つためには、本来的には__str__メソッドを実装するか、自分が何のエンコーディングを使用しているかを意識する必要があります。互換性を考えないのであれば、以下のような関数を作成することも一つの手段だと思います。

    def tostring(obj):

      import clr

      return obj.ToString()

     

    CPythonなどへ移植する場合は、この関数の中身だけを変更すれば良いでしょう。

     

    ちなみにCPython2.4などで私が調べた限りは、文字列オブジェクトは内部的にchar配列になっており、ASCIIであろうが無かろうが文字コードがそのままの形式で格納されています。このためシフトJISを指定すると、そのままの文字コードで格納されています。Python言語の仕様に乗っ取る限りは、ASCII以外の文字列はunicodeとして扱うのが正しい方法ではないかと思います。もちろん、バイナリとして扱うのであれば、この限りではありませんけど。

     

    2008年3月10日 2:46
  • 大野様、荒井様、ご返答ありがとうございます。

    元々この件に関して質問を挙げたのは、「pyparsing」というパーサライブラリを利用しようとしたときに
    発生した問題に起因します。他にも互換性の問題が出まして、結局IronPythonでの使用は諦めたの
    ですが(^^;

    この関数内で、オブジェクトの文字列表現(obj.ToString())を取得するために以下の関数が用意されて
    いました。

    def _ustr(obj):
        try:
            return str(obj)
            
        except UnicodeEncodeError, e:
            return unicode(obj)

    'ASCII'で表現されるオブジェクトならASCII、そうでないからUNICODE表現を返す、となっています。
    この実装はCPythonの内部文字列が2種類あることに起因するのですが、これが、objがunicode
    となる場合、IronPythonでは両関数が共に例外となり、この関数は失敗してしまいます。

    CPythonのライブラリ仕様書を確認したところ、「引数が無いunicode(obj)は、str()のunicode版である」
    と解釈しました。しかし、IronPythonの実装は(恐らく)str(),basestring(),unicode()ともに同一の
    関数に振り分けられているとおもいます。

    Builtin.cs(161):         public static object basestring = DynamicHelpers.GetPythonTypeFromType(typeof(string));
    Builtin.cs(1364):         public static object str = DynamicHelpers.GetPythonTypeFromType(typeof(string));
    Builtin.cs(1377):         public static object unicode = DynamicHelpers.GetPythonTypeFromType(typeof(string));

    ascii文字列が存在しないIronPythonにとって、str関数がunicode文字で例外を出す合理的理由が
    見当たらないと感じています。合理的に考えて、
    ・CPythonの挙動と合わせる(unicode()ではasciiチェックをしない)
    ・str()がunicodeでエラーを返さない実装にする
    この何れかだと思うのですが‥‥。

    これに限らず、str()は既存コードで結構出てきます。上記のpyparsingはまだ気を利かせてくれて
    いるほうです(^^;。組み込み関数であるため影響はかなり大きいと思います。Silverlight2の正規版が
    出るまでに対応が入って欲しいと期待しています。

    追記:
    私は、str()関数の本来の目的は、ASCIIにエンコードする関数ではなく、オブジェクトの文字列表現を返す関数と
    解釈しています。実際CPythonにunicode文字列が無かった時代はエンコード処理は無かったはずです。
    unicode文字列追加時に、unicode->ascii変換の変換チェックが追加され、
    同時にunicode()関数が設けられたものと考えています。

    unicode()関数には
    「引数が無い場合、オブジェクトのunicode表現を返す、元々unicodeの時はそのまま返す」
    「引数がある場合、指定されたエンコードの入力と見なしてエンコード処理を行う」
    という、2つの役割が同居していると解釈しています。
    この2つは全然違う処理なので関数分けたほうが良かったと思いますが‥‥(^^;

    追記2:
    オープンソース扱いとのこと、了解しました。そういえばDLR自体には含まれていない?
    組み込み関数、且つ日本語の取り扱いで致命傷になりうる部分なので
    #   __str__(self) or obj.ToString() 日本語などを返す場合に全滅
    フィードバックしたいのですが、なにぶん英語圏で発生しない問題を熱弁するだけの
    英語力がなく‥‥。無駄な長文すいません(--;
    2008年3月11日 0:33
  • MAzさん、ご連絡ありがとうございます。

     

    >ascii文字列が存在しないIronPythonにとって、str関数がunicode文字で例外を出す合理的理由が

    この点ですが、1バイト系のみはASCIIとして処理していますし、またスクリプトファイルもデフォルトでASCII、UTF-8、UTF-16エンコーディングを識別するようになっています。

     

    >私は、str()関数の本来の目的は、ASCIIにエンコードする関数ではなく、オブジェクトの文字列表現を返す関数と
    解釈しています。

    了解しました。私は組み込みデータ型を生成するためのものであると理解していますので、前述のような記述になっています。

     

    >これに限らず、str()は既存コードで結構出てきます。

    これは確かにそうですね。全てが移植されているわけではないですが、CPythonとの互換性を向上したFePyというプロジェクトもあります。このFePyの中でも、pyparsingは含まれていませんが。

    何方かご存知ならJythonでは、どうしているかを教えていただけませんでしょうか。

    FePyプロジェクトをリードされているSeoさんという方がいらっしゃるんですけど、この方は韓国に在住されている方なので、日本語に関わらずUNICODEの問題に気がついていると思うのですが。FePyでもstr関数に関しては、同様の挙動を示します。

     

    UNICODE関数に限りませんが、文字列のエンコーディング方法をオブジェクトが持っているわけではないので、100%の解決方法は無いのではないかと私は考えています。俗に云う文字化け問題です。ある程度まで対応できるアルゴリズムはありますが、100%ではないので。

     

    >#   __str__(self) or obj.ToString() 日本語などを返す場合に全滅

    え、そうですか? 私が使っている限りは、問題なく日本語のUnicodeを返せています。表示に関してはフォントの問題がありますけど。試しているのは、IronPython 1.1、2.0A8、Silverlight2.0Beta1です。ファイルのエンコーディングによっては、u'\u3042’と記述するか、u'あ'などと記述しています。宜しければ、確認された環境と具体例を教えていただけませんか?

     

    #ちなみにSilverlight1.1αでは、フォントの問題からXAML上には日本語を表示できませんでしたが、IronPythonのコード中で問題なく日本語(UNICODE)を使用できているのを確認しています。

    2008年3月11日 3:00
  • 荒井様、ご返答ありがとうございます。
    まず1点、勘違いをお詫びします。
    >#   __str__(self) or obj.ToString() が日本語などを返す場合に全滅
    指摘どおり__str__(self)は大丈夫でした。早合点しました‥‥。

    さて、本質問自体は解決(というか疑問はあるが様子見)とします。

    #### 以下長文です、興味のある方のみご覧ください‥‥。 ####

    どうも色々調べたところ、文字列に関しては処理系依存にならざるをえな
    いような気がしてきましたので‥‥。以下、色々調べた内容です。

    試験のため、以下のスクリプトを用意してコマンドラインから
    呼び出してみました。環境は以下の通りです。
      IronPython 2.0 Alpha 2.0.0.800 on .NET 2.0.0.0
      CPython    2.5.1
      Jython     2.2.1 on java1.6.0_05

    ========================================================
    # 用意したスクリプト
    ========================================================
    # -*- coding: utf-8 -*-
    # test.py

    def T(obj,func):
        s = "ENCODE_ERR"
        try:
          s = func(obj)
        except Exception,e:
          print "* ENCODE_ERR" ,e
          return
        try:
          print s
        except Exception,e:
          print "* PRINT_ERR" ,e

    def ustr(obj):
        try:
            return str(obj)
        except UnicodeEncodeError, e:
            return unicode(obj)

    def ustr2(obj):
        try:
            return obj.__str__()
        except Exception, e:
            return obj.__unicode__()

    class UNI:
        def __str__(self):
            return u"あいうえお"

    class ASCII:
        def __str__(self):
            return "ASCII"


    ========================================================
    # インタプリタ実行          #    |C|I|J|
    ========================================================
    from test import *          #  1 | | | |
    u = UNI()                   #  2 | | | |
    a = ASCII()                 #  3 | | | |
    T(a     ,unicode)           #  4 | | | |
    T(u     ,unicode)           #  5 | | |E|E 関数実行例外
    T(u'あ' ,unicode)           #  6 | |E|P|P print例外
    T('あ'  ,unicode)           #  7 |E|E|E|
    T(a     ,ustr)              #  8 | | | |
    T(u     ,ustr)              #  9 | | |?|? 印字が崩れる
    T(u'あ' ,ustr)              # 10 | |E|E|
    T('あ'  ,ustr)              # 11 | |E| |
    T(a     ,ustr2)             # 12 | | | |
    T(u     ,ustr2)             # 13 | | |P|
    T(u'あ' ,ustr2)             # 14 |E| |E|
    T('あ'  ,ustr2)             # 15 | | | |


    ここで意図しない結果となるのは、CPythonが7,14、
    IronPythonは6,7,10,11、Jythonでは5,6,7,9,10,13,14です。

    恐らく以下のようになっているかと‥‥。
     CPython   :byte文字列 | unicode文字列 の両方がある
     IronPython:unicode文字列のみ
     Jython    :byte文字列のみ?(深入りした調査はせず)

    上記ustr2()関数ですが、__str__(self)を取り出す関数にすれば
    うまくいくかと作ってみました。が、よりによってCPythonで
    例外(14)です。

    # なんで__unicode__(self)が、unicode文字列にないかな‥‥(--;


    閑話休題。

    実はここまで書いて、CPython/IronPythonで共有できる、
    obj.ToString()を実現する文法が見つかりました。

    ========================================================
    # 互換性のある、obj.ToString()と等価な関数
    ========================================================
    def ustr3(obj):
        return '%s' % obj


    この挙動が唯一、不要なエンコードを伴わない書式化処理のようです。
    ただ、この書式もPythonマニュアルでは「str()で変換します」

    # http://www.python.org/doc/current/lib/typesseq-strings.html
    # http://www.python.jp/doc/release/lib/typesseq-strings.html

    となっているため、私の意見としては変わらず、IronPythonの実装は
    「str(obj) == obj.ToString()」とするのが最も妥当と考えています。
    # unicode(obj)は現状のまま

    ‥‥と疑問は感じますが、文字列をunicode限定にした事による、
    CPythonとの互換性の歪ととらえるしかないのかもしれません。
    とりあえず逃げの手は見つかったので様子を見ることにします。

    以上です、ありがとうございました。

     

    2008年3月12日 5:03
  • MAzさん、ご連絡ありがとうございます。それと具体的な例もご提示下さって、本当に助かります。

     

    対応策を見つけられたようで、何よりです。

     

    >文字列をunicode限定にした事による、CPythonとの互換性の歪ととらえるしかないのかもしれません。
    はい。この辺りは、実装上のプラットフォームから来る制限だと私も考えています。

    但し、現状でもコミュニティなどから改善要望として出ているという話を私は知りません(私が知らないだけかも知れませんが)。もっとも、この問題に対処するようにソースコードに改善を加えるという方法もありますよね。Silverlightも前提に考えると、無理だと思いますけど。

    先にご紹介したFePyプロジェクトでは、socketやreなどがCPythonと異なるので、ソースコードにパッチを投入したり、モジュールの差し替えをしたりしています。この辺りは、どのような解決策が良いかを考える必要があると思います。

     

    今後ともIronPythonを宜しくお願いいたします。

     

     

     

     

     

     

     

    2008年3月12日 5:49