【问题标题】:How to call a property of the base class if this property is being overwritten in the derived class?如果该属性在派生类中被覆盖,如何调用基类的属性?
【发布时间】:2010-11-04 12:31:14
【问题描述】:

我正在将我的一些类从广泛使用 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

【问题讨论】:

    标签: python inheritance properties overloading descriptor


    【解决方案1】:

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

    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) - 没有“自我” - 我认为这是隐含传递的?
    • 我得到一个 TypeError: 'property' object is not callable
    • @nerdoc 从Foo.bar.fset 链中得到的self 暗示在哪里?
    • 只是一个想法 - AFAIK self 总是被隐式传递。如果您执行 foo = Foo() \ foo.bar(c) 没有传递 self,但 bar() 从 Python 接收它。我不是 Python 专家,或多或少也是初学者。这只是一个想法。
    • self 仅在对类的实例调用该方法时才隐式传递。例如,如果我有一个类A 和一个方法b(self, arg),并且我创建了一个实例c = A(),那么调用c.b(arg) 就相当于A.b(c, arg)
    【解决方案2】:

    Super 应该可以解决问题:

    return super().bar
    

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

    return super(FooBar, self).bar
    

    【讨论】:

    • TypeError: super() 至少需要 1 个参数(给定 0)
    • 我猜(嗯,从链接判断:P),这个答案是 python3 相关的。在 python3 中 super() 可以接受零个参数,是的。
    • super().bar 似乎适用于 getter,但不适用于通过覆盖 setter 中的基本属性进行分配。如果我做 super().bar = 3 我得到 AttributeError: 'super' object has no attribute 'bar'
    • 好点 Rob,不知道。这里有更多信息:stackoverflow.com/questions/10810369/…
    • 虽然这适用于获取,但无法使用 AttributeError 进行设置。
    【解决方案3】:

    还有一个使用 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 ?谢谢
    • 您能否解释一下super(B, self.__class__) 如何与super(class, class) 完全配合使用?它记录在哪里?
    • 我在my answer 中建议对此答案进行一些小调整
    【解决方案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 的第三方基类呢?
    【解决方案5】:

    Maxime's answer的一些小改进:

    • 使用__class__ 避免写入B。请注意,self.__class__self 的运行时类型,但 __class__ without 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)
    

    【讨论】:

    • 我不清楚super(__class__, self)super(__class__, self.__class__) 之间的区别,以及为什么我们需要后者来设置祖先类的属性?
    • help()super,有两个重载——super(type, obj),第一种情况,super(type, type2),第二种情况。我们将.prop 作为类属性而不是实例属性访问,这就是需要后者的原因。
    • super(type(self), type(self)).setter.fset(self, value) 不适用于多重继承。试试我的解决方案duper(super()).setter = valuegist.github.com/willrazen/bef3fcb26a83dffb6692e5e10d3e67ac
    • 什么情况下不能正常工作?
    【解决方案6】:

    您可以使用以下模板:

    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)错了,应该是Super(Child, self.__class__)
    【解决方案7】:
        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()
    

    (除非我在你的解释中遗漏了什么)

    【讨论】:

    • 你是:他在谈论属性,而不是简单的方法
    猜你喜欢
    • 2017-07-05
    • 1970-01-01
    • 1970-01-01
    • 2013-05-16
    • 1970-01-01
    • 2021-08-31
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多