スポンサーサイト

    上記の広告は1ヶ月以上更新のないブログに表示されています。
    新しい記事を書く事で広告が消せます。

    クラスで__new__する

    Pythonで自身の変更不可なオブジェクトを継承する時は__init__ではなく__new__メソッドを使用しますね。
    class MyUnicode( unicode ):
        def __new__( cls, value ):
            newValue = value + ' +Unicode'
            return super( MyUnicode, cls ).__new__( cls, newValue )
    
    # Ex.
    uni = MyUnicode( 'test' )
    print( uni )
    # Result : test +Unicode
    

    この例ではMyUnicodeのインスタンスを生成する時に与えた文字列に" +Unicode"と言う文字を強制的に付加された状態で返ってくると言うクラスです。(変な例ですけど気にしない(^_^;)

     この時、__new__の第一引数に渡すのは通常のクラスメソッドで使用される"self"ではなく"cls"になります。
     なんで? って話ですが、通常のクラスメソッドと違ってこちらは特殊なメソッドだからです!!!
     とドヤ顔で言った所でワケワカメなんでもうちょっと詳しく見てせう~



     通常のクラスメソッドの場合の第一引数"self"はその名の通り己自身です。
     従ってそのクラスの持っているメソッドは基本的に全て使えます。
    class MyClass( object ):
        def __init__( self, value ):
            newvalue = self.valuetest( value )
            self.result = newvalue
        def valuetest( self, value ):
            if value < 10:
                return True
            else:
                return False
    
    # Ex.
    check = MyClass( 8 )
    print( check.result )
    # Result : True
     __init__は初期化関数ですが、当然__init__の中のでもselfはvaluetest関数を使用できます。
     ところが__new__の中で同じ事をやろうとしてもうまくいきません。
    class MyClass( object ):
        def __new__( cls, value ):
            newvalue = cls.valuetest( value )
            cls.result = newvalue
            return super( MyClass, cls ).__new__( cls, value )
        def valuetest( self, value ):
            if value < 10:
                return True
            else:
                return False
    
    # Ex.
    check = MyClass( 8 )
    # Error: TypeError: file  line 3: unbound method valuetest() must be called with MyClass instance as first argument (got int instance instead) # 
    
     なんてこった・・・
     なんでこんなにクラス関数を呼びたいかって言うと、インスタンス作成時に与えられた引数の内容に応じて色々内容を変更したいのですが、その判断部分をクラスメソッド化する事によって__new__とは切り離して管理し、カスタマイズを簡単にさせたかったからです。
     継承されている子供のクラスが毎回__new__メソッドを定義するってのもなんかアホらしいですもんね~。

     ダイレクトにはクラス関数を呼べない・・・しかしクラス関数を呼んで判断部分を別パーツ化しておきたい・・・
     色々悩んだ上で出た結論! それはスタティックメソッドにすればいいんでなかろうか!って事でした。
    class MyClass( object ):
        def __new__( cls, value ):
            newvalue = cls.valuetest( value )
            cls.result = newvalue
            return super( MyClass, cls ).__new__( cls )
        @staticmethod
        def valuetest( value ):
            if value < 10:
                return True
            else:
                return False
    
    class MyRelatedClass( MyClass ):
        @staticmethod
        def valuetest( value ):
            if value < 20:
                return True
            else:
                return False
    
    # Ex.
    check = MyRelatedClass( 12 )
    # Result : True
    checkB = MyRelatedClass( 22 )
    # Result : False
    
     やったぁ~、出来たぁぁぁぁ(*´∀`*)
     しかしこれをガチンコ先生に見せたところ一言「これはないでしょww」と全否定・・・まぁ継承する度に毎回@staticmethod宣言とかアホ丸出しですからね・・・わかってましたよ、チェッ・・・
     なんて、凹んでたのもつかの間、これにはトンデモないバグが潜んでいたのです!!
    check = MyRelatedClass( 12 )
    checkB = MyRelatedClass( 22 )
    print( check.result )
    # Result : False
    
     あれ、本来変数checkのresultはTrueになるはずなのにcheckBの後にresultをprintしたらFalseになってる・・・(?_?;)
     これは何かおかしいぞ、って事で色々調べてみました。



    結果・・・
     試しにselfとclsをそれぞれprintして見ると
    self : <__main__.MyClass object at 0x000000003F0B3EB8>
    cls : <class '__main__.MyClass'>
    な・・・中身が全然違う!!






     オレ達はとんでもない思い違いをしていたようだ。これを見てみろ。
    __new__(	cls[, args...])
    __init__(	self[, ...])
     __new__メソッドの第一引数には"cls"とある。
     おれはこの"cls"を完全に見落としていたんだ。なぜ__init__はselfなのに__new__はclsなのか・・・
     __new__とは一体何者なのか・・・それは!

    __new__とは暗黙的に宣言されているスタティックメソッドだったんだよっ!!!!


    な・なんだってぇぇぇぇぇぇぇぇっ!!!?
    <AA略>



     MMRめっちゃ好きやぁ~(*´▽`*)
     って事で実は__new__メソッドは暗黙的に宣言されているスタティックメソッドのため第一引数の内容が変わるんだとか・・・
     なのでこの第一引数には「インスタンスを 生成するよう要求されているクラス」をとるそうです。
     めっちゃマニュアルに書いてました、今知った・・・ガ━━(;゚Д゚)━━ン!!!
     え、暗黙的って何って? それは「言わせんな恥ずかしい!」って事ですね。


     上記の通り__new__の第一引数は「インスタンスを 生成するよう要求されているクラス」なので、どんなに頑張ってもselfのようなクラスのインスタンスじゃないためクラス関数を呼べません。
     また、clsに変更を加えてしまうとインスタンスではなく本体に変更がかかってしまうので、当然インスタンスのみなさんはみんな結果が同じになってしまいます。

     仕組みさえわかれば後の処置は簡単!
    class MyClass( object ):
        def __new__( cls, value ):
            clsObject = super( MyClass, cls ).__new__( cls )
            newvalue = cls.valuetest( clsObject, value )
            clsObject.result = newvalue
            return clsObject 
        def valuetest( self, value ):
            if value < 10:
                return True
            else:
                return False
    
    class MyRelatedClass( MyClass ):
        def valuetest( self, value ):
            if value < 20:
                return True
            else:
                return False
     先にsuper使って親クラスのインスタンスを作ってしまい、clsメソッドにそれを引数として渡して使ってやればよかったんですね。
     仕組みが分かってれば当たり前のことなんですが、ここに行きつまでにどれだけ悩んだか・・・
     これで判断部分をクラスメソッド化して、継承されたクラス内でカスタマイズしてやる仕組みが出来ました!
     上記の4行目なんかを見ると普段クラスメソッドの第一引数に必ずselfを入れてる合理的な理由なんかも垣間見えますね。
     また、インスタンスオブジェクト.アトリビュート=値で簡単にオブジェクトにパラメータを追加できる仕組みもこういう使われ方を見ると納得ですね。非常に勉強になりました\(^o^)/



    本日の一言
    マニュアルはきちっと読みましょう!
    スポンサーサイト

    コメントの投稿

    非公開コメント

    プロフィール

    Eske

    Author:Eske
    萌えイラストレーターを目指す3DCGイラストレーター。
    現在ポケモンカードゲーム、ガンダムトライエイジ、ガンダムコンクエスト、妖怪ウォッチとりつきカードゲームなどで3DCGを使用したイラストレーターとして参加中。

    主にここでは日々気づいたメモなんかを残してます。
    イラストのお仕事も受け付けております。ココからアクセスできますので、お気軽にご相談下さい。

    最新記事
    最新コメント
    カテゴリ
    最新トラックバック
    月別アーカイブ
    検索フォーム
    リンク
    QRコード
    QR
    上記広告は1ヶ月以上更新のないブログに表示されています。新しい記事を書くことで広告を消せます。