python: super()-like proxy object that starts the MRO search at a specified class
根据文件,
a proxy object that delegates method calls to a parent or sibling
class of type cls
号
我理解为什么
例子:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | 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 |
当然,我可以做
如果我只需要打电话给
另外一个问题是,
编辑
更新的委托方法也适用于以下示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 | 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() |
号
注意,更新的
因此,我提出的问题在Python中有一个(几乎)完美的答案。
事实证明,在更高的层次上,我试图做一些我不应该做的事情(见我在这里的评论)。
此类应涵盖最常见的情况:
1 2 3 4 5 6 7 8 9 | 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 |
这样使用:
1 | b = Delegate(B, c) |
号
(使用示例代码中的名称。)
限制:
您不能从通过该代理传递给构造函数的类中检索一些特殊属性,如
如果要检索的属性是某种特殊类型的描述符,则这可能表现为异常。
编辑:如果希望问题更新中的代码按需工作,可以使用以下代码:
1 2 3 4 5 6 7 8 | 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 |
这将代理对象作为
如果还希望可以访问实例属性,则可以使用此版本:
1 2 3 4 5 6 7 8 9 10 11 | 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 |
。
A separate question, the documentation for super() (in Python 3.2)
only talks about its method delegation, and does not clarify that
attribute lookups for the proxy are also performed the same way. Is it
an accidental omission?
号
不,这不是偶然的。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | 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' |
哪个阶级"拥有"
如果我想把
如果我把
所有这些问题在Python中都是胡说八道。没有可能的方法来设计一个具有Python语义的系统,该语义可以为它们提供合理的答案。
实际上,
1 2 | >>> instance.__dict__ {'baz': 'baz set from C', 'foo': 'foo set from D', 'bar': 'not set from a class at all'} |
号
无法判断它们中的哪一个最初是由哪个类设置的,或者最后一个是由哪个类设置的,或者您想要的任何所有权度量。当然,没有办法让"EDOCX1 12"被EDOCX1"13"所遮蔽,正如你所期望的C++一样,它们是同一个属性,任何一个类(或来自别处)对它的写入都会破坏另一个类中的一个值。
这样做的结果是,
事实上,从运行一些实验来看,无论是
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | 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) |
然后这两种方法都按预期工作:
1 2 3 4 | >>> d.foo() A.foo >>> s.foo() A.foo |
。
但是:
1 2 3 4 5 6 7 8 9 10 11 12 | >>> 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' |
因此,它们都只适用于调用某些方法,而不适用于传递给任意第三方代码来假装是要委托给的类的实例。
不幸的是,在存在多个继承的情况下,它们的行为并不相同。鉴于:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 | 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) |
。
然后:
1 2 3 4 5 6 7 8 9 | >>> 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 |
。
因为
这是我的尝试:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | 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) |
我依靠类的
我没有尝试处理异常的属性;那些用描述符(包括属性)实现的属性,或者那些用Python在后台查找的神奇方法,这些方法通常从类而不是直接从实例开始。但这是你所要求的适度的行为(在我的文章的第一部分中有关于恶心的警告;用这种方式查找属性与直接在实例中查找属性相比,不会给你任何不同的结果)。