【问题标题】:python: super()-like proxy object that starts the MRO search at a specified classpython:在指定类开始 MRO 搜索的类似 super() 的代理对象
【发布时间】:2012-02-09 00:53:46
【问题描述】:

根据文档,super(cls, obj) 返回

将方法调用委托给父级或兄弟级的代理对象 cls 类型的类

我理解super() 提供此功能的原因,但我需要一些稍微不同的东西:我需要创建一个代理对象,将方法调用(和属性查找)委托给类cls 本身;和super 一样,如果cls 没有实现方法/属性,我的代理应该继续查看MRO 顺序(new 而不是original班级)。有没有我可以编写的函数来实现这一点?

例子:

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),但这取决于了解确切的类层次结构B 在 MRO 中遵循 A 的事实。如果这两个假设中的任何一个在未来发生变化,它就会悄然崩溃。 (注意super 没有做出任何这样的假设!)

如果我只需要拨打b.act(),我可以使用B.act(c)。但是我正在将b 传递给其他人,并且不知道他们会用它做什么。我需要确保它不会背叛我,并在某个时候开始表现得像 class C 的实例。

一个单独的问题,super()(在 Python 3.2 中)的文档只讨论了它的方法委托,并没有说明代理的属性查找也以相同的方式执行。是不小心遗漏了吗?

编辑

更新后的委托方法也适用于以下示例:

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 Delegatesuper() 更接近我想要的,原因有两个:

  1. super() 只代理第一个呼叫;后续调用将照常进行,因为此时使用的是对象,而不是其代理。

  2. super() 不允许属性访问。

因此,我提出的问题在 Python 中有一个(几乎)完美的答案。

事实证明,在更高的层次上,我试图做一些我不应该做的事情 (see my comments here)。

【问题讨论】:

  • 你不能确保它不像 C 类的实例,因为它就是这样。如果您真的非常想要一个 B() 的实例,我建议您只需创建一个具有所需属性的 B 实例。您可以从 C 复制它们。
  • @LennartRegebro:好吧,我没有要求bclass C 的一个实例,事实上它不是;它是一个完全不同的类的实例(class Delegate 在 Sven 解决方案的情况下,class super 在内置super() 的情况下,等等)。除此之外,您可能是对的,b 不能被阻止背叛其(间接)访问class C 实例方法和属性的能力。如果是这样,您能否举例说明 Sven 的 Delegate(B, c) 将如何充当 class C 的实例?

标签: python python-3.x multiple-inheritance superclass super


【解决方案1】:

这个类应该涵盖最常见的情况:

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)

(使用示例代码中的名称。)

限制:

  1. 您无法从通过此代理传入构造函数的类中检索某些特殊属性,例如__class__ 等。 (此限制也适用于super。)

  2. 如果您要检索的属性是某种奇怪的描述符,这可能会表现得很奇怪。

编辑:如果您希望问题更新中的代码按预期工作,您可以使用以下代码:

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

【讨论】:

  • 我刚刚更新了这个问题,因为事实证明我实际上将b 传递给了其他人。这意味着我的functools 解决方案不再有效。看来你做的正好符合我的需求,让我测试一下。我想我只是说b = Delegate(B, c)?
  • 在存在多重继承的情况下,这不像super。它委托给_delegate_cls 的MRO,无论它在_delegate_obj 的完整MRO 中处于什么位置。事实上,它委托给_delegate_cls,不管它是否在_delegate_obj 的完整MRO 中,这很有趣。 :) 但是鉴于 OP 明确谈到了多重继承和 MRO,这可能不是我们所希望的。它也不支持属性查找(super 也不介意)。
  • 啊,是的,我们需要检查 cls 是否在 MRO 中,如果在 cls 中找不到方法,则在 cls 之后从 MRO 中的下一个类开始搜索
  • @Ben:我不确定是否真的需要使用原始对象的 MRO。
  • @Max:Delegate 使用getattr()cls 中查找属性。这自动意味着cls 的所有基类也会被搜索。正如 Ben 所说,这与原始对象的 MRO 中 cls 之后的类不同,但我不确定这是您真正想要的。
【解决方案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'

哪个类“拥有”foobarbaz

如果我想将instance 视为C 的一个实例,它是否应该在调用method 之前具有baz 属性?之后呢?

如果我将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.fooD.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。如果返回一步使用super 的怪异太多,您可以从那时开始自己检查类__dict__s 的方法来遍历MRO 链。

我没有尝试处理异常属性;那些用描述符(包括属性)实现的,或者那些由 Python 在幕后查找的魔术方法,它们通常从类开始而不是直接从实例开始。但这表现得像您所问的那样(在我的帖子的第一部分中阐述了令人作呕的警告;以这种方式查找属性与直接在实例中查找属性不会给您带来任何不同的结果)。

【讨论】:

  • superDelegate 不处理实例属性,因为这样做没有多大意义。不过,它们都确实正确处理了类属性。
  • @SvenMarnach 它确实用于 OP 的声明目的,即捆绑一个实例,该实例实现在 MRO 中进一步启动的方法,然后传递给其他不知道它正在获取包装器的代码。通常,但并非总是如此,其他代码将纯粹通过方法操作对象,因此您也希望能够检索属性。
  • @Ben:谢谢,这真的有助于我理解为什么super() 不适用于属性查找。而且我确实了解您用于在原始 MRO 中查找方法的代码;出于我的目的,我确实希望将新的 MRO 用于我的目的。
猜你喜欢
  • 1970-01-01
  • 2021-07-24
  • 2015-10-17
  • 1970-01-01
  • 2014-08-15
  • 2023-04-04
  • 1970-01-01
  • 1970-01-01
  • 2011-06-15
相关资源
最近更新 更多