3

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

  • 添加了“作为实例的类”方法的示例,以及对“作为类的实例”方法的解释;合并并引用了 Alex Martelli 的回答;

我想知道如何在 Python 中实现原型继承。这个问题似乎有两种不同的方法:作为实例的类和作为类的实例。

第二种方法似乎更灵活,因为它可以应用于各种类型的现有对象,而第一种方法对于典型用例可能更方便。

类作为实例

这里的想法是使用元类使实例化实际上是类,而不是对象。这种方法看起来像这样:

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 的回答中得到了例证,尽管他用于此方法的示例实现了复制原型,而不是允许后代继承对其原型的后续更改。

我的方法是做这样的事情:

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

这将以一种 javascript-y 的方式工作:对给定对象的更改不会影响其后代,但对父对象__class__(如 javascript prototype)的更改会。我认为这是因为type构造函数复制 obj.__dict__而不是在某种 mro-ish 方案中引用它。

我试图实现一个允许真正的原型继承的改进版本,其中对象将继承对父对象的更新。这个想法是将原型对象的__dict__属性分配给新创建的类的相同属性,即成为后代对象的类的属性。

但是,这并没有成功,因为我发现__dict__atype不能分配给;此限制也适用于从type. 我仍然很好奇是否有可能通过创建一个“实现类型协议”的对象来解决这个问题,就像对可迭代对象、序列等所做的那样,但实际上并不继承自type. 这可能会产生其他问题,例如亚历克斯在回答的第一部分中提到的授权方法所固有的问题。

代表团

Alex 还提出了第三种方法,即委托,其中对象的状态通过__getattr__魔术方法传播到后代对象。同样,请参阅 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)

...只要您还继承原型的状态,而不仅仅是行为。不幸的是,原型中的非覆盖方法不会感知当前对象中可能已被覆盖的任何状态。此外,在类而不是实例中查找的特殊方法(具有魔术名称以双下划线开头和结尾的方法)不能简单地以这种方式委托。因此,要解决这些问题可能需要大量工作。

如果您不关心“无缝继承”未来对原型的更改,但可以在“原型继承”时拍摄后者的快照,那么它更简单:

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. 遵循协作继承约定,以免破坏基于类的继承

一个限制是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)

委托-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 回答