Como chamar uma propriedade da classe base se essa propriedade estiver sendo substituída na classe derivada?

87

Estou mudando algumas classes minhas de um uso extensivo de getters e setters para um uso mais pythônico de propriedades.

Mas agora estou preso porque alguns dos meus getters ou setters anteriores chamariam o método correspondente da classe base e, em seguida, executariam outra coisa. Mas como isso pode ser feito com propriedades? Como chamar a propriedade getter ou setter na classe pai?

É claro que apenas chamar o próprio atributo dá recursão infinita.

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
UncleZeiv
fonte

Respostas:

100

Você pode pensar que poderia chamar a função da classe base, que é chamada por propriedade:

class FooBar(Foo):

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

Embora essa seja a coisa mais óbvia a tentar, eu acho - não funciona porque bar é uma propriedade, não um chamável.

Mas uma propriedade é apenas um objeto, com um método getter para encontrar o atributo correspondente:

class FooBar(Foo):

    @property
    def bar(self):
        # return the same value
        # as in the base class
        return Foo.bar.fget(self)
David Cournapeau
fonte
Por que devo chamar Foo.bar.fset(self, c)um setter herdado? Por que não Foo.bar.fset(c)- sem o "eu" - pensei que isso fosse implicitamente passado?
nerdoc de
2
Recebo um TypeError: o objeto 'propriedade' não pode ser
chamado
@nerdoc onde o selfget implica na cadeia Foo.bar.fset?
Tadhg McDonald-Jensen
Apenas um pensamento - o eu AFAIK é sempre passado implicitamente. Se você fizer um foo = Foo()\, foo.bar(c)não haverá auto- aprovação, mas bar () o receberá do Python. Não sou nenhum especialista em Python, mais ou menos um iniciante também. É apenas um pensamento.
nerdoc
selfsó é passado implicitamente quando o método é chamado em uma instância de uma classe. Por exemplo, se eu tenho uma classe Acom um método b(self, arg)e crio uma instância c = A(), chamar c.b(arg)é equivalente aA.b(c, arg)
TallChuck
53

Super deve resolver:

return super().bar

No Python 2.x, você precisa usar uma sintaxe mais detalhada:

return super(FooBar, self).bar
Pankrat
fonte
1
TypeError: super () leva pelo menos 1 argumento (0 fornecido)
Aaron Maenpaa
4
Eu acho (bem, a julgar pelo link: P), essa resposta está relacionada ao python3. Em python3, super () pode aceitar zero argumentos, sim.
shylent de
6
super () .bar parece funcionar bem para o getter, mas não funciona para atribuição por meio da propriedade base em um setter substituído. Se eu fizer super (). Bar = 3, obtenho AttributeError: o objeto 'super' não tem o atributo 'bar'
Rob Smallshire
1
Bom ponto Rob, não sabia disso. Mais informações: stackoverflow.com/questions/10810369/…
Pankrat
2
Embora funcione para obter, falha ao configurar com um AttributeError.
Ethan Furman
24

Há uma alternativa de uso superque não requer referência explícita ao nome da classe base.

Classe base 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

Em B, acessando o getter de propriedade da classe pai A:

Como outros já responderam, é:

super(B, self).prop

Ou em Python 3:

super().prop

Isso retorna o valor retornado pelo getter da propriedade, não o getter em si, mas é suficiente para estender o getter.

Em B, acessando o configurador de propriedades da classe pai A:

A melhor recomendação que vi até agora é a seguinte:

A.prop.fset(self, value)

Eu acredito que este é melhor:

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

Neste exemplo, ambas as opções são equivalentes, mas usar super tem a vantagem de ser independente das classes base de B. Se Bfosse herdar de uma Cclasse que também estende a propriedade, você não teria que atualizar Bo código.

Código completo de B estendendo a propriedade de 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)

Uma ressalva:

A menos que sua propriedade não tenha um setter, você deve definir o setter e o getter, Bmesmo que apenas altere o comportamento de um deles.

Maxime R.
fonte
Olá, isso é muito útil. Eu tenho uma pergunta complementar para isso. Esta configuração funciona bem; no entanto, ele para de funcionar quando defino o init para B para definir propriedades adicionais. Existe uma maneira de ter um init separado para B? Obrigado
Cantado em
Você poderia explicar como super(B, self.__class__)funciona exatamente com super(class, class)? Onde está documentado?
Art
Sugeri alguns pequenos ajustes para esta resposta em minha resposta
Eric,
4

tentar

@property
def bar:
    return super(FooBar, self).bar

Embora eu não tenha certeza se python suporta a chamada da propriedade da classe base. Uma propriedade é na verdade um objeto chamável que é configurado com a função especificada e então substitui aquele nome na classe. Isso pode significar facilmente que não há nenhuma função super disponível.

Você sempre pode mudar sua sintaxe para usar a função 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
fonte
Isso funciona bem se você escrever a classe base. Mas e se você estender uma classe base de terceiros que usa o mesmo nome para a propriedade e o getter?
akaihola
1

Algumas pequenas melhorias na resposta de Maxime :

  • Usando __class__para evitar a escrita B. Observe que self.__class__é o tipo de tempo de execução de self, mas __class__ sem self é o nome da definição de classe envolvente. super()é uma abreviação de super(__class__, self).
  • Usando em __set__vez de fset. O último é específico para propertys, mas o primeiro se aplica a todos os objetos semelhantes a propriedades (descritores).
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)
Eric
fonte
0

Você pode usar o seguinte modelo:

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)

Nota! Todos os métodos de propriedade devem ser redefinidos juntos. Se não quiser redefinir todos os métodos, use o seguinte modelo:

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)
Nader Aryabarzan
fonte
-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()

(isto é, a menos que eu esteja faltando alguma coisa em sua explicação)

brilhante
fonte
1
você é: ele está falando sobre propriedades, não métodos simples
akaihola