Как вызвать свойство базового класса, если это свойство перезаписывается в производном классе?

87

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

Но теперь я застрял, потому что некоторые из моих предыдущих методов получения или установки вызывали соответствующий метод базового класса, а затем выполняли что-то еще. Но как этого добиться с помощью свойств? Как вызвать метод получения или установки свойства в родительском классе?

Конечно, простой вызов атрибута дает бесконечную рекурсию.

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)

Хотя, я думаю, это наиболее очевидная вещь, которую можно попробовать - это не работает, потому что bar - это свойство, а не вызываемый объект.

Но свойство - это просто объект с методом получения для поиска соответствующего атрибута:

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: объект "свойство" не
вызывается
@nerdoc, где можно selfполучить из цепочки Foo.bar.fset?
Tadhg McDonald-Jensen
Просто мысль - AFAIK self всегда неявно передается. Если вы собираетесь делать foo = Foo()\ foo.bar(c)не само прошло, но бар () получает его из Python. Я не специалист по Python, более или менее новичок. Это просто мысль.
nerdoc 07
selfпередается только неявно, когда метод вызывается в экземпляре класса. Например, если у меня есть класс Aс методом b(self, arg), и я создаю экземпляр c = A(), то вызов c.b(arg)эквивалентенA.b(c, arg)
TallChuck
53

Super должен сделать свое дело:

return super().bar

В Python 2.x вам нужно использовать более подробный синтаксис:

return super(FooBar, self).bar
Панкрат
источник
1
TypeError: super () принимает как минимум 1 аргумент (задано 0)
Аарон Маенпаа,
4
Думаю (ну, судя по ссылке: P), этот ответ связан с python3. Да, в python3 super () может принимать нулевые аргументы.
shylent
6
super (). bar, похоже, отлично работает для получателя, но не работает для назначения через базовое свойство в переопределенном сеттере. Если я делаю super (). Bar = 3, я получаю AttributeError: объект «super» не имеет атрибута «bar»
Роб Смоллшир,
1
Хорошая мысль, Роб, не знал этого. Дополнительная информация: stackoverflow.com/questions/10810369/…
Панкрат
2
Это работает для получения, но не для настройки с расширением AttributeError.
Итан Фурман,
24

Существует альтернативный вариант использования 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:

Как уже ответили другие, это:

super(B, self).prop

Или в Python 3:

super().prop

Это возвращает значение, возвращаемое получателем свойства, а не сам получатель, но этого достаточно для расширения получателя.

В B доступ к установщику свойств родительского класса A:

Лучшая рекомендация, которую я видел до сих пор, следующая:

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)

Одно предостережение:

Если у вашего свойства нет сеттера, вам нужно определить как сеттер, так и получатель, Bдаже если вы изменяете поведение только одного из них.

Максим Р.
источник
Привет, это очень помогло. У меня есть дополнительный вопрос по этому поводу. Эта установка работает хорошо; однако он перестает работать, когда я устанавливаю init для B для определения дополнительных свойств. Есть ли способ создать отдельный init для B? Спасибо
пела
Не могли бы вы объяснить, как super(B, self.__class__)именно работает super(class, class)? Где это задокументировано?
Art
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
workmad3
источник
Это отлично работает, если вы пишете базовый класс. Но что, если вы расширите сторонний базовый класс, который использует одно и то же имя для свойства и получателя?
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
вы: он говорит о свойствах, а не о простых методах
акайхола