свойства и наследяване на python

Имам базов клас със свойство, което (метода 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). Намерих решение с ламбда израз, който работи:

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

И така, това правилното решение ли е за използване на свойства и презаписването им в подклас или има други предпочитани начини да направите това?


person Peter Hoffmann    schedule 26.10.2008    source източник


Отговори (11)


Просто предпочитам да повторя 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, свойствата увеличиха двойка методи setter и deleter, които могат да се използва за прилагане към общите свойства на прекия път, който вече е наличен за такива само за четене:

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

    @x.setter
    def x(self, value):
        self._x = value
person piro    schedule 26.10.2008
comment
Чакайте, така че предложеното решение за получаване на поведение, дефинирано в родителски клас, е повторното му прилагане? Това не изглежда ли като грубо нарушение на DRY (не се повтаряйте)? - person Adam Parkin; 26.06.2012
comment
Това се проваля в случай, когато искате да промените поведението само на инструмента за получаване или настройка, но не и на двата. Ако промените само гетъра, ще получите AttributeError, когато се опитвате да използвате сетера. Промяната само на сетера почти просто не работи. Единственият начин, по който дефинирането му работи (ако приемем, че getter не е предефиниран) е, ако използвате @‹super class›.‹property name›.setter, но това е неправилно, тъй като ще промени свойството на суперкласа, а не на подкласа, който определено не е това, което искате. - person Matt; 02.01.2013
comment
Не е вярно, предоставих случай по-долу, при който се променя само сетерът (въпреки че или гетерът, сетерът или и двете могат да бъдат променени, ако желаете). - person Mr. B; 01.07.2014

Не съм съгласен, че избраният отговор е идеалният начин да се позволи замяна на методите на свойствата. Ако очаквате гетерите и сетерите да бъдат отменени, тогава можете да използвате ламбда, за да предоставите достъп до себе си, с нещо като lambda self: self.<property func>.

Това работи (поне) за Python версии 2.4 до 3.6.

Ако някой знае начин да направи това с използване на свойство като декоратор вместо като директно извикване на свойство(), бих искал да го чуя!

Пример:

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"

Открих това на маниак в възпроизвеждане.

person Mr. B    schedule 16.01.2013
comment
Това работи добре за мен. Благодаря за примера и линка. - person KobeJohn; 12.02.2013

Друг начин да го направите, без да се налага да създавате допълнителни класове. Добавих set метод, за да покажа какво правите, ако замените само един от двата:

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)

Това е доста измислен пример, но трябва да схванете идеята.

person Kamil Kisiel    schedule 14.11.2008
comment
Това изглежда правилно решение, ако е необходим python ‹ 2.6. - person ziima; 18.06.2014
comment
Това е достатъчно прилично решение, което работи с python 2.4+, но ми харесва повече решението, което намерих на geek at play, тъй като не изисква повторно създаване на свойството в подкласа -- просто методът getter или setter може да бъде заменен и се прилага нормално MRO. - person Mr. B; 01.07.2014
comment
И аз използвам този подход. Намерих този код по-лесен за четене и подкласовете имат достъп до функция super().value_getter() или setter. - person VPfB; 21.12.2015

Да, това е начинът да го направите; декларацията на свойството се изпълнява по времето, когато се изпълнява дефиницията на родителския клас, което означава, че може да "вижда" само версиите на методите, които съществуват в родителския клас. Така че, когато предефинирате един или повече от тези методи на дъщерен клас, трябва да декларирате отново свойството, като използвате версията на метода(ите) на дъщерния клас.

person James Bennett    schedule 26.10.2008

Възможно заобиколно решение може да изглежда така:

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

Взех идеята от „Готварска книга на Python“ от Дейвид Бийзли и Браян К. Джоунс. Използване на Python 3.5.3 на Debian GNU/Linux 9.11 (разтягане)

person Vladimir Zolotykh    schedule 04.01.2020

Съгласен съм с вашето решение, което изглежда като шаблонен метод в движение. Тази статия се занимава с вашия проблем и предоставя точно вашето решение.

person Federico A. Ramponi    schedule 26.10.2008

Нещо подобно ще свърши работа

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
person Kozyarchuk    schedule 26.10.2008
comment
за протокола пълно внедряване със set, get и del: infinitesque.net/articles/2005/enhancing свойството на Python.xhtml - person Peter Hoffmann; 26.10.2008
comment
FWIW, ето текущо работеща връзка към статията Отменяема алтернатива на свойството Функция в Python, спомената в коментара на Peter Hoffmann. - person martineau; 28.02.2013

Същото като @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'
person Nizam Mohamed    schedule 20.05.2016

Сблъсках се с проблеми при настройването на свойство в родителски клас от дъщерен клас. Следното решение разширява свойство на родител, но го прави чрез директно извикване на метода _set_age на родителя. Набръчканите винаги трябва да са правилни. Все пак е малко javathonic.

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)
person andrew pate    schedule 02.06.2015

Мисля, че отговорът от 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.

person Bastian Ebeling    schedule 16.05.2021

class Foo:
    # Template method
    @property
    def age(self):
        return self.dothis()
    # Hook method of TM is accessor method of property at here
    def dothis(self):
        return 11
class Bar(Foo):
    def dothis(self):
        return 44

Същото като Низам Мохамед, само да спомена, че ръководство за стил 2.13 .4 използване както на метода на шаблона, така и на свойството

person Robin    schedule 23.08.2019