如果派生类中的基类属性被覆盖,该如何调用基类的属性?


87

我正在将我的某些类从对getter和setter的广泛使用改为对属性的更pythonic的使用。

但是现在我陷入了困境,因为以前的一些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

Answers:


100

您可能认为您可以调用由属性调用的基类函数:

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)

为什么要调用Foo.bar.fset(self, c)继承的二传手?为什么不Foo.bar.fset(c)-没有“自我”-我以为这是暗中通过的?
nerdoc

2
我收到一个TypeError:'property'对象不可调用
hithwen 2013年

@nerdoc selfget在链中的含义是Foo.bar.fset什么?
塔德格·麦克唐纳·詹森

只是一个想法-AFAIK自我总是被隐式传递。如果执行foo = Foo()\ foo.bar(c),则不会传递自我,但是bar()从Python接收到它。我既不是Python专家,也不是初学者。这只是一个想法。
nerdoc

self仅当在类的实例上调用该方法时才隐式传递。例如,如果我有一个A带有method的类b(self, arg),并且创建了一个instance c = A(),则调用c.b(arg)等效于A.b(c, arg)
TallChuck

53

超级应该做到这一点:

return super().bar

在Python 2.x中,您需要使用更详细的语法:

return super(FooBar, self).bar

1
TypeError:super()至少接受1个参数(给定0个)
Aaron Maenpaa,2009年

4
我猜(通过链接:P判断),这个答案与python3有关。在python3中,super()可以接受零个参数,是的。
shylent

6
super()。bar似乎对getter很好用,但不适用于通过重写的setter中的base属性进行赋值的情况。如果我做超()吧= 3,我得到AttributeError的:“超级”对象有没有属性“酒吧”
罗布Smallshire

1
好点,罗布,不知道。以下是详细信息:stackoverflow.com/questions/10810369/...
潘克拉特

2
尽管此方法可以获取,但无法使用进行设置AttributeError
伊桑·弗曼

24

有一种替代使用 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的属性获取器:

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

super(B, self).prop

或在Python 3中:

super().prop

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

在B中,访问父类A的属性设置器:

到目前为止,我看到的最好的建议是:

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)

一个警告:

除非您的属性没有设置器,否则B即使只更改其中一个的行为,也必须同时定义设置器和获取器。


您好,这非常有帮助。我对此有一个后续问题。此设置效果很好;但是,当我为B设置init以定义其他属性时,它将停止工作。有没有办法为B提供单独的初始化?谢谢
Sang

您能解释一下如何super(B, self.__class__)正确使用super(class, class)吗?它在哪里记录?
艺术

我已经在我的答案中
Eric

4

尝试

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

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

不过,您始终可以切换语法以使用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 2013年

1

Maxime答案的一些小改进:

  • 使用__class__以避免书写B。请注意,这self.__class__是的运行时类型self,但__class__ 没有 self是封闭类定义的名称。super()是的简写super(__class__, self)
  • 使用__set__代替fset。后者特定于propertys,但前者适用于所有类似属性的对象(描述符)。
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)

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)

-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
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.