根据文档,super(cls, obj)
返回
将方法调用委托给父级或兄弟级的代理对象 类型cls
我理解为什么super()
提供了这个功能,但我需要稍微不同的东西:我需要创建一个代理对象,将方法调用(和属性查找)委托给类cls
本身;和super
一样,如果cls
没有实现方法/属性,我的代理应该继续查看MRO订单( new 而不是原始版本) 类)。我能写出什么功能可以达到这个目的吗?
示例:
class X:
def act():
#...
class Y:
def act():
#...
class A(X, Y):
def act():
#...
class B(X, Y):
def act():
#...
class C(A, B):
def act():
#...
c = C()
b = some_magic_function(B, c)
# `b` needs to delegate calls to `act` to B, and look up attribute `s` in B
# I will pass `b` somewhere else, and have no control over it
当然,我可以b = super(A, c)
,但这依赖于知道确切的类层次结构和 {M}中B
跟随A
的事实。如果这两个假设中的任何一个在将来发生变化,它将默默地破裂。 (注意super
没有做出任何这样的假设!)
如果我只需要致电b.act()
,我可以使用B.act(c)
。但是我将b
传递给其他人,并且不知道他们会用它做什么。我需要确保它不会背叛我,并在某些时候开始像class C
的实例一样。
一个单独的问题,super()
(在Python 3.2中)的文档只讨论了它的方法委托,并没有说明代理的属性查找也以相同的方式执行。这是意外遗漏吗?
修改
更新的Delegate方法也适用于以下示例:
class A:
def f(self):
print('A.f')
def h(self):
print('A.h')
self.f()
class B(A):
def g(self):
self.f()
print('B.g')
def f(self):
print('B.f')
def t(self):
super().h()
a_true = A()
# instance of A ends up executing A.f
a_true.h()
b = B()
a_proxy = Delegate(A, b)
# *unlike* super(), the updated `Delegate` implementation would call A.f, not B.f
a_proxy.h()
请注意,更新后的class Delegate
比super()
更接近我想要的内容,原因有两个:
super()
仅代理第一个电话;后续调用将正常进行,因为到那时使用对象,而不是其代理。
super()
不允许属性访问。
因此,我的问题在Python中有一个(几乎)完美的答案。
事实证明,在更高的层面上,我试图做一些我不应该做的事情(see my comments here)。
答案 0 :(得分:3)
本课程应涵盖最常见的案例:
class Delegate:
def __init__(self, cls, obj):
self._delegate_cls = cls
self._delegate_obj = obj
def __getattr__(self, name):
x = getattr(self._delegate_cls, name)
if hasattr(x, "__get__"):
return x.__get__(self._delegate_obj)
return x
像这样使用:
b = Delegate(B, c)
(使用示例代码中的名称。)
限制:
您无法从通过此代理在构造函数中传递的类中检索某些特殊属性,如__class__
等。 (此重述也适用于super
。)
如果您要检索的属性是某种类型的描述符,则可能会出现这种情况。
修改:如果您希望问题更新中的代码能够按预期运行,您可以使用以下代码:
class Delegate:
def __init__(self, cls):
self._delegate_cls = cls
def __getattr__(self, name):
x = getattr(self._delegate_cls, name)
if hasattr(x, "__get__"):
return x.__get__(self)
return x
这会将代理对象作为self
参数传递给任何被调用的方法,并且根本不需要原始对象,因此我将其从构造函数中删除。
如果您还希望可以访问实例属性,则可以使用此版本:
class Delegate:
def __init__(self, cls, obj):
self._delegate_cls = cls
self._delegate_obj = obj
def __getattr__(self, name):
if name in vars(self._delegate_obj):
return getattr(self._delegate_obj, name)
x = getattr(self._delegate_cls, name)
if hasattr(x, "__get__"):
return x.__get__(self)
return x
答案 1 :(得分:2)
一个单独的问题,super()的文档(在Python 3.2中) 只讨论其方法授权,并没有澄清 代理的属性查找也以相同的方式执行。是吗 意外遗漏?
不,这不是偶然的。 super()
对属性查找没有任何作用。原因是实例上的属性与特定类没有关联,它们就在那里。请考虑以下事项:
class A:
def __init__(self):
self.foo = 'foo set from A'
class B(A):
def __init__(self):
super().__init__()
self.bar = 'bar set from B'
class C(B):
def method(self):
self.baz = 'baz set from C'
class D(C):
def __init__(self):
super().__init__()
self.foo = 'foo set from D'
self.baz = 'baz set from D'
instance = D()
instance.method()
instance.bar = 'not set from a class at all'
哪个班级“拥有”foo
,bar
和baz
?
如果我想将instance
视为C的实例,那么在调用baz
之前它是否应该具有method
属性?之后怎么样?
如果我将instance
视为A的实例,foo
应具有什么价值? bar
应该是不可见的,因为它只是在B中添加,或者是可见的,因为它被设置为类外的值?
所有这些问题在Python中都是无稽之谈。没有办法设计一个具有Python语义的系统,可以为它们提供合理的答案。 __init__
在向类的实例添加属性方面甚至没有特别之处;它只是一个非常普通的方法,恰好被称为实例创建协议的一部分。任何方法(或者实际上来自另一个类的代码,或者根本不是来自任何类的代码)都可以在它引用的任何实例上创建属性。
事实上,instance
的所有属性都存储在同一个地方:
>>> instance.__dict__
{'baz': 'baz set from C', 'foo': 'foo set from D', 'bar': 'not set from a class at all'}
没有办法告诉他们哪一个最初由哪个类设置,或者最后由哪个类设置,或者你想要什么样的所有权。正如您对C ++所期望的那样,当然没有办法让“A.foo
被D.foo
遮蔽”。它们是相同的属性,一个类(或来自其他地方)对它的任何写入都会破坏另一个类留在其中的值。
这样做的结果是super()
不执行属性查找,就像查找方法一样;它不能,也不能写任何代码。
事实上,通过运行一些实验,super
和Sven Delegate
实际上都不支持直接属性检索!
class A:
def __init__(self):
self.spoon = 1
self.fork = 2
def foo(self):
print('A.foo')
class B(A):
def foo(self):
print('B.foo')
b = B()
d = Delegate(A, b)
s = super(B, b)
然后两者都按预期方法工作:
>>> d.foo()
A.foo
>>> s.foo()
A.foo
可是:
>>> d.fork
Traceback (most recent call last):
File "<pyshell#43>", line 1, in <module>
d.fork
File "/tmp/foo.py", line 6, in __getattr__
x = getattr(self._delegate_cls, name)
AttributeError: type object 'A' has no attribute 'fork'
>>> s.spoon
Traceback (most recent call last):
File "<pyshell#45>", line 1, in <module>
s.spoon
AttributeError: 'super' object has no attribute 'spoon'
所以他们只是真正用于调用某些方法,而不是传递给任意第三方代码假装是你想要委派的类的实例。
不幸的是,在存在多重继承的情况下,它们的行为方式不同。给出:
class Delegate:
def __init__(self, cls, obj):
self._delegate_cls = cls
self._delegate_obj = obj
def __getattr__(self, name):
x = getattr(self._delegate_cls, name)
if hasattr(x, "__get__"):
return x.__get__(self._delegate_obj)
return x
class A:
def foo(self):
print('A.foo')
class B:
pass
class C(B, A):
def foo(self):
print('C.foo')
c = C()
d = Delegate(B, c)
s = super(C, c)
然后:
>>> d.foo()
Traceback (most recent call last):
File "<pyshell#50>", line 1, in <module>
d.foo()
File "/tmp/foo.py", line 6, in __getattr__
x = getattr(self._delegate_cls, name)
AttributeError: type object 'B' has no attribute 'foo'
>>> s.foo()
A.foo
因为Delegate
忽略了任何类_delegate_obj
的完整MRO,只是使用_delegate_cls
的MRO。虽然super
做了你在问题中提出的问题,但行为似乎很奇怪:它没有包装C的实例来假装它是B的实例,因为B的直接实例没有foo
定义
这是我的尝试:
class MROSkipper:
def __init__(self, cls, obj):
self.__cls = cls
self.__obj = obj
def __getattr__(self, name):
mro = self.__obj.__class__.__mro__
i = mro.index(self.__cls)
if i == 0:
# It's at the front anyway, just behave as getattr
return getattr(self.__obj, name)
else:
# Check __dict__ not getattr, otherwise we'd find methods
# on classes we're trying to skip
try:
return self.__obj.__dict__[name]
except KeyError:
return getattr(super(mro[i - 1], self.__obj), name)
我依靠类的__mro__
属性来正确地找出从哪里开始,然后我只使用super
。如果返回一步使用__dict__
的奇怪之处,那么你可以自己检查类super
来检查类{{1}}的MRO链。
我没有试图处理不寻常的属性;那些用描述符(包括属性)实现的,或者那些魔术方法在Python后面查找,这些方法通常从类而不是直接从实例开始。但是这种行为就像你问得适中一样(在我帖子的第一部分中就广告恶意阐述了警告;以这种方式查找属性不会给你任何不同的结果,而不是直接在实例中查找它们。)