如何在派生类中重写一个属性后调用基类的同名属性?

103

我正在将我的一些类从广泛使用的getter和setter更改为更具Python风格的属性。

但是现在我遇到了困难,因为我的一些先前的getter或setter将调用基类的相应方法,然后执行其他操作。但是如何使用属性来实现这个呢?如何在父类中调用属性的getter或setter?

当然,仅调用属性本身会导致无限递归。

class Foo(object):

    @property
    def bar(self):
        return 5

    @bar.setter
    def bar(self, a):
        print a

class FooBar(Foo):

    @property
    def bar(self):
        # return the same value
        # as in the base class
        return self.bar # --> recursion!

    @bar.setter
    def bar(self, c):
        # perform the same action
        # as in the base class
        self.bar = c    # --> recursion!
        # then do something else
        print 'something else'

fb = FooBar()
fb.bar = 7
7个回答

114
你可能认为可以调用被属性调用的基类函数:
class FooBar(Foo):

    @property
    def bar(self):
        # return the same value
        # as in the base class
        return Foo.bar(self)

虽然这是最明显的尝试,但我认为 - 它不起作用,因为bar是一个属性,而不是可调用对象。

但属性只是一个带有getter方法来查找相应属性的对象:

class FooBar(Foo):

    @property
    def bar(self):
        # return the same value
        # as in the base class
        return Foo.bar.fget(self)

为什么在继承的setter中要调用Foo.bar.fset(self, c)?为什么不是Foo.bar.fset(c) - 没有"self" - 我以为这是隐式传递的? - nerdoc
2
我收到了一个 TypeError: 'property' 对象不可调用的错误。 - hithwen
@nerdoc,self 是从链式调用 Foo.bar.fset 中推断出来的吗? - Tadhg McDonald-Jensen
只是一个想法 - 据我所知,self总是隐式传递的。如果你执行foo = Foo() \ foo.bar(c),没有self被传递,但是*bar()*从Python中接收它。我不是Python专家,更多的是一个初学者。这只是一个想法。 - nerdoc
1
self is only implicitly passed when the method is called on an instance of a class. For example, if I have a class A with a method b(self, arg), and I create an instance c = A(), then calling c.b(arg) is equivalent to A.b(c, arg) - TallChuck
我想提一下,如果有人有理由不想按名称调用第一个基类,即使这会使代码变得模糊不清... Foo 就是 self.__class__.__bases__[0](对于 super(self.__class__.__bases__[1], self) 也是一个方便的技巧)。 - Matteo Ferla

70

super()可以解决问题:

return super().bar
在Python 2.x中,您需要使用更冗长的语法:
return super(FooBar, self).bar

2
类型错误:super()需要至少1个参数(未提供0个) - Aaron Maenpaa
4
我猜(好吧,根据链接判断的:P),这个答案是关于Python3的。在Python3中,super()可以不带参数,是的。 - shylent
14
使用super().bar在getter方面运行良好,但在通过重写的setter中通过基础属性进行赋值时不起作用。如果我尝试使用super().bar = 3 进行赋值,则会出现AttributeError: 'super' object has no attribute 'bar'的错误。 - Rob Smallshire
1
好观点,Rob。我不知道那个。这里有更多信息:https://dev59.com/rWgv5IYBdhLWcg3wKthA - Pankrat
2
虽然这对于获取有效,但在设置时会出现“AttributeError”错误。 - Ethan Furman
显示剩余3条评论

30

有一种另外的方法使用super,不需要显式地引用基类名称。

基类A:

class A(object):
    def __init__(self):
        self._prop = None

    @property
    def prop(self):
        return self._prop

    @prop.setter
    def prop(self, value):
        self._prop = value

class B(A):
    # we want to extend prop here
    pass

在B中,访问父类A的属性getter:

正如其他人已经回答的那样,这是:

super(B, self).prop

或者在 Python 3 中:

super().prop

这返回属性的getter方法所返回的值,而不是getter方法本身,但足以扩展getter方法。

在B中访问父类A的属性setter:

到目前为止,我看过的最好建议如下:

A.prop.fset(self, value)

我相信这个更好:

super(B, self.__class__).prop.fset(self, value)

在这个例子中,两个选项都是等效的,但使用super的优点是独立于B的基类。如果 B 还要继承一个扩展该属性的 C 类,您将不必更新 B 的代码。

B 扩展 A 属性的完整代码:

class B(A):
    @property
    def prop(self):
        value = super(B, self).prop
        # do something with / modify value here
        return value

    @prop.setter
    def prop(self, value):
        # do something with / modify value here
        super(B, self.__class__).prop.fset(self, value)

一个注意点:

除非您的属性没有setter,否则即使您只更改其中一个的行为,您也必须在B中定义setter和getter。


你好,这非常有帮助。我有一个跟进问题。这个设置很好用;但是,当我设置B的__init__来定义其他属性时,它就停止工作了。是否有一种方法可以为B拥有单独的__init__?谢谢。 - Sang
请问您能否解释一下super(B, self.__class__)super(class, class)的工作原理?它在哪里有文档记录? - Art
我在我的回答中建议了一些小的调整。 - Eric

4

Maxime的回答进行了一些小改进:

  • 使用__class__避免编写B。注意,self.__class__self的运行时类型,但没有self__class__是封闭类定义的名称。super()super(__class__, self)的简写。
  • 使用__set__而不是fset。后者只适用于property,而前者适用于所有类似属性(描述符)。
class B(A):
    @property
    def prop(self):
        value = super().prop
        # do something with / modify value here
        return value

    @prop.setter
    def prop(self, value):
        # do something with / modify value here
        super(__class__, self.__class__).prop.__set__(self, value)

我不清楚super(__class__, self)super(__class__, self.__class__)之间的区别,以及为什么我们需要后者来设置祖先类的属性? - CharlesB
1
help()中了解到,super有两种重载方式 - super(type,obj)是第一种情况,super(type,type2)是第二种情况。我们正在访问.prop作为类属性而不是实例属性,这就是为什么需要后者的原因。 - Eric
1
super(type(self), type(self)).setter.fset(self, value)在多重继承中无法正常工作。请尝试我的解决方案duper(super()).setter = value: https://gist.github.com/willrazen/bef3fcb26a83dffb6692e5e10d3e67ac - Will Razen
在什么情况下它不能正常工作? - Eric

4

尝试

@property
def bar:
    return super(FooBar, self).bar

虽然我不确定Python是否支持调用基类属性。属性实际上是一个可调用对象,它使用指定的函数设置,然后替换类中的名称。这很容易意味着没有super函数可用。

不过,您可以始终切换语法以使用property()函数:

class Foo(object):

    def _getbar(self):
        return 5

    def _setbar(self, a):
        print a

    bar = property(_getbar, _setbar)

class FooBar(Foo):

    def _getbar(self):
        # return the same value
        # as in the base class
        return super(FooBar, self)._getbar()

    def bar(self, c):
        super(FooBar, self)._setbar(c)
        print "Something else"

    bar = property(_getbar, _setbar)

fb = FooBar()
fb.bar = 7

如果您编写基类,那么这将很好地工作。但是如果您扩展了一个使用相同名称作为属性和getter的第三方基类呢? - akaihola

0
您可以使用以下模板:
class Parent():
    def __init__(self, value):
        self.__prop1 = value

    #getter
    @property
    def prop1(self):
        return self.__prop1

    #setter
    @prop1.setter
    def prop1(self, value):
        self.__prop1 = value

    #deleter
    @prop1.deleter
    def prop1(self):
        del self.__prop1
  
class Child(Parent):

    #getter
    @property
    def prop1(self):
        return super(Child, Child).prop1.__get__(self)

    #setter
    @prop1.setter
    def prop1(self, value):
        super(Child, Child).prop1.__set__(self, value)

    #deleter
    @prop1.deleter
    def prop1(self):
        super(Child, Child).prop1.__delete__(self)

注意!所有属性方法必须一起重新定义。如果不想重新定义所有方法,请使用以下模板:

class Parent():
    def __init__(self, value):
        self.__prop1 = value

    #getter
    @property
    def prop1(self):
        return self.__prop1

    #setter
    @prop1.setter
    def prop1(self, value):
        self.__prop1 = value

    #deleter
    @prop1.deleter
    def prop1(self):
        del self.__prop1


class Child(Parent):

    #getter
    @Parent.prop1.getter
    def prop1(self):
        return super(Child, Child).prop1.__get__(self)

    #setter
    @Parent.prop1.setter
    def prop1(self, value):
        super(Child, Child).prop1.__set__(self, value)

    #deleter
    @Parent.prop1.deleter
    def prop1(self):
        super(Child, Child).prop1.__delete__(self)

super(Child, Child) is wrong, it should be Super(Child, self.__class__) - Eric

-4
    class Base(object):
      def method(self):
        print "Base method was called"

    class Derived(Base):
      def method(self):
        super(Derived,self).method()
        print "Derived method was called"

    d = Derived()
    d.method()

(这是除非我从你的解释中漏掉了什么)


1
你是:他在谈论属性,而不是普通方法。 - akaihola

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接