Por lo tanto, estoy tratando de descubrir la mejor manera (la más elegante con la menor cantidad de código) para permitir anular funciones específicas de una propiedad (por ejemplo, solo el captador, solo el definidor, etc.) en Python. Soy fanático de la siguiente forma de hacer propiedades, debido al hecho de que todos sus métodos están encapsulados en el mismo bloque de código sangrado (es más fácil ver dónde se detienen las funciones que tratan con una propiedad y las funciones que tratan con el siguiente comienzo):

@apply
def foo():
    """A foobar"""
    def fget(self):
        return self._foo
    def fset(self, val):
        self._foo = val
    return property(**locals())

Sin embargo, si quiero heredar de una clase que define propiedades de esta manera, y luego, por ejemplo, anular la función de establecimiento foo, parece complicado. He realizado algunas búsquedas y la mayoría de las respuestas que he encontrado han sido definir funciones separadas en la clase base (por ejemplo, getFoo y setFoo), crear explícitamente una definición de propiedad a partir de ellas (por ejemplo, { {X3}}), y luego anula getFoo, setFoo y delFoo según sea necesario.

No me gusta esta solución porque significa que tengo que definir lambas para cada propiedad individual y luego escribir cada llamada de función (cuando antes podría haber hecho property(**locals())). Tampoco obtengo la encapsulación que tenía originalmente.

Idealmente, lo que me gustaría poder hacer sería algo como esto:

class A(object):
    def __init__(self):
        self.foo = 8
    @apply
    def foo():
        """A foobar"""
        def fget(self):
            return self._foo
        def fset(self, val):
            self._foo = val
        return property(**locals())

class ATimesTwo(A):
    @some_decorator
    def foo():
        def fset(self, val):
            self._foo = val * 2
        return something

Y luego la salida se vería así:

>>> a = A()
>>> a.foo
8
>>> b = ATimesTwo()
>>> b.foo
16

Básicamente, ATimesTwo hereda la función getter de A pero anula la función setter. ¿Alguien sabe de una manera de hacer esto (de una manera similar al ejemplo anterior)? ¿Cómo se vería la función some_decorator y cuál debería ser la función foo?

53
Jessica Hamrick 11 ago. 2011 a las 04:48

3 respuestas

La mejor respuesta

Estoy seguro de que has escuchado esto antes, pero apply ha quedado en desuso durante ocho años , desde Python 2.3. No lo uses El uso de locals() también es contrario al Zen de Python: explícito es mejor que implícito. Si realmente le gusta la sangría aumentada, no hay necesidad de crear un objeto desechable, solo hágalo

if True:
    @property
    def foo(self):
        return self._foo
    @foo.setter
    def foo(self, val):
        self._foo = val

Lo que no abusa de locals, usa apply, requiere la creación de un objeto adicional o necesita una línea posterior con foo = foo(), lo que dificulta ver el final del bloque. Funciona igual de bien para su forma tradicional de usar property, simplemente haga foo = property(fget, fset) de la forma habitual.

Si desea anular una propiedad en una subclase arbitraria, puede usar un receta como esta.

Si la subclase sabe dónde se definió la propiedad, simplemente haga lo siguiente:

class ATimesTwo(A):
    @A.foo.setter
    def foo(self, val):
        self._foo = val * 2
48
agf 11 ago. 2011 a las 03:10

La respuesta de stderr satisface la mayoría de los casos de uso.

Me gustaría agregar una solución para el caso en el que desea extender un getter, setter y / o deleter. Dos formas de hacer esto son:

1. Subclase property

La primera forma de hacerlo es subclasificando el property incorporado y agregando decoradores que son versiones de getter, setter y / o deleter que extienden el actual obtener, establecer y eliminar devoluciones de llamada

Ejemplo de una propiedad que admite métodos anexados a las funciones de conjunto:

class ExtendableProperty(property):
    def append_setter(self, fset):
        # Create a wrapper around the new fset that also calls the current fset
        _old_fset = self.fset

        def _appended_setter(obj, value):
            _old_fset(obj, value)
            fset(obj, value)
        # Use that wrapper as setter instead of only the new fset
        return self.setter(_appended_setter)

El uso es el mismo que para las propiedades normales, solo que ahora es posible agregar métodos a los establecedores de propiedades:

class A(object):
    @ExtendableProperty
    def prop(self):
        return self._prop

    @prop.setter
    def prop(self, v):
        self._prop = v


class B(A):
    @A.prop.append_setter
    def prop(self, v):
        print('Set', v)
>>> a = A()
>>> a.prop = 1
>>> a.prop
1

>>> b = B()
>>> b.prop = 1
Set 1
>>> b.prop
1

2. Sobrescribir getter, setter y / o deleter

Use una propiedad normal, sobrescriba el getter, setter o deleter y luego agregue llamadas a fget, fset o fdel en la propiedad de la clase padre.

Ejemplo para el tipo de propiedad como en el ejemplo 1:

class A(object):
    @property
    def prop(self):
        return self._prop

    @prop.setter
    def prop(self, v):
        self._prop = v


class B(A):
    @A.prop.setter
    def prop(self, v):
        A.prop.fset(self, v)  # This is the call to the original set method
        print('Set {}'.format(v))

Creo que la primera opción se ve mejor porque la llamada al fset de la súper propiedad no es necesaria

2
jusx 20 ene. 2020 a las 13:54

Python docs en el decorador property sugiere el siguiente modismo :

class C(object):
    def __init__(self):
        self._x = None
    @property
    def x(self):
        return self._x
    @x.setter
    def x(self, value):
        self._x = value
    @x.deleter
    def x(self):
        del self._x

Y luego las subclases pueden anular un solo setter / getter como este:

class C2(C):
    @C.x.getter
    def x(self):
        return self._x * -1

Esto es un poco vergonzoso porque anular varios métodos parece requerir que hagas algo como:

class C3(C):
    @C.x.getter
    def x(self):
        return self._x * -1
    # C3 now has an x property with a modified getter
    # so modify its setter rather than C.x's setter.
    @x.setter 
    def x(self, value):
        self._x = value * 2

Por supuesto, en el momento en que anule getter, setter y deleter, probablemente pueda redefinir la propiedad para C3.

53
stderr 13 feb. 2017 a las 23:50