Pythonでインスタンスを再分類する 質問する

Pythonでインスタンスを再分類する 質問する

外部ライブラリから提供されたクラスがあります。このクラスのサブクラスを作成しました。元のクラスのインスタンスも持っています。

ここで、インスタンスが既に持っているプロパティを変更せずに (サブクラスがオーバーライドするものは除く)、このインスタンスをサブクラスのインスタンスに変換したいと思います。

次の解決策が機能するようです。

# This class comes from an external library. I don't (want) to control
# it, and I want to be open to changes that get made to the class
# by the library provider.
class Programmer(object):
    def __init__(self,name):
        self._name = name

    def greet(self):
        print "Hi, my name is %s." % self._name

    def hard_work(self):
        print "The garbage collector will take care of everything."

# This is my subclass.
class C_Programmer(Programmer):
    def __init__(self, *args, **kwargs):
        super(C_Programmer,self).__init__(*args, **kwargs)
        self.learn_C()

    def learn_C(self):
        self._knowledge = ["malloc","free","pointer arithmetic","curly braces"]

    def hard_work(self):
        print "I'll have to remember " + " and ".join(self._knowledge) + "."

    # The questionable thing: Reclassing a programmer.
    @classmethod
    def teach_C(cls, programmer):
        programmer.__class__ = cls # <-- do I really want to do this?
        programmer.learn_C()


joel = C_Programmer("Joel")
joel.greet()
joel.hard_work()
#>Hi, my name is Joel.
#>I'll have to remember malloc and free and pointer arithmetic and curly braces.

jeff = Programmer("Jeff")

# We (or someone else) makes changes to the instance. The reclassing shouldn't
# overwrite these.
jeff._name = "Jeff A" 

jeff.greet()
jeff.hard_work()
#>Hi, my name is Jeff A.
#>The garbage collector will take care of everything.

# Let magic happen.
C_Programmer.teach_C(jeff)

jeff.greet()
jeff.hard_work()
#>Hi, my name is Jeff A.
#>I'll have to remember malloc and free and pointer arithmetic and curly braces.

しかし、このソリューションには、私が考えていない注意事項が含まれていないとは思えません (三重否定で申し訳ありません)。特に、魔法の再割り当てが__class__適切ではないと感じるからです。たとえこれが機能したとしても、これを行うにはもっと Python 的な方法があるはずだという気がしてなりません。

ありますか?


編集: 皆さんの回答に感謝します。私が得た回答は次のとおりです:

  • 割り当てることでインスタンスを再分類するというアイデアは、__class__広く使用されている慣用句ではありませんが、ほとんどの回答 (執筆時点では 6 件中 4 件) では、有効なアプローチであると見なされています。1 つの回答 (ojrac による) では、「一見するとかなり奇妙」と述べられており、私も同意します (これが質問の理由です)。1 つの回答 (Jason Baker による、肯定的なコメントと投票が 2 件) だけが、これを実行することを積極的に思いとどまらせましたが、これは一般的な手法というよりも、使用例に基づいて実行しました。

  • 肯定的であろうとなかろうと、どの回答もこのメソッドの実際の技術的問題を見つけるものではありません。小さな例外は、古いスタイルのクラス (おそらく真実です) と C 拡張機能に注意するよう言及している jls です。新しいスタイルのクラスを認識する C 拡張機能は、Python 自体と同様にこのメソッドで問題なく動作するはずです (後者が真実であると仮定した場合)。ただし、同意しない場合は、引き続き回答してください。

これがどの程度 Python 的であるかという質問については、肯定的な回答がいくつかありましたが、具体的な理由は示されていませんでした。Zen ( import this) を見ると、この場合の最も重要なルールは「明示的であることは暗黙的であることよりも優れている」ということだと思います。ただし、このルールがこのように再分類することの賛成または反対を表明しているかどうかはわかりません。

  • {has,get,set}attr魔法を使用するのではなく、オブジェクトに明示的に変更を加えるため、を使用する方がより明示的に思えます。

  • 属性を黙って変更するのではなく、オブジェクトのユーザーに古いクラスの通常のオブジェクトを扱っている__class__ = newclassと信じ込ませるのではなく、「これはクラス 'newclass' のオブジェクトになったので、異なる動作が期待されます」と明示的に言うので、 を使用する方がより明示的であるように見えます。

まとめ: 技術的な観点からは、この方法は問題ないようです。Pythonicity の質問には「はい」という回答が寄せられていますが、答えは未だ出ていません。

私は Martin Geisler の回答を受け入れました。Mercurial プラグインの例は非常に強力なものだからです (また、私自身がまだ尋ねていなかった質問にも答えてくれたからです)。ただし、Pythonicity の質問に関して何か議論があれば、ぜひお聞きしたいです。これまでのところ、ありがとうございました。

PS 実際の使用例は、追加機能を拡張する必要があるUIデータコントロールオブジェクトです。実行時ただし、この質問は非常に一般的な内容です。

ベストアンサー1

このようなインスタンスの再分類は、気まぐれな(分散リビジョン管理システム)拡張機能(プラグイン)がローカルリポジトリを表すオブジェクトを変更したい場合。オブジェクトは呼び出されrepo、最初はlocalrepoインスタンスです。それは各拡張機能に順番に渡され、必要に応じて拡張機能はサブクラスである新しいクラスを定義しますrepo.__class__変化のクラスをrepoこの新しいサブクラスに変更します。

それはこのようなコード内:

def reposetup(ui, repo):
    # ...

    class bookmark_repo(repo.__class__): 
        def rollback(self):
            if os.path.exists(self.join('undo.bookmarks')):
                util.rename(self.join('undo.bookmarks'), self.join('bookmarks'))
            return super(bookmark_repo, self).rollback() 

        # ...

    repo.__class__ = bookmark_repo 

拡張機能 (ブックマーク拡張機能からコードを取得) は、 と呼ばれるモジュール レベルの関数を定義しますreposetup。Mercurial は拡張機能を初期化するときにこれを呼び出し、ui(ユーザー インターフェイス) およびrepo(リポジトリ) 引数を渡します。

この関数は、そのクラスのサブクラスを定義しますrepoないlocalrepo拡張機能は互いに拡張できる必要があるため、 をサブクラス化するだけで十分です。したがって、最初の拡張機能repo.__class__が に変更された場合foo_repo、次の拡張機能はのサブクラスではなく のrepo.__class__サブクラスに変更される必要があります。最後に、関数はコードで行ったように、インスタンスøのクラスを変更します。foo_repolocalrepo

このコードがこの言語機能の正当な使用方法を示していることを願っています。これが実際に使用されているのを見た唯一の場所だと思います。

おすすめ記事