свойства и наследование 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 в property появилась пара методов 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 при попытке использовать сеттер. Изменение только сеттера в значительной степени просто не работает. Единственный способ его определения (при условии, что геттер не переопределен) - это если вы используете @‹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, так как оно не требует повторного создания свойства в подклассе — просто метод получения или установки может быть переопределен, и применяется обычный 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, упомянутая в комментарии Питера Хоффманна. - 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

Я думаю, что ответ из Владимир Золотых почти оптимален.
Насколько я понимаю, несколько лучшим вариантом может быть вызов конструктора суперкласса.
В результате получается следующий код :

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

с питоном3.

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