首页 文章

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

提问于
浏览
4

根据文档, 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) ,但这依赖于知道确切的类层次结构 and 事实是 B 在MRO中跟随 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) .

2 回答

  • 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 . )

    • 如果要检索的属性是某种类型的描述符,这可能会表现得很好 .

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

    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
    
  • 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'
    

    哪个类"owns" foobarbaz

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

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

    所有这些问题在Python中都是无稽之谈 . 没有办法设计一个具有Python语义的系统,可以为它们提供合理的答案 . __init__ isn 't even special in terms of adding attributes to instances of the class; it'只是一个非常普通的方法,恰好被称为实例创建协议的一部分 . 任何方法(或者实际上来自另一个类的代码,或根本不来自任何类的代码)都可以在它引用的任何实例上创建属性 .

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

    >>> instance.__dict__
    {'baz': 'baz set from C', 'foo': 'foo set from D', 'bar': 'not set from a class at all'}
    

    肯定没有办法得到“ A.fooD.foo 阴影”,正如你对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
    

    但:

    >>> 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 忽略任何类的完整MRO _delegate_obj 是一个实例,只使用 _delegate_cls 的MRO . 虽然 super 做了你在问题中提出的问题,但行为似乎很奇怪:它是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后面查找,这些方法通常从类而不是直接从实例开始 . 但是这种行为就像你问得适度一样(在我的帖子的第一部分中对广告恶心进行了阐述;以这种方式查找属性不会给你任何不同的结果,而不是直接在实例中查找它们) .

相关问题