3

updated 2010-06-15T09:45:00Z:

  • 「インスタンスとしてのクラス」アプローチの例と、「クラスとしてのインスタンス」アプローチの説明を追加しました。Alex Martelliの回答を組み込み、参照しました。

Python でプロトタイプの継承を実装する方法を考えています。この問題には、インスタンスとしてのクラスとクラスとしてのインスタンスという 2 つの異なるアプローチがあるようです。

2 番目の方法は、さまざまなタイプの既存のオブジェクトに適用できるという点で、より柔軟に見えますが、最初の方法は、典型的なユース ケースにより便利である可能性があります。

インスタンスとしてのクラス

ここでの考え方は、メタクラスを使用してインスタンス化を実際にオブジェクトではなくクラスにすることです。このアプローチは次のようになります。

class ClassAsInstance(type):
    """ ClassAsInstance(type)\n
        >>> c = ClassAsInstance()
        >>> c.prop = 6

        It's sort of annoying to have to make everything a class method.
        >>> c.jef = classmethod(lambda self: self.prop)
        >>> c.jef()
        6
        >>> cc = c()
        >>> cc.jef()
        6

        But it works.
        >>> c.prop = 10
        >>> cc.jef()
        10
        >>> c.jef = classmethod(lambda self: self.prop * 2)
        >>> cc.jef()
        20
    """
    def __new__(self):
        return type(self.__name__ + " descendant", (self, ), {})

このアプローチで複雑なことを実際にテストしたことはないので、制限があるかもしれません。

クラスとしてのインスタンス

このアプローチでは、typeコンストラクターを使用してオブジェクトからクラスを作成するという考え方です。これはAlex Martelli の answerに例示されていますが、このアプローチで彼が使用する例では、子孫がプロトタイプへの後の変更を継承できるようにするのではなく、コピー プロトタイプを実装しています。

私のアプローチは、次のようなことをすることでした:

def createDescendant(obj):
    return type(obj.__class__.__name__ + " descendant", (obj.__class__, ), obj.__dict__)()

これは、一種の javascript-y の方法で機能します。特定のオブジェクトへの変更は、その子孫には影響しませんが、親オブジェクト__class__( javascript などprototype) の変更には影響しません。これは、typeコンストラクターがある種の mro-ish スキームで参照するのではなく、コピーするためだと思います。 obj.__dict__

オブジェクトが親オブジェクトへの更新を継承する、真のプロトタイプ継承を可能にする改良版の実装を試みました。アイデアは、プロトタイプ オブジェクトの__dict__プロパティを、新しく作成されたクラス (子孫オブジェクトのクラスになるクラス) の同じプロパティに割り当てることでした。

ただし、これはうまくいきませんでし__dict__type。この制限は、 から派生したクラスにも適用されますtype。イテラブルやシーケンスなどで行われるように、「型プロトコルを実装する」オブジェクトを作成することでこの問題を回避できるかどうか、まだ興味がありますが、実際には から継承しませんtype。これにより、アレックスが回答の最初の部分で言及した委任アプローチに固有の問題など、他の問題が発生する可能性があります。

委任

__getattr__アレックスは、オブジェクトの状態が魔法のメソッドを介して子孫オブジェクトに伝播される、委任の 3 番目のアプローチも提案しています。繰り返しますが、例については Alex の回答と、このアプローチの制限の詳細を参照してください。

これらのアプローチの実用性に関するさらなる洞察と、代替提案がここに求められます。

4

2 に答える 2

4

プロトタイプ オブジェクトの将来の変更をすべての「子孫」に透過的に反映する必要がある場合は、明示的な委任に頼る必要があります。通常のメソッドでは、次のように を介して簡単に実行できます__getattr__

class FromPrototype(object):
  def __init__(self, proto):
    self._proto = proto
  def __getattr__(self, name):
    return getattr(self._proto, name)

...振る舞いだけでなく、プロトタイプの状態も継承している限り。残念ながら、プロトタイプのオーバーライドされていないメソッドは、現在のオブジェクトでオーバーライドされた可能性のある状態を認識しません。さらに、インスタンスではなくクラスで検索される特別なメソッド (2 つのアンダースコアで始まり、2 つのアンダースコアで終わる魔法の名前を持つもの) は、この方法で単純に委譲することはできません。したがって、これらの問題を修正するには、多くの作業が必要になる場合があります。

プロトタイプへの将来の変更を「シームレスに継承」することに関心がなく、「プロトタイプの継承」時に後者のスナップショットを取得しても問題ない場合は、次のように簡単です。

import copy

def proto_inherit(proto):
  obj = copy.deepcopy(proto)
  obj.__class__ = type(obj.__class__.__name__, (obj.__class__,), {})
  return obj

この方法で構築された各オブジェクトには独自のクラスがあるため、proto_inherit他のオブジェクトに影響を与えることなく (からオブジェクトを取得した後) クラスに特別なメソッドを設定できます (通常のメソッドの場合は、クラスまたはインスタンスのいずれかに設定できますが、常にクラスを使用すると、より規則的で一貫したものになります)。

于 2010-06-15T04:41:22.633 に答える
0

これは、委任アプローチのより堅牢なバージョンです。主な改善点は、

  1. 継承されたメンバーがメソッドの場合、元の呼び出し元のオブジェクトにバインドされている同じ基になる関数を持つメソッドが返されます。これは、@AlexMartelli が彼の回答で提起した問題に対処しています。

    ...動作だけでなく、プロトタイプの状態も継承している限り。残念ながら、プロトタイプのオーバーライドされていないメソッドは、現在のオブジェクトでオーバーライドされた可能性のある状態を認識しません。

  2. クラスベースの継承を壊さないように、共同継承の規則に従っています

1 つの制限は、Proto初期化が正しく機能するために、クラスがメソッド解決順序で最初に来なければならないことです。

デリゲート.py

import types
import inspect

class Proto(object):
  def __new__(self, proto, *args, **kw):
    return super(Proto, self).__new__(self, *args, **kw)
  def __init__(self, proto, *args, **kw):
    self.proto = proto
    super(Proto, self).__init__(*args, **kw)
  def __getattr__(self, name):
    try:
      attr = getattr(self.proto, name)
      if (inspect.ismethod(attr) and attr.__self__ != None):
        attr = types.MethodType(attr.__func__, self)
      return attr
    except AttributeError:
      return super(Proto, self).__getattr__(name)

delegate-demo.py

以下の呼び出しb.getY()はアレックスのポイントを示しておりFromPrototype、彼の回答のクラスが代わりに使用された場合は失敗しますProto

from delegate import Proto

class A(Proto):
  x = "This is X"
  def getY(self):
    return self._y

class B(Proto):
  _y = "This is Y"

class C(object):
  def __getattr__(self, name):
    return "So you want "+name

class D(B,C):
  pass

if __name__ == "__main__":
  a = A(None)
  b = B(a)
  print b.x
  print b.getY()
  d = D(a)
  print d.x
  print d.getY()
  print d.z
于 2015-12-12T22:07:49.763 に答える