实现原型或实例化类对象

发布于 2024-09-05 20:36:04 字数 2188 浏览 3 评论 0原文

更新于 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__ 属性分配给新创建的类的相同属性,该类将成为后代对象的类。

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

委托

Alex 还建议了第三种方法,即委托,其中对象的状态通过 __getattr__ 魔术方法传播到后代对象。再次,请参阅 Alex 的答案作为示例,以及有关此方法的局限性的详细信息。

特此要求对这些方法的实用性以及替代建议提供进一步的见解。

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

  • added an example for the "classes as instances" approach, and an explanation of the "instances as classes" approach; incorporated and referenced Alex Martelli's answer;

I'm wondering how to implement prototypal inheritance in Python. There would seem to be two different approaches to this problem: classes as instances, and instances as classes.

The second method would seem to be more flexible, in that it could be applied to existing objects of varied types, while the first would likely be more convenient for typical use cases.

classes as instances

The idea here is to use a metaclass to cause instantiations to actually be classes, rather than objects. This approach looks something like this:

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, ), {})

I haven't really tested any complicated stuff with this approach, so it may have limitations.

instances as classes

With this approach, the idea is to use the type constructor to create classes from objects. This is exemplified in Alex Martelli's answer, although the example he uses for this approach implements copy prototyping rather than allowing the descendants to inherit later changes to their prototypes.

My approach was to do something like this:

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

which will work in sort of a javascript-y kind of way: changes to a given object will not influence its descendants, but changes to the parent object's __class__ (like a javascript prototype) will. I gather that this is because the type constructor copies obj.__dict__ rather than referencing it in some sort of mro-ish scheme.

I attempted to implement an improved version that would allow true prototypal inheritance, wherein objects would inherit updates to the parent objects. The idea was to assign the prototype object's __dict__ property to the same property of the newly-created class, the one that becomes the class of the descendant object.

However, this didn't work out, as I discovered that the __dict__ of a type cannot be assigned to; this limitation also applies to classes derived from type. I'm still curious if it's possible to get around this problem by creating an object that "implements the type protocol", as is done with iterables, sequences, etc., but does not actually inherit from type. This might create other problems, such as those inherent to the delegational approach that Alex mentions in the first part of his answer.

delegation

Alex also suggests a third approach, that of delegation, wherein the state of an object is propagated to descendant objects via the __getattr__ magic method. Again, see Alex's answer for an example, as well as details on the limitations of this approach.

Further insights on the practicality of these approaches, as well as alternative suggestions, are hereby requested.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

迷荒 2024-09-12 20:36:04

如果您需要原型对象的未来更改透明地反映在所有“后代”中,那么您必须求助于显式委托。在普通方法中,这可以通过 __getattr__ 轻松完成,例如,派生自:

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

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

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

import copy

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

这种方式构建的每个对象都有自己的类,因此您可以设置类上的特殊方法(从 proto_inherit 获取对象后),而不影响任何其他对象(对于普通方法,您可以在类或实例上设置它们,但始终使用该类将是更加规律和一致)。

If you need future alterations of the prototype object to be transparently reflected in all "descendants", then you must have recourse to explicit delegation. On normal methods, that's easily done via __getattr__, e.g., deriving from:

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

...as long as you're also inheriting the state of the prototype, not just the behavior. Unfortunately, non-overridden methods from the prototype will not perceive any state that may have been overridden in the current object. In addition, special methods (ones with magic names starting and ending in double underscore), which are looked up in the class rather than the instance, cannot be simply delegated this way. So, to fix these issues may require a lot of work.

If you're not concerned with "seamlessly inheriting" future alterations to the prototype, but are fine with taking a snapshot of the latter at "prototype inheritance" time, it's simpler:

import copy

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

each object built this way has its own class so you can set special methods on the class (after getting the object from proto_inherit) without affecting any other object (for normal methods, you can set them either on the class or on the instance, though always using the class would be more regular and consistent).

此生挚爱伱 2024-09-12 20:36:04

这是委托方法的更强大的版本。
主要改进是

  1. 当继承的成员是方法时,则返回具有相同底层功能但绑定到原始调用对象的方法。
    这解决了 @AlexMartelli 在他的回答中提出的问题:

    <块引用>

    ...只要您还继承了原型的状态,而不是
    只是行为。不幸的是,来自的非重写方法
    原型不会感知任何可能已被覆盖的状态
    当前对象。

  2. 遵循协作继承约定,以免破坏基于类的继承

一个限制是 Proto 类必须排在第一位初始化正确工作的方法解析顺序

delegate.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() 的调用说明了 Alex 的观点,如果 FromPrototype他的答案中使用了 class 而不是 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

This is a more robust version of the delegation approach.
The main improvements are

  1. when the inherited member is a method, then a method with the same underlying function, but bound to the original calling object is returned.
    This addresse the problem that @AlexMartelli raises in his answer:

    ...as long as you're also inheriting the state of the prototype, not
    just the behavior. Unfortunately, non-overridden methods from the
    prototype will not perceive any state that may have been overridden in
    the current object.

  2. Cooperative inheritance conventions are followed so as not to break class based inheritance

One restriction is that the Proto class must come first in the method resolution order for initialization to work correctly

delegate.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

The call to b.getY() below illustrates Alex's point and would fail if the FromPrototype class in his answer was used instead of 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
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文