¿Ejemplo del mundo real sobre cómo usar la característica de propiedad en python?

142

Estoy interesado en cómo usarlo @propertyen Python. He leído los documentos de Python y el ejemplo allí, en mi opinión, es solo un código de juguete:

class C(object):
    def __init__(self):
        self._x = None

    @property
    def x(self):
        """I'm the 'x' property."""
        return self._x

    @x.setter
    def x(self, value):
        self._x = value

    @x.deleter
    def x(self):
        del self._x

No sé qué beneficio (s) puedo obtener al envolver el _xrelleno con el decorador de propiedades. ¿Por qué no simplemente implementar como:

class C(object):
    def __init__(self):
        self.x = None

Creo que la característica de propiedad podría ser útil en algunas situaciones. ¿Pero cuando? ¿Podría alguien darme algunos ejemplos del mundo real?

Gracias.

xiao 啸
fuente
9
Esta es la mejor y más limpia explicación que he encontrado sobre el decorador de propiedades [haga clic aquí ]
Sumudu
2
@Anubis en el último ejemplo en el enlace que proporcionó, la configuración de c = Celsius (-500) no arrojó ningún ValueError, que creo que no está logrando el resultado deseado.
Sajuuk
De acuerdo con @Anubis. Se implementa correctamente aquí: python-course.eu/python3_properties.php
anon01 el

Respuestas:

91

Otros ejemplos serían la validación / filtrado de los atributos establecidos (forzándolos a estar dentro de límites o aceptables) y la evaluación diferida de términos complejos o que cambian rápidamente.

Cálculo complejo oculto detrás de un atributo:

class PDB_Calculator(object):
    ...
    @property
    def protein_folding_angle(self):
        # number crunching, remote server calls, etc
        # all results in an angle set in 'some_angle'
        # It could also reference a cache, remote or otherwise,
        # that holds the latest value for this angle
        return some_angle

>>> f = PDB_Calculator()
>>> angle = f.protein_folding_angle
>>> angle
44.33276

Validación:

class Pedometer(object)
    ...
    @property
    def stride_length(self):
        return self._stride_length

    @stride_length.setter
    def stride_length(self, value):
        if value > 10:
            raise ValueError("This pedometer is based on the human stride - a stride length above 10m is not supported")
        else:
            self._stride_length = value
benosteen
fuente
1
Me gusta el ejemplo PDB_Calculator: las cosas complicadas se abstraen, todo funciona y el usuario puede disfrutar de la simplicidad.
Adam Kurkiewicz
2
posiblemente, desde el punto de vista profesional, estos son muy buenos ejemplos. Pero, como novato, encuentro estos ejemplos bastante ineficaces. my bad ... :(
kmonsoor
77

Un caso de uso simple será establecer un atributo de instancia de solo lectura, ya que sabe que llevar un nombre de variable con un guión bajo _xen Python generalmente significa que es privado (uso interno), pero a veces queremos poder leer el atributo de instancia y no escribir es así que se puede utilizar propertypara esto:

>>> class C(object):

        def __init__(self, x):
            self._x = x

        @property
        def x(self):
            return self._x

>>> c = C(1)
>>> c.x
1
>>> c.x = 2
AttributeError        Traceback (most recent call last)

AttributeError: can't set attribute
mouad
fuente
66
Todavía se puede configurar c._x, si el usuario lo desea. Python en realidad no tiene atributos privados reales.
20

Eche un vistazo a este artículo para un uso muy práctico. En resumen, explica cómo en Python generalmente puede deshacerse del método getter / setter explícito, ya que si llega a necesitarlos en algún momento, puede usarlos propertypara una implementación perfecta.

Eli Bendersky
fuente
15

Una cosa para la que lo he usado es el almacenamiento en caché de valores lentos para buscar, pero inmutables, almacenados en una base de datos. Esto se generaliza a cualquier situación en la que sus atributos requieran cómputo u otra operación larga (por ejemplo, verificación de la base de datos, comunicación de red) que solo desea hacer a pedido.

class Model(object):

  def get_a(self):
    if not hasattr(self, "_a"):
      self._a = self.db.lookup("a")
    return self._a

  a = property(get_a)

Esto fue en una aplicación web donde cualquier vista de página dada solo podría necesitar un atributo particular de este tipo, pero los objetos subyacentes podrían tener varios de estos atributos: inicializarlos todos en la construcción sería un desperdicio, y las propiedades me permiten ser flexible en lo que los atributos son perezosos y cuáles no.

desviarse
fuente
1
¿No puedes usar @cached_propertypara esto?
firme
@adarsh ​​- Suena interesante. ¿Donde es eso?
detly
Lo he estado usando, pero olvidé que no estaba integrado, pero puedes usarlo con esto, pypi.python.org/pypi/cached-property/0.1.5
hasta el
2
Interesante. Creo que se publicó por primera vez después de esta respuesta, pero cualquiera que lea esto probablemente debería usarlo en su lugar.
detly
10

Leyendo las respuestas y comentarios, el tema principal parece ser que las respuestas parecen estar perdiendo un ejemplo simple pero útil. He incluido uno muy simple aquí que demuestra el uso simple del @propertydecorador. Es una clase que permite al usuario especificar y obtener mediciones de distancia utilizando una variedad de unidades diferentes, es decir, in_feeto in_metres.

class Distance(object):
    def __init__(self):
        # This private attribute will store the distance in metres
        # All units provided using setters will be converted before
        # being stored
        self._distance = 0.0

    @property
    def in_metres(self):
        return self._distance

    @in_metres.setter
    def in_metres(self, val):
        try:
            self._distance = float(val)
        except:
            raise ValueError("The input you have provided is not recognised "
                             "as a valid number")

    @property
    def in_feet(self):
        return self._distance * 3.2808399

    @in_feet.setter
    def in_feet(self, val):
        try:
            self._distance = float(val) / 3.2808399
        except:
            raise ValueError("The input you have provided is not recognised "
                             "as a valid number")

    @property
    def in_parsecs(self):
        return self._distance * 3.24078e-17

    @in_parsecs.setter
    def in_parsecs(self, val):
        try:
            self._distance = float(val) / 3.24078e-17
        except:
            raise ValueError("The input you have provided is not recognised "
                             "as a valid number")

Uso:

>>> distance = Distance()
>>> distance.in_metres = 1000.0
>>> distance.in_metres
1000.0
>>> distance.in_feet
3280.8399
>>> distance.in_parsecs
3.24078e-14
Miguel
fuente
Para mí personalmente, los mejores ejemplos de getters / setters son mostrarle a la gente el tipo de cambios que necesita hacer más adelante, pero obviamente, eso lleva un poco más de tiempo.
dtc
7

La propiedad es solo una abstracción en torno a un campo que le brinda más control sobre las formas en que un campo específico puede ser manipulado y hacer cálculos de middleware. Pocos de los usos que vienen a la mente son la validación y la inicialización previa y la restricción de acceso

@property
def x(self):
    """I'm the 'x' property."""
    if self._x is None:
        self._x = Foo()

    return self._x
Stanislav Ageev
fuente
6

Sí, para el ejemplo original publicado, la propiedad funcionará exactamente igual que simplemente tener una variable de instancia 'x'.

Esto es lo mejor de las propiedades de Python. ¡Desde el exterior, funcionan exactamente como variables de instancia! Lo que le permite usar variables de instancia fuera de la clase.

Esto significa que su primer ejemplo podría usar una variable de instancia. Si las cosas cambiaron, y luego decide cambiar su implementación y una propiedad es útil, la interfaz de la propiedad seguirá siendo la misma desde el código fuera de la clase. Un cambio de variable de instancia a propiedad no tiene impacto en el código fuera de la clase.

Muchos otros lenguajes y cursos de programación darán instrucciones de que un programador nunca debe exponer variables de instancia, y en su lugar usar 'getters' y 'setters' para acceder a cualquier valor desde fuera de la clase, incluso el caso simple como se cita en la pregunta.

Código fuera de la clase con muchos idiomas (por ejemplo, Java)

object.get_i()
    #and
object.set_i(value)

#in place of (with python)
object.i
    #and 
object.i = value

Y al implementar la clase, hay muchos 'captadores' y 'establecedores' que hacen exactamente como su primer ejemplo: replicar una variable de instancia simple. Estos captadores y establecedores son necesarios porque si la implementación de la clase cambia, todo el código fuera de la clase deberá cambiar. Pero las propiedades de Python permiten que el código fuera de la clase sea el mismo que con las variables de instancia. Por lo tanto, no es necesario cambiar el código fuera de la clase si agrega una propiedad o si tiene una variable de instancia simple. Entonces, a diferencia de la mayoría de los lenguajes orientados a objetos, para su ejemplo simple, puede usar la variable de instancia en lugar de 'getters' y 'setters' que realmente no son necesarios, tenga la certeza de que si cambia a una propiedad en el futuro, el código usa Tu clase no necesita cambiar.

Esto significa que solo necesita crear propiedades si hay un comportamiento complejo, y para el caso simple muy común en el que, como se describe en la pregunta, una variable de instancia simple es todo lo que se necesita, simplemente puede usar la variable de instancia.

innov8
fuente
6

Otra buena característica de las propiedades sobre el uso de setters y getters es que le permiten continuar usando operadores OP = (por ejemplo, + =, - =, * = etc.) en sus atributos mientras conserva cualquier validación, control de acceso, almacenamiento en caché, etc. los acomodadores y captadores proporcionarían.

por ejemplo, si escribiste la clase Personcon un setter setage(newage)y un getter getage(), para aumentar la edad tendrías que escribir:

bob = Person('Robert', 25)
bob.setage(bob.getage() + 1)

pero si hiciste ageuna propiedad, podrías escribir el limpiador mucho más:

bob.age += 1
quizdog
fuente
5

La respuesta corta a su pregunta es que, en su ejemplo, no hay beneficio. Probablemente debería usar el formulario que no involucra propiedades.

La razón por la que existen las propiedades es que si su código cambia en el futuro y de repente necesita hacer más con sus datos: valores de caché, proteger el acceso, consultar algún recurso externo ... lo que sea, puede modificar fácilmente su clase para agregar captadores y configuradores para los datos sin cambiar la interfaz, por lo que no tiene que buscar en todas partes en su código donde se accede a esos datos y cambiar eso también.

SpoonMeiser
fuente
4

Algo que muchos no notan al principio es que puede crear sus propias subclases de propiedad. Esto me ha resultado muy útil para exponer atributos de objetos de solo lectura o atributos que puede leer y escribir pero no eliminar. También es una excelente manera de ajustar la funcionalidad, como el seguimiento de modificaciones a los campos de objetos.

class reader(property):
    def __init__(self, varname):
        _reader = lambda obj: getattr(obj, varname)
        super(reader, self).__init__(_reader)

class accessor(property):
    def __init__(self, varname, set_validation=None):
        _reader = lambda obj: getattr(obj, varname)
        def _writer(obj, value):
            if set_validation is not None:
               if set_validation(value):
                  setattr(obj, varname, value)
        super(accessor, self).__init__(_reader, _writer)

#example
class MyClass(object):
   def __init__(self):
     self._attr = None

   attr = reader('_attr')

fuente
Me gusta esto. ¿Estoy leyendo esto correctamente en que el lector es de solo lectura mientras el descriptor de acceso es de lectura / escritura sin capacidad de eliminación? Sin embargo, ¿cómo agregaría la validación de datos? Soy bastante nuevo en Python, pero creo que probablemente haya una forma de agregar una devolución de llamada a la attr = reader('_attr')línea o alguna forma de verificación previa attr = if self.__isValid(value): reader('_attr'). Sugerencias?
Gabe Spradlin
Lo siento, me di cuenta de que estaba preguntando sobre la validación de datos para una variable de solo lectura. Pero, obviamente, esto solo se aplicaría a la parte de establecimiento de la clase de acceso. Así que cambia attr = reader('_attr')a attr = accessor('_attr'). Gracias
Gabe Spradlin
Tiene razón en que si desea la validación, entonces agregaría una función para validar y aumentar la excepción si no es válida (o cualquier comportamiento que le haya gustado, incluyendo no hacer nada) al init . Modifiqué lo anterior con un posible patrón. El validador debe devolver True | False para guiar si el conjunto ocurre o no.