Як викликати властивість базового класу, якщо ця властивість перезаписується у похідному класі?


87

Я міняю деякі мої класи від широкого використання геттерів та сеттерів до більш пітонічного використання властивостей.

Але зараз я застряг, тому що деякі з моїх попередніх геттерів або установників викликали б відповідний метод базового класу, а потім виконували щось інше. Але як цього можна досягти за допомогою властивостей? Як викликати властивість 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

Відповіді:


100

Ви можете подумати, що можете викликати функцію базового класу, яка викликається властивістю:

class FooBar(Foo):

    @property
    def bar(self):
        # return the same value
        # as in the base class
        return Foo.bar(self)

Хоча це найочевидніше, що я маю спробувати, я думаю - це не працює, оскільки бар є властивістю, а не викликається.

Але властивість - це просто об’єкт із методом геттера для пошуку відповідного атрибута:

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' не можна
викликати

@nerdoc, звідки натяк selfget з ланцюжка Foo.bar.fset?
Tadhg McDonald-Jensen

Тільки думка - самості AFAIK завжди неявно передаються. Якщо ви збираєтеся робити foo = Foo()\ foo.bar(c)НЕ саме пройшло, але бар () отримує його з Python. Я не фахівець з Python, більш-менш новачок. Це лише думка.
nerdoc 07.03.17

selfпередається лише неявно, коли метод викликається на екземплярі класу. Наприклад, якщо у мене є клас Aіз методом b(self, arg)і я створюю екземпляр 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

4
Я думаю (ну, судячи за посиланням: P), ця відповідь пов'язана з python3. У python3 super () може приймати нуль аргументів, так.
shylent

6
super (). bar, здається, добре працює для геттера, але не працює для присвоєння через базову властивість у перевизначеному сеттері. Якщо я роблю super (). Bar = 3, я отримую AttributeError: 'super' об'єкт не має атрибута 'bar'
Роб Смальлшир,

1
Хороший момент, Роб, не знав цього. Ось додаткова інформація: stackoverflow.com/questions/10810369/…
Панкрат,

2
Хоча це працює для отримання, не вдається встановити за допомогою AttributeError.
Ітан Фурман,

24

Існує альтернативний варіант використання super, який не вимагає явного посилання на ім’я базового класу.

Базовий клас А:

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

Це повертає значення, повернене гетером властивості, а не самим гетером, але цього достатньо, щоб продовжити гетер.

У B, доступ до установника властивостей батьківського класу A:

Найкраща рекомендація, яку я бачив дотепер, така:

A.prop.fset(self, value)

Я вважаю, що це краще:

super(B, self.__class__).prop.fset(self, value)

У цьому прикладі обидва варіанти еквівалентні, але використання супер має ту перевагу, що не залежить від базових класів 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навіть якщо ви змінюєте поведінку лише одного з них.


Привіт, це дуже корисно. У мене є наступне запитання щодо цього. Ця установка працює добре; однак він перестає працювати, коли я встановлюю init для B для визначення додаткових властивостей. Чи є спосіб створити окремий ініціал для B? Дякую
Співав

Не могли б ви пояснити, як super(B, self.__class__)саме працює super(class, class)? Де це задокументовано?
Арт

У своїй відповіді я запропонував декілька невеликих змін щодо цієї відповіді
Ерік,

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

Це чудово працює, якщо ви пишете базовий клас. Але що, якщо ви розширите сторонній базовий клас, який використовує одне і те ж ім’я для властивості та геттера?
akaihola

1

Деякі невеликі вдосконалення у відповіді Максима :

  • Використання, __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
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.