python:super()-类似于代理对象,在指定类上启动MRO搜索

python:super()-类似于代理对象,在指定类上启动MRO搜索,python,python-3.x,multiple-inheritance,superclass,super,Python,Python 3.x,Multiple Inheritance,Superclass,Super,根据文档,super(cls,obj)返回 将方法调用委托给父级或同级的代理对象 cls类型的类 我理解为什么super()提供了这个功能,但我需要一些稍微不同的东西:我需要创建一个代理对象,将方法调用(和属性查找)委托给类cls本身;与super一样,如果cls没有实现方法/属性,我的代理应该继续按照MRO顺序(新类而不是原始类)查找。我能写什么函数来实现这一点 例如: class X: def act(): #... class Y: def act(): #...

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

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

我理解为什么
super()
提供了这个功能,但我需要一些稍微不同的东西:我需要创建一个代理对象,将方法调用(和属性查找)委托给类
cls
本身;与
super
一样,如果
cls
没有实现方法/属性,我的代理应该继续按照MRO顺序(新类而不是原始类)查找。我能写什么函数来实现这一点

例如:

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
传递给其他人,不知道他们会怎么处理它。我需要确保它不会背叛我,并在某个时候开始表现得像
类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()
请注意,更新的
类委托
super()
更接近我想要的,原因有两个:

  • super()
    只对第一次调用进行代理;后续调用将正常进行,因为此时将使用对象,而不是其代理

  • super()
    不允许属性访问

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


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

    本课程应该涵盖最常见的情况:

    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)
    
    (使用示例代码中的名称。)

    限制:

  • 您无法从通过此代理传入构造函数的类中检索某些特殊属性,如
    \uuuuuuuuuuuuuu class\uuuuuuu
    等。(此restistions也适用于
    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
    
    另一个问题是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

    如果我想将
    实例
    视为C的实例,那么在调用
    方法
    之前,它是否应该具有
    baz
    属性?之后呢

    如果我将
    instance
    视为A的实例,那么
    foo
    应该具有什么值?
    bar
    应该是不可见的,因为它只添加到B中,还是可见的,因为它被设置为类外的值

    所有这些问题在Python中都是无稽之谈。用Python的语义设计一个系统是不可能的,它不能给出合理的答案
    \uuuu init\uuuu
    在向类实例添加属性方面甚至没有什么特别之处;它只是一个非常普通的方法,碰巧作为实例创建协议的一部分被调用。任何方法(或者完全来自另一个类的代码,或者根本不来自任何类)都可以在其引用的任何实例上创建属性

    实际上,
    instance
    的所有属性都存储在同一个位置:

    >>> instance.__dict__
    {'baz': 'baz set from C', 'foo': 'foo set from D', 'bar': 'not set from a class at all'}
    
    没有办法知道它们中的哪一个最初是由哪个类设置的,或者是最后由哪个类设置的,或者任何您想要的所有权度量。当然,没有办法让“<代码> A.foo <代码>被<代码> D.foo < /C> >所遮蔽,正如你所期望的C++;它们是相同的属性,一个类(或从其他地方)对其进行的任何写入都将破坏另一个类在其中留下的值

    其结果是
    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
    
    但是:

    然后:

    我依靠类的
    \uuumro\uuuu
    属性来正确地确定从何处开始,然后我只使用
    super
    。你可以从这一点开始走MRO链,检查类
    \uuu dict\uuu
    s的方法,如果返回一步使用
    super
    的奇怪之处太多了

    我没有试图处理不寻常的属性;那些用描述符(包括属性)实现的,或者Pyth在幕后查找的神奇方法
    >>> 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
    
    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)