¿Cómo llamar a una propiedad de la clase base si esta propiedad se sobrescribe en la clase derivada?

87

Estoy cambiando algunas clases mías de un uso extensivo de captadores y definidores a un uso más pitónico de propiedades.

Pero ahora estoy atascado porque algunos de mis getters o setters anteriores llamaban al método correspondiente de la clase base y luego realizaban otra cosa. Pero, ¿cómo se puede lograr esto con propiedades? ¿Cómo llamar al getter o setter de propiedad en la clase principal?

Por supuesto, simplemente llamar al atributo en sí da una recursividad 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
TíoZeiv
fuente

Respuestas:

100

Podría pensar que podría llamar a la función de clase base que es llamada por propiedad:

class FooBar(Foo):

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

Aunque creo que esto es lo más obvio para intentarlo , no funciona porque bar es una propiedad, no un invocable.

Pero una propiedad es solo un objeto, con un método getter para encontrar el atributo correspondiente:

class FooBar(Foo):

    @property
    def bar(self):
        # return the same value
        # as in the base class
        return Foo.bar.fget(self)
David Cournapeau
fuente
¿Por qué debería llamar Foo.bar.fset(self, c)a un setter heredado? ¿Por qué no Foo.bar.fset(c), sin el "yo", pensé que esto se pasa implícitamente?
nerdoc
2
Obtengo un TypeError: el objeto 'propiedad' no se puede
llamar
@nerdoc ¿de dónde viene el selfget de la cadena Foo.bar.fset?
Tadhg McDonald-Jensen
Solo un pensamiento: el yo AFAIK siempre se pasa implícitamente. Si hace un foo = Foo()\, foo.bar(c)no se pasa self , pero bar () lo recibe de Python. No soy un experto en Python, más o menos un principiante también. Es solo un pensamiento.
nerdoc
selfsolo se pasa implícitamente cuando se llama al método en una instancia de una clase. Por ejemplo, si tengo una clase Acon un método b(self, arg)y creo una instancia c = A(), la llamada c.b(arg)es equivalente aA.b(c, arg)
TallChuck
53

Super debería hacer el truco:

return super().bar

En Python 2.x, debe usar la sintaxis más detallada:

return super(FooBar, self).bar
Pankrat
fuente
1
TypeError: super () toma al menos 1 argumento (0 dado)
Aaron Maenpaa
4
Supongo (bueno, a juzgar por el enlace: P), esta respuesta está relacionada con python3. En python3 super () puede tomar cero argumentos, sí.
Shylent
6
super (). bar parece funcionar bien para el getter, pero no funciona para la asignación a través de la propiedad base en un setter anulado. Si hago super (). Bar = 3 obtengo AttributeError: el objeto 'super' no tiene atributo 'bar'
Rob Smallshire
1
Buen punto Rob, no lo sabía. Aquí hay más información: stackoverflow.com/questions/10810369/…
Pankrat
2
Si bien esto funciona para obtener, falla para configurar con un AttributeError.
Ethan Furman
24

Existe un uso alternativo superque no requiere hacer referencia explícita al nombre de la clase base.

Clase 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

En B, accediendo al getter de propiedad de la clase padre A:

Como otros ya han respondido, es:

super(B, self).prop

O en Python 3:

super().prop

Esto devuelve el valor devuelto por el captador de la propiedad, no el captador en sí, pero es suficiente para extender el captador.

En B, accediendo al establecedor de propiedades de la clase padre A:

La mejor recomendación que he visto hasta ahora es la siguiente:

A.prop.fset(self, value)

Creo que este es mejor:

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

En este ejemplo, ambas opciones son equivalentes pero usar super tiene la ventaja de ser independiente de las clases base de B. Si Bheredara de una Cclase que también extendiera la propiedad, no tendría que actualizar Bel código.

Código completo de B que extiende la propiedad 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)

Una advertencia:

A menos que su propiedad no tenga un setter, debe definir tanto el setter como el getter Bincluso si solo cambia el comportamiento de uno de ellos.

Maxime R.
fuente
Hola, esto es muy útil. Tengo una pregunta complementaria a esto. Esta configuración funciona bien; sin embargo, deja de funcionar cuando configuro init para que B defina propiedades adicionales. ¿Hay alguna forma de tener un init separado para B? Gracias
Cantó el
¿Podría explicar cómo super(B, self.__class__)funciona exactamente con super(class, class)? ¿Dónde está documentado?
Arte
He sugerido algunos pequeños ajustes a esta respuesta en mi respuesta
Eric
4

tratar

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

Aunque no estoy seguro de si Python admite llamar a la propiedad de la clase base. Una propiedad es en realidad un objeto invocable que se configura con la función especificada y luego reemplaza ese nombre en la clase. Esto podría significar fácilmente que no hay una superfunción disponible.

Sin embargo, siempre puede cambiar su sintaxis para usar la función 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
fuente
Esto funciona bien si escribe la clase base. Pero, ¿qué pasa si amplía una clase base de terceros que usa el mismo nombre para la propiedad y el captador?
akaihola
1

Algunas pequeñas mejoras a la respuesta de Maxime :

  • Utilizando __class__para evitar escribir B. Tenga en cuenta que self.__class__es el tipo de tiempo de ejecución de self, pero __class__ sin self el nombre de la definición de clase adjunta. super()es una abreviatura de super(__class__, self).
  • Usando en __set__lugar de fset. El último es específico de propertys, pero el primero se aplica a todos los objetos similares a propiedades (descriptores).
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
fuente
0

Puede utilizar la siguiente plantilla:

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 los métodos de propiedad deben redefinirse juntos. Si no desea redefinir todos los métodos, utilice la siguiente plantilla en su lugar:

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

(es decir, a menos que me falte algo en tu explicación)

shylent
fuente
1
usted es: él está hablando de propiedades, no de métodos simples
akaihola