58

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

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

次の解決策はうまくいくようです。

# 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 人) が有効なアプローチであると考えています。ある回答者 (ojrac による) は、「一見するとかなり奇妙だ」と述べていますが、これには同意します (それが質問をする理由でした)。1 つの回答 (Jason Baker によるもの、2 つの肯定的なコメントと投票) だけが、これを行うことを積極的に思いとどまらせましたが、一般的な手法よりもユースケースの例に基づいてそうしました。

  • 肯定的であろうとなかろうと、この方法で実際の技術的な問題を見つける答えはありません。小さな例外は、古いスタイルのクラスに注意するように言及している jls です。新しいスタイルのクラスを意識した C 拡張機能は、Python 自体と同じようにこのメソッドで問題ないと思います (後者が正しいと仮定します)。

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

  • {has,get,set}attr魔法を使用する代わりにオブジェクトに明示的に変更を加えているため、使用はより明確に見えます。

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

要約: 技術的な観点からは、この方法は問題ないようです。pythonicity の質問は、「はい」に偏っており、未回答のままです。

Mercurial プラグインの例は非常に強力なものであるため (また、私がまだ自分自身に尋ねたことのない質問にも回答しているため)、Martin Geisler の回答を受け入れました。ただし、pythonicity の質問について何らかの議論がある場合は、引き続き聞きたいと思います。今までありがとう。

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

4

8 に答える 8

19

このようなインスタンスの再クラス化は、拡張機能 (プラグイン) がローカル リポジトリを表すオブジェクトを変更したい場合に、 Mercurial (分散型リビジョン コントロール システム) で行われます。オブジェクトが呼び出され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変更される必要があります。最後に、関数は、コードで行ったように、instanceø のクラスを変更します。repo.__class__foo_repolocalrepo

このコードが、この言語機能の正当な使用法を示してくれることを願っています。野生で使われているのを見たのはここだけだと思います。

于 2009-06-13T18:47:45.290 に答える
11

この場合、継承の使用が最適かどうかはわかりません(少なくとも「再分類」に関しては)。あなたは正しい軌道に乗っているようですが、これには構成または集約が最適であるように思えます。これが私が考えていることの例です(テストされていない、疑似的なコードで):

from copy import copy

# As long as none of these attributes are defined in the base class,
# this should be safe
class SkilledProgrammer(Programmer):
    def __init__(self, *skillsets):
        super(SkilledProgrammer, self).__init__()
        self.skillsets = set(skillsets)

def teach(programmer, other_programmer):
    """If other_programmer has skillsets, append this programmer's
       skillsets.  Otherwise, create a new skillset that is a copy
       of this programmer's"""
    if hasattr(other_programmer, skillsets) and other_programmer.skillsets:
        other_programmer.skillsets.union(programmer.skillsets)
    else:
        other_programmer.skillsets = copy(programmer.skillsets)
def has_skill(programmer, skill):
    for skillset in programmer.skillsets:
        if skill in skillset.skills
            return True
    return False
def has_skillset(programmer, skillset):
    return skillset in programmer.skillsets


class SkillSet(object):
    def __init__(self, *skills):
        self.skills = set(skills)

C = SkillSet("malloc","free","pointer arithmetic","curly braces")
SQL = SkillSet("SELECT", "INSERT", "DELETE", "UPDATE")

Bob = SkilledProgrammer(C)
Jill = Programmer()

teach(Bob, Jill)          #teaches Jill C
has_skill(Jill, "malloc") #should return True
has_skillset(Jill, SQL)   #should return False

セット任意の引数リストに慣れていない場合は、この例を理解するためにそれらについてさらに読む必要があるかもしれません。

于 2009-06-13T15:31:03.763 に答える
3

これで問題ありません。私はこのイディオムを何度も使ってきました。ただし、この考え方は、古いスタイルのクラスやさまざまな C 拡張機能ではうまく機能しないことに注意してください。通常、これは問題になりませんが、外部ライブラリを使用しているため、古いスタイルのクラスや C 拡張を扱っていないことを確認する必要があります。

于 2009-06-13T18:13:46.760 に答える
2

「状態パターンにより、オブジェクトは内部状態が変化したときにその動作を変更できます。オブジェクトはそのクラスを変更したように見えます。」●ヘッドファーストデザインパターン。非常によく似た何かが、Gamma et.al を書いています。彼らのデザインパターンの本で。(別の場所に持っているので、引用はありません)。それがこのデザインパターンの要点だと思います。しかし、実行時にオブジェクトのクラスを変更できる場合、ほとんどの場合、パターンは必要ありません (状態パターンがクラス変更をシミュレートする以上のことを行う場合があります)。

また、実行時にクラスを変更しても常に機能するとは限りません。

class A(object):
    def __init__(self, val):
        self.val = val
    def get_val(self):
        return self.val

class B(A):
    def __init__(self, val1, val2):
        A.__init__(self, val1)
        self.val2 = val2
    def get_val(self):
        return self.val + self.val2


a = A(3)
b = B(4, 6)

print a.get_val()
print b.get_val()

a.__class__ = B

print a.get_val() # oops!

それとは別に、Pythonic の実行時にクラスを変更することを検討し、時々使用しています。

于 2010-07-19T20:31:20.480 に答える
1

このテクニックは私にはかなりPythonicに思えます。構成も良い選択ですが、に割り当てること__class__は完全に有効です(少し異なる方法でそれを使用するレシピについては、ここを参照してください)。

于 2009-06-13T15:53:16.957 に答える
1

へへへ、楽しい例え。

一見すると、「再分類」はかなり奇妙です。「コピーコンストラクタ」アプローチはどうですか?これは、リフレクションのようなhasattrgetattrおよびで行うことができますsetattr。このコードは、オブジェクトが存在しない限り、あるオブジェクトから別のオブジェクトにすべてをコピーします。メソッドをコピーしたくない場合は、それらを除外できます。コメントを参照してくださいif

class Foo(object):
    def __init__(self):
        self.cow = 2
        self.moose = 6

class Bar(object):
    def __init__(self):
        self.cat = 2
        self.cow = 11

    def from_foo(foo):
        bar = Bar()
        attributes = dir(foo)
        for attr in attributes:
            if (hasattr(bar, attr)):
                break
            value = getattr(foo, attr)
            # if hasattr(value, '__call__'):
            #     break # skip callables (i.e. functions)
            setattr(bar, attr, value)

        return bar

このすべてのリフレクションはきれいではありませんが、クールなことを実現するために醜いリフレクション マシンが必要になる場合があります。;)

于 2009-06-13T14:43:34.173 に答える
0

ojracの回答では、ループbreakから抜け出し、forそれ以上属性をテストしません。if-statement を使用して各属性を 1 つずつ処理する方法を決定し、すべての属性に対して -loop を実行する方が理にかなっていると思いforます。それ以外の場合は、ojrac の回答が気に入って__class__います。(私は Python の初心者で、覚えている限りでは、これが StackOverFlow への最初の投稿です。素晴らしい情報をありがとう!!)

だから私はそれを実装しようとしました。dir() がすべての属性をリストしていないことに気付きました。 http://jedidjah.ch/code/2013/9/8/wrong_dir_function/ そこで、' class '、' doc '、' module ' および ' init ' を追加するもののリストに追加しました (それらがまだ存在しない場合) 、(おそらくそれらはすべてすでに存在しますが)、さらにディレクトリミスがあるかどうか疑問に思いました。また、変だと言った後、 「クラス」に(潜在的に)割り当てていることに気付きました。

于 2014-05-15T17:10:40.237 に答える
-2

それがうまくいくなら、これはまったく問題ないと思います。

于 2009-06-13T15:21:50.307 に答える