6

単純なものを実装する次のコードを検討してくださいMixIn

class Story(object):
    def __init__(self, name, content):  
     self.name = name
     self.content = content    

class StoryHTMLMixin(object):
    def render(self):
     return ("<html><title>%s</title>"
         "<body>%s</body></html>"
         % (self.name, self.content))

def MixIn(TargetClass, MixInClass):
    if MixInClass not in TargetClass.__bases__:
     TargetClass.__bases__ += (MixInClass,)

if __name__ == "__main__":
    my_story = Story("My Life", "<p>Is good.</p>")
    # plug-in the MixIn here
    MixIn(Story, StoryHTMLMixin)
    # now I can render the story as HTML
    print my_story.render()

実行mainすると、次のエラーが発生します。

TypeError: Cannot create a consistent method resolution
order (MRO) for bases object, StoryHTMLMixin

問題は、との両方StoryStoryHTMLMixinから派生しobject菱形継承問題が発生することです。

解決策は、単に古いスタイルのクラスStoryHTMLMixinを作成することです。つまり、から継承を削除して、クラスの定義を次のように変更します。objectStoryHTMLMixin

class StoryHTMLMixin:
    def render(self):
     return ("<html><title>%s</title>"
         "<body>%s</body></html>"
         % (self.name, self.content))

実行すると、次の結果になりますmain

<html><title>My Life</title><body><p>Is good.</p></body></html>

古いスタイルのクラスを使用するのは好きではないので、私の質問は次のとおりです。

これはPythonでこの問題を処理する正しい方法ですか、それともより良い方法がありますか?

編集:

最新のPythonソースのクラスUserDictは、(私の例で示されているように)古いスタイルのクラスに頼るMixInを定義していることがわかります。

すべての人が推奨しているように、MixInを使用せずに、実現したい機能(つまり、実行時のメソッドのバインド)を再定義することに頼ることができます。ただし、要点はまだ残っています。これは、再実装に頼ったり、古いスタイルのクラスにフォールバックしたりせずにMROをいじることができない唯一のユースケースですか?

4

6 に答える 6

5

mroをハッキングするのではなく、ミックスインを直接使用してみませんか?

class Story(object):
    def __init__(self, name, content):  
     self.name = name
     self.content = content    

class StoryHTMLMixin(object):
    def render(self):
     return ("<html><title>%s</title>"
         "<body>%s</body></html>"
         % (self.name, self.content))

class StoryHTML(Story, StoryHTMLMixin):
    pass


print StoryHTML('asd','asdf').render() # works just fine

あなたが本当に、本当に、本当に本当にクラスに余分なメソッドを接着したいのなら、それは大きな問題ではありません。まあ、それが邪悪で悪い習慣であることを除いて。とにかく、いつでもクラスを変更できます。

# first, the story
x = Story('asd','asdf')

# changes a class object
def stick_methods_to_class( cls, *methods):
    for m in methods:
        setattr(cls, m.__name__, m)

# a bare method to glue on
def render(self):
 return ("<html><title>%s</title>"
     "<body>%s</body></html>"
     % (self.name, self.content))

# change the class object
stick_methods_to_class(Story, render)

print x.render()

しかし、結局のところ、疑問が残ります。なぜクラスは突然余分なメソッドを増やす必要があるのでしょうか。それはホラー映画が作られているものです;-)

于 2010-12-23T00:17:53.847 に答える
4

__bases__魔法を排除し、作成しているクラスを明示的に作成すると、何が起こっているのかを簡単に確認できます。

class StoryHTMLMixin(object):
    def render(self):
        return ("<html><title>%s</title>"
            "<body>%s</body></html>"
            % (self.name, self.content))

class Story(object, StoryHTMLMixin):
    def __init__(self, name, content):
        self.name = name
        self.content = content

それはあなたがしていることの最終結果です-またはそれが成功した場合はどうなるでしょう。同じエラーが発生します。

これは実際にはダイヤモンドの継承ではないことに注意してください。これには4つのクラスが含まれ、2つの基本クラスはそれぞれ共通の4番目のクラスを継承します。Pythonの多重継承はそれを扱います。

ここでは、クラスが3つしかないため、次のような継承が発生します。

StoryHTMLMixin <--- Story
            |   _____/
            |  |
            v  v
           object

Pythonはこれを解決する方法を知りません。

この回避策についてはわかりません。原則として、解決策は、ベースに追加すると同時にobjectベースから削除することですが、それはやや不透明な内部的な理由から許可されていません()。StoryStoryHTMLMixinTypeError: __bases__ assignment: 'StoryHTMLMixin' deallocator differs from 'object'

とにかく、このようなクラスを変更するための実用的な実際の使用法は見つかりませんでした。わかりにくく、紛らわしいようです。これら2つのクラスから派生したクラスが必要な場合は、通常どおりクラスを作成してください。

エド:

これは、クラスをインプレースで変更せずに、同じようなことを行うアプローチです。関数の引数から動的に派生して、新しいクラスを返す方法に注意してください。これははるかに明確です。たとえば、すでにインスタンス化されているオブジェクトを誤って変更することはできません。

class Story(object):
    def __init__(self, name, content):
        self.name = name
        self.content = content

class StoryHTMLMixin(object):
    def render(self):
        return ("<html><title>%s</title>"
            "<body>%s</body></html>"
            % (self.name, self.content))

def MixIn(TargetClass, MixInClass, name=None):
    if name is None:
        name = "mixed_%s_with_%s" % (TargetClass.__name__, MixInClass.__name__)

    class CombinedClass(TargetClass, MixInClass):
        pass

    CombinedClass.__name__ = name
    return CombinedClass

if __name__ == "__main__":
    MixedStory = MixIn(Story, StoryHTMLMixin, "MixedStory")
    my_story = MixedStory("My Life", "<p>Is good.</p>")
    print my_story.render()
于 2010-12-23T00:27:18.087 に答える
2

編集:私の悪い、バグは別の問題です(@GlennMaynardに感謝します)。ミックスインがから直接継承している限り、次のハックは引き続き機能しますobject

class Text(object): pass
class Story(Text):
    ....

ただし、ミックスインは問題に対する最も適切な解決策ではないと思います。提供されている他の両方のソリューション(クラスデコレータと通常のサブクラス)は、Storyクラスをレンダリング可能として明確にマークしますが、ソリューションはこの事実を隠します。つまりrender、他のソリューションでのメソッドの存在は明示的ですが、ソリューションでは非表示になっています。これは後で混乱を引き起こすと思います。特に、ミックスインの方法論に大きく依存するようになった場合はそうです。

個人的にはクラスデコレータが好きです。

于 2010-12-23T00:17:22.847 に答える
2

ただし、要点はまだ残っています。これは、再実装に頼ったり、古いスタイルのクラスにフォールバックしたりせずにMROをいじることができない唯一のユースケースですか?

他の人は、より良い解決策を提案しています-目的のクラスの明示的な構築など-しかし、編集した質問に答えるために、古いスタイルのクラスに頼ることなくミックスインを定義することが可能です:

class Base(object): pass

class Story(Base):
    def __init__(self, name, content):  
     self.name = name
     self.content = content    

class StoryHTMLMixin(Base):
    def render(self):
     return ("<html><title>%s</title>"
         "<body>%s</body></html>"
         % (self.name, self.content))

def MixIn(TargetClass, MixInClass):
    if MixInClass not in TargetClass.__bases__:
        TargetClass.__bases__ = (MixInClass,)+TargetClass.__bases__

if __name__ == "__main__":
    my_story = Story("My Life", "<p>Is good.</p>")
    # plug-in the MixIn here
    MixIn(Story, StoryHTMLMixin)
    # now I can render the story as HTML
    print my_story.render()

収量

<html><title>My Life</title><body><p>Is good.</p></body></html>
于 2010-12-23T02:17:06.943 に答える
1

以前の回答で述べられたすべてのこと(そして悪で悪い考えであること)を除けば、あなたの問題は次のとおりです。

  1. 基地は逆に注文する必要があります

    TargetClass.__bases__ = (MixInClass,) + TargetClass.__bases__

  2. http://bugs.python.org/issue672115-最も簡単な回避策は、オブジェクトではなく、ユーザー定義のクラスからすべてを継承することです。

    class MyBase(object): pass
    
    
    class Story(MyBase):
         ...
    
    
    class StoryHTMLMixin(MyBase):
         ...
    
于 2010-12-23T08:42:31.730 に答える
1

代わりに、デコレータを使用して機能を追加してみてください。

def render(obj):
  return ("<html><title>%s</title>"
    "<body>%s</body></html>"
    % (obj.name, obj.content))

def renderable(cls):
  cls.render = render
  return cls

@renderable
class Story(object):
  ...
于 2010-12-23T00:05:31.870 に答える