Python属性和继承

63

我有一个基类,其中有一个属性(get方法),我想在子类中覆盖它。我的第一个想法是:

class Foo(object):
    def _get_age(self):
        return 11

    age = property(_get_age)


class Bar(Foo):
    def _get_age(self):
        return 44

此方法无效(子类bar.age返回11)。我找到了一个使用lambda表达式的解决方案:

age = property(lambda self: self._get_age())

这是在子类中使用属性并覆盖它们的正确解决方案吗?还是有其他更优选的方法可以实现?

11个回答

72

当覆盖类方法时,我更喜欢重复property()@classmethod装饰器。尽管在Python标准中看起来很冗长,但你可能会注意到:

1)对于只读属性,可以使用 property 作为装饰器:

class Foo(object):
    @property
    def age(self):
        return 11

class Bar(Foo):
    @property
    def age(self):
        return 44

2) 在Python 2.6中,属性增加了一对方法setterdeleter,可以用于将现有只读属性的快捷方式应用于普通属性:

class C(object):
    @property
    def x(self):
        return self._x

    @x.setter
    def x(self, value):
        self._x = value

5
等一下,所以解决父类行为定义的方案是重新实现它?这不是明显违反了DRY(不要重复自己)原则吗? - Adam Parkin
2
如果你只想更改getter或setter中的一种行为,则该方法会失败。如果只更改getter,使用setter时将会引发AttributeError异常。只更改setter几乎根本不起作用。唯一的定义它工作的方式(假设getter未重新定义)是使用@<super class>.<property name>.setter,但这是不正确的,因为它将修改超类的属性而不是子类的属性,这绝对不是你想要的。 - Matt
不是这样的,我提供了一个案例,只修改了 setter(尽管如果需要,getter、setter 或两者都可以更改)。 - Mr. B

21

我不同意所选答案是允许重写属性方法的理想方式。如果您期望getter和setter被重写,那么可以使用lambda来提供对self的访问,例如lambda self: self.<property func>

这适用于Python 2.4到3.6版本。

如果有人知道如何通过将property作为装饰器而不是直接调用property()来实现此操作,我很想听听!

示例:

class Foo(object):
    def _get_meow(self):
        return self._meow + ' from a Foo'
    def _set_meow(self, value):
        self._meow = value
    meow = property(fget=lambda self: self._get_meow(),
                    fset=lambda self, value: self._set_meow(value))

通过这种方式,可以轻松地执行覆盖:

class Bar(Foo):
    def _get_meow(self):
        return super(Bar, self)._get_meow() + ', altered by a Bar'

以便于:

>>> foo = Foo()
>>> bar = Bar()
>>> foo.meow, bar.meow = "meow", "meow"
>>> foo.meow
"meow from a Foo"
>>> bar.meow
"meow from a Foo, altered by a Bar"

我在geek at play上发现了这个。


1
这对我很有效。感谢您提供的示例和链接。 - KobeJohn

12

另一种方法是不需要创建任何额外类的。我添加了一个设置方法,以展示如果您仅覆盖其中之一,该怎么做:

class Foo(object):
    def _get_age(self):
        return 11

    def _set_age(self, age):
        self._age = age

    age = property(_get_age, _set_age)


class Bar(Foo):
    def _get_age(self):
        return 44

    age = property(_get_age, Foo._set_age)

这只是一个比较牵强的例子,但你应该能够理解。


如果需要使用Python < 2.6,则此解决方案似乎是正确的。 - ziima
这是一个足够好的解决方案,适用于Python 2.4+,但我更喜欢在_geek at play_上找到的解决方案,因为它不需要在子类中重新创建属性 - 只需覆盖getter或setter方法,并且正常的MRO适用。 - Mr. B
1
我也使用这种方法。我发现这段代码更易于阅读,并且子类可以访问super()。value_getter()或setter函数。 - VPfB

7

是的,这是正确的方法;属性声明在父类定义执行时执行,这意味着它只能“看到”父类存在的方法版本。因此,当您在子类中重新定义一个或多个这些方法时,您需要使用子类的版本重新声明属性。


6
一个可能的解决方法如下:
class Foo:
    def __init__(self, age):
        self.age = age

    @property
    def age(self):
        print('Foo: getting age')
        return self._age

    @age.setter
    def age(self, value):
        print('Foo: setting age')
        self._age = value


class Bar(Foo):
    def __init__(self, age):
        self.age = age

    @property
    def age(self):
        return super().age

    @age.setter
    def age(self, value):
        super(Bar, Bar).age.__set__(self, value)

if __name__ == '__main__':
    f = Foo(11)
    print(f.age)
    b = Bar(44)
    print(b.age)

它会打印出来。

Foo: setting age
Foo: getting age
11
Foo: setting age
Foo: getting age
44

灵感来自 David Beazley 和 Brian K. Jones 的 "Python Cookbook"。在 Debian GNU/Linux 9.11 (stretch) 上使用 Python 3.5.3。


2

像这样做可以起到作用。

class HackedProperty(object):
    def __init__(self, f):
        self.f = f
    def __get__(self, inst, owner):    
        return getattr(inst, self.f.__name__)()

class Foo(object):
    def _get_age(self):
        return 11
    age = HackedProperty(_get_age)

class Bar(Foo):
    def _get_age(self):
        return 44

print Bar().age
print Foo().age

记录一下带有设置、获取和删除的完整实现:http://infinitesque.net/articles/2005/enhancing Python's property.xhtml - Peter Hoffmann
顺便提一下,这里是一篇目前可用的文章链接 Python中属性函数可重写的替代方案,它在Peter Hoffmann的评论中提到过。 - martineau

2

我同意你的解决方案,这似乎是一种即时模板方法。 这篇文章涉及到了你的问题,并提供了与你的解决方案完全相同的方法。


2
@mr-b相同,但带有装饰器。
class Foo(object):
    def _get_meow(self):
        return self._meow + ' from a Foo'
    def _set_meow(self, value):
        self._meow = value
    @property
    def meow(self):
        return self._get_meow()
    @meow.setter
    def meow(self, value):
        self._set_meow(value)

这样,可以很容易地执行覆盖操作:
class Bar(Foo):
    def _get_meow(self):
        return super(Bar, self)._get_meow() + ', altered by a Bar'

0
我在子类中设置父类属性时遇到了问题。以下解决方法是通过直接调用父类的_set_age方法来扩展父类的属性。Wrinkled应该总是正确的。虽然有点像Java主义。
import threading


class Foo(object):
    def __init__(self):
        self._age = 0

    def _get_age(self):
        return self._age

    def _set_age(self, age):
        self._age = age

    age = property(_get_age, _set_age)


class ThreadsafeFoo(Foo):

    def __init__(self):
        super(ThreadsafeFoo, self).__init__()
        self.__lock = threading.Lock()
        self.wrinkled = False

    def _get_age(self):
        with self.__lock:
             return super(ThreadsafeFoo, self).age

    def _set_age(self, value):
        with self.__lock:
            self.wrinkled = True if value > 40 else False
            super(ThreadsafeFoo, self)._set_age(value)

    age = property(_get_age, _set_age)

0

我认为Vladimir Zolotykh答案几乎是最优的。
在我的理解中,一个稍微更好的变体可能是调用超类的构造器。
代码如下:

class Foo:
    def __init__(self, age):
        self.age = age

    @property
    def age(self):
        print("Foo: getting age")
        return self._age

    @age.setter
    def age(self, value):
        print("Foo: setting age")
        self._age = value


class Bar(Foo):
    def __init__(self, age):
        super().__init__(age)


if __name__ == "__main__":
    a = Foo(11)
    print(a.age)
    b = Bar(44)
    print(b.age)

使用这个解决方案,子类不需要重新实现属性。只需通过构造函数告诉子类,它应该像超类一样行为。

上述代码将产生以下输出:

Foo: setting age
Foo: getting age
11
Foo: setting age
Foo: getting age
44

使用Python3。


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