Come chiamare una proprietà della classe base se questa proprietà viene sovrascritta nella classe derivata?


87

Sto cambiando alcune mie classi da un uso estensivo di getter e setter a un uso più pitonico delle proprietà.

Ma ora sono bloccato perché alcuni dei miei precedenti getter o setter chiamavano il metodo corrispondente della classe base e quindi eseguivano qualcos'altro. Ma come si può ottenere questo risultato con le proprietà? Come chiamare la proprietà getter o setter nella classe genitore?

Ovviamente la semplice chiamata dell'attributo stesso fornisce una ricorsione 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

Risposte:


100

Potresti pensare di poter chiamare la funzione della classe base che viene chiamata dalla proprietà:

class FooBar(Foo):

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

Anche se questa è la cosa più ovvia da provare, penso che non funzioni perché il bar è una proprietà, non un richiamo.

Ma una proprietà è solo un oggetto, con un metodo getter per trovare l'attributo corrispondente:

class FooBar(Foo):

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

Perché dovrei chiamare Foo.bar.fset(self, c)un setter ereditato? Perché no Foo.bar.fset(c)- senza il "sé" - pensavo che questo fosse implicitamente passato?
nerdoc

2
Ottengo un'eccezione TypeError: l'oggetto 'property' non è richiamabile
hithwen

@nerdoc da dove vengono le implicazioni selfget dalla catena Foo.bar.fset?
Tadhg McDonald-Jensen

Solo un pensiero: per quanto ne so, il sé è sempre implicitamente passato. Se esegui foo = Foo()\ foo.bar(c)non viene trasmesso alcun self , ma bar () lo riceve da Python. Non sono un esperto di Python, più o meno anche un principiante. È solo un pensiero.
nerdoc

selfviene passato solo implicitamente quando il metodo viene chiamato su un'istanza di una classe. Ad esempio, se ho una classe Acon un metodo b(self, arg)e creo un'istanza c = A(), la chiamata c.b(arg)equivale aA.b(c, arg)
TallChuck

53

Super dovrebbe fare il trucco:

return super().bar

In Python 2.x è necessario utilizzare la sintassi più dettagliata:

return super(FooBar, self).bar

1
TypeError: super () richiede almeno 1 argomento (0 dato)
Aaron Maenpaa,

4
Immagino (beh, a giudicare dal link: P), questa risposta è correlata a python3. In python3 super () può accettare zero argomenti, sì.
shylent

6
super (). bar sembra funzionare bene per il getter, ma non funziona per l'assegnazione tramite la proprietà di base in un setter sovrascritto. Se faccio super (). Bar = 3 ottengo AttributeError: l'oggetto 'super' non ha l'attributo 'bar'
Rob Smallshire,

1
Buon punto Rob, non lo sapevo. Ecco maggiori informazioni: stackoverflow.com/questions/10810369/...
Pankrat

2
Sebbene funzioni per ottenere, non riesce per l'impostazione con un file AttributeError.
Ethan Furman

24

Esiste un'alternativa superche non richiede di fare riferimento esplicitamente al nome della classe di 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

In B, accedendo alla proprietà getter della classe genitrice A:

Come altri hanno già risposto, è:

super(B, self).prop

O in Python 3:

super().prop

Questo restituisce il valore restituito dal getter della proprietà, non il getter stesso ma è sufficiente per estendere il getter.

In B, accedendo al setter di proprietà della classe genitrice A:

La migliore raccomandazione che ho visto finora è la seguente:

A.prop.fset(self, value)

Credo che questo sia migliore:

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

In questo esempio entrambe le opzioni sono equivalenti ma l'uso di super ha il vantaggio di essere indipendente dalle classi base di B. SeBC dovessi ereditare da una classe che estende anche la proprietà, non dovresti aggiornare Bil codice di.

Codice completo di B che estende la proprietà di 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)

Un avvertimento:

A meno che la tua proprietà non abbia un setter, devi definire sia il setter che il getter Banche se cambi solo il comportamento di uno di loro.


Ciao, questo è molto utile. Ho una domanda di follow-up a questo. Questa configurazione funziona bene; tuttavia, smette di funzionare quando imposto init per B per definire proprietà aggiuntive. C'è un modo per avere un init separato per B? Grazie
cantato il

Potresti spiegare come super(B, self.__class__)funziona esattamente con super(class, class)? Dove è documentato?
Arte

Ho suggerito alcune piccole modifiche a questa risposta nella mia risposta
Eric

4

provare

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

Anche se non sono sicuro che python supporti la chiamata della proprietà della classe base. Una proprietà è in realtà un oggetto richiamabile che viene impostato con la funzione specificata e quindi sostituisce quel nome nella classe. Ciò potrebbe facilmente significare che non è disponibile alcuna super funzione.

Puoi sempre cambiare la sintassi per usare la funzione 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

Funziona bene se scrivi la classe base. Ma cosa succede se estendi una classe base di terze parti che utilizza lo stesso nome per la proprietà e il getter?
akaihola

1

Alcuni piccoli miglioramenti alla risposta di Maxime :

  • Usando __class__per evitare di scrivere B. Notare che self.__class__è il tipo di runtime di self, ma __class__ senza self è il nome della definizione della classe che lo racchiude. super()è una scorciatoia per super(__class__, self).
  • Usando __set__invece di fset. Quest'ultimo è specifico di propertys, ma il primo si applica a tutti gli oggetti simili a proprietà (descrittori).
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

Puoi utilizzare il seguente modello:

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! Tutti i metodi di proprietà devono essere ridefiniti insieme. Se non si desidera ridefinire tutti i metodi, utilizzare invece il seguente modello:

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()

(a meno che non mi manchi qualcosa dalla tua spiegazione)


1
tu sei: sta parlando di proprietà, non di metodi semplici
akaihola
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.