实现原型还是实例化类对象
更新于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的方式工作:对某个对象的更改不会影响它的子类,但对父对象的__class__
(就像JavaScript的prototype
)的更改会影响。听说这是因为type
构造函数是复制了obj.__dict__
,而不是以某种mro的方式引用它。
我尝试实现一个改进版本,允许真正的原型继承,让对象能够继承父对象的更新。我的想法是将原型对象的__dict__
属性赋值给新创建类的同一个属性,这个属性成为子对象的类。
然而,这并没有成功,因为我发现type
的__dict__
是不能被赋值的;这个限制同样适用于从type
派生的类。我仍然好奇是否可以通过创建一个“实现类型协议”的对象来绕过这个问题,就像可迭代对象、序列等那样,但实际上并不继承自type
。这可能会引发其他问题,比如Alex在他回答的第一部分提到的委托方法所固有的问题。
委托
Alex还建议了一种第三种方法,即委托,通过__getattr__
魔法方法将对象的状态传播给子对象。再次建议查看Alex的回答以获取示例,以及关于这种方法限制的详细信息。
希望能进一步了解这些方法的实用性,以及其他替代建议。
2 个回答
这是一个更强大的委托方法版本。
主要改进有:
当继承的成员是一个方法时,会返回一个与原始调用对象绑定的、具有相同基本功能的方法。这解决了@AlexMartelli在他的回答中提到的问题:
...只要你也继承了原型的状态,而不仅仅是行为。不幸的是,来自原型的未重写方法将无法感知当前对象中可能被重写的状态。
遵循合作继承的约定,以避免破坏基于类的继承。
一个限制是,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
类而不是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
如果你希望将来对原型对象的修改能够自动反映到所有“子类”中,那么你需要使用明确的委托方式。对于普通的方法,这可以通过 __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
获取对象后),而不会影响到其他对象(对于普通方法,你可以在类或实例上设置,但始终使用类会更规范和一致)。