web-dev-qa-db-ja.com

トリプル継承はメタクラスの競合を引き起こします...時々

メタクラスとは何の関係も望まないのに、メタクラスの地獄に出くわしたようです。

PySideを使用してQt4でアプリを作成しています。 QtDesignerファイルから生成されるUI定義からイベント駆動型の部分を分離したいと思います。したがって、私は「コントローラー」クラスを作成しますが、私の生活を楽にするために、とにかくそれらを多重継承します。例:

class BaseController(QObject):
    def setupEvents(self, parent):
        self.window = parent

class MainController(BaseController):
    pass

class MainWindow(QMainWindow, Ui_MainWindow, MainController):
    def __init__(self, parent=None):
        super(MainWindow, self).__init__(parent)

        self.setupUi(self)
        self.setupEvents(self)

これは期待どおりに機能します。また、(QDialogUi_DialogBaseController)。しかし、BaseControllerをサブクラス化し、そのサブクラスから継承しようとすると(BaseControllerの代わりに)、エラーが発生します。

TypeError:メタクラスベースの呼び出し時のエラーメタクラスの競合:派生クラスのメタクラスは、そのすべてのベースのメタクラスの(非厳密な)サブクラスである必要があります

明確化:QMainWindowQDialogはどちらもQObjectから継承します。 BaseControllerは、Qtイベントシステムの特性により、それから継承する必要もあります。 Ui_クラスは単純なPythonオブジェクトクラスからのみ継承します。解決策を探しましたが、すべてメタクラスを意図的に使用する場合が含まれているため、ひどく間違ったことをしているに違いありません。

編集:グラフを追加することで、私の説明がより明確になる可能性があります。

実例:

QObject
|      \___________________
|            object        |
QMainWindow     |          BaseController
|      /---Ui_MainWindow   |
|      |                   MainController
MainWindow-----------------/

別の実用的な例:

QObject
|      \___________________
|            object        |
QDialog         |          BaseController
|      /---Ui_OtherWindow  |
|      |                   |
OtherWindow----------------/

機能しない例:

QObject
|      \___________________
|            object        |
QDialog         |          BaseController
|      /---Ui_OtherWindow  |
|      |                   OtherController
OtherWindow----------------/
39
Red

エラーメッセージは、階層のどこかに2つの競合するメタクラスがあることを示しています。競合がどこにあるかを把握するには、各クラスとQTクラスを調べる必要があります。

同じ状況を設定する簡単なサンプルコードを次に示します。

class MetaA(type):
    pass
class MetaB(type):
    pass
class A:
    __metaclass__ = MetaA
class B:
    __metaclass__ = MetaB

pythonはどのメタクラスを使用するかわからないため、これらのクラスの両方を直接サブクラス化することはできません。

>>> class Broken(A, B): pass
... 
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: Error when calling the metaclass bases
  metaclass conflict: the metaclass of a derived class must be a (non-strict)
  subclass of the metaclasses of all its bases

エラーが私たちに伝えようとしているのは、基本クラスのすべてのメタクラスのサブクラスである3番目のメタクラスを導入することによって、2つのメタクラス間の競合を解決する必要があるということです。

エラーメッセージ自体よりも明確かどうかはわかりませんが、基本的には次のように修正します。

class MetaAB(MetaA, MetaB):
    pass

class Fixed(A, B):
    __metaclass__ = MetaAB

このコードは、正しくコンパイルおよび実行されるようになりました。もちろん、実際の状況では、競合を解決するメタクラスは、採用する親メタクラスの動作を決定する必要があります。これは、アプリケーションの要件から自分で把握する必要があります。

継承されたクラスは、2つのメタクラスのoneのみを取得することに注意してください。__init__メソッドは、すべての作業を行うことがあるので、多くの場合、追加する必要があります。 __init__は、両方を呼び出すのに役立つ何らかの方法で両方を呼び出します。

34
tangentstorm

次のようなものを使用します。

class CooperativeMeta(type):
    def __new__(cls, name, bases, members):
        #collect up the metaclasses
        metas = [type(base) for base in bases]

        # Prune repeated or conflicting entries
        metas = [meta for index, meta in enumerate(metas)
            if not [later for later in metas[index+1:]
                if issubclass(later, meta)]]

        # whip up the actual combined meta class derive off all of these
        meta = type(name, Tuple(metas), dict(combined_metas = metas))

        # make the actual object
        return meta(name, bases, members)

    def __init__(self, name, bases, members):
        for meta in self.combined_metas:
            meta.__init__(self, name, bases, members)

優れた最新のメタクラス実装の衛生状態(メタクラスサブクラスtype、および__init__で実行できることはすべてそこで実行される)を想定すると、これにより多くのメタクラスがうまくいくことができます。

__new__で実際にそして必然的にほとんどの作業を行うメタクラスは、とにかく組み合わせるのが難しいでしょう。そのクラスが多重継承の最初の要素であることを確認することで、ここにそれらの1つを忍び込ませることができます。

これを使用するには、次のように宣言します。

__metaclass__ = CooperativeMeta

異なるメタクラスが一緒になるクラスの場合。

この場合、例えば:

class A:
    __metaclass__ = MetaA
class B:
    __metaclass__ = MetaB
class Fixed(A, B):
    __metaclass__ = CooperativeMeta

これは、コンパイラをシャットダウンするためにそれらを一緒に継承するよりも、さまざまなMetaAおよびMetaBに対して全面的に正しく機能する可能性が何倍もあります。

うまくいけば、コメントはコードを説明しています。トリッキーな行は1つだけです。それは、さまざまな場所から継承された任意の__metaclass__への冗長な呼び出しを削除し、明示的なメタクラスのないクラスが他のクラスとうまく連携できるようにすることです。行き過ぎだと思われる場合は、それを省略して、コードで基本クラスを慎重に注文するだけです。

これにより、ソリューションは3行になり、かなり明確になります。

8
Jon Obermark