¿Cuál es la diferencia entre una "propiedad" y un "atributo" de Python?

146

Generalmente estoy confundido acerca de la diferencia entre una "propiedad" y un "atributo", y no puedo encontrar un gran recurso para detallar de manera concisa las diferencias.

Carson
fuente

Respuestas:

184

Las propiedades son un tipo especial de atributo. Básicamente, cuando Python encuentra el siguiente código:

spam = SomeObject()
print(spam.eggs)

que mira hacia arriba eggsen spam, y luego se examina eggspara ver si tiene una __get__, __set__o __delete__método - si lo hace, es una propiedad. Si es una propiedad, en lugar de simplemente devolver el eggsobjeto (como lo haría con cualquier otro atributo), llamará al __get__método (ya que estábamos haciendo una búsqueda) y devolverá lo que ese método devuelva.

Más información sobre el modelo de datos y descriptores de Python .

Ethan Furman
fuente
Parece que las propiedades implican un procesamiento adicional para acceder al valor objetivo ... ¿sabe cuán significativo / mucho más lento es?
Martineau
@martineau: Bueno, hay una llamada de función adicional, pero lo más probable es que el trabajo y el tiempo adicionales dependan de cuánto esté haciendo la propiedad (consejo general: hacer / no / usar propiedades para ocultar E / S).
Ethan Furman el
56

Con una propiedad, usted tiene control completo sobre sus métodos getter, setter y deleter, que no tiene (si no usa advertencias) con un atributo.

class A(object):
    _x = 0
    '''A._x is an attribute'''

    @property
    def x(self):
        '''
        A.x is a property
        This is the getter method
        '''
        return self._x

    @x.setter
    def x(self, value):
        """
        This is the setter method
        where I can check it's not assigned a value < 0
        """
        if value < 0:
            raise ValueError("Must be >= 0")
        self._x = value

>>> a = A()
>>> a._x = -1
>>> a.x = -1
Traceback (most recent call last):
  File "ex.py", line 15, in <module>
    a.x = -1
  File "ex.py", line 9, in x
    raise ValueError("Must be >= 0")
ValueError: Must be >= 0
neurino
fuente
Sin embargo, este ("control completo") también se puede hacer con atributos de "no propiedad", solo que sin decoradores tan simples.
8
Me gusta que esta respuesta proporcione un ejemplo realista y útil. Siento que demasiadas respuestas en este sitio explican innecesariamente cómo funcionan las cosas en el back-end sin aclarar cómo el usuario debe interactuar con ellas. Si uno no entiende por qué / cuándo usar alguna funcionalidad, no tiene sentido saber cómo funciona detrás de escena.
Tom
Esta respuesta viola el "Zen de Python: debe haber una, y preferiblemente solo una, forma obvia de hacerlo". Hay 2 formas de establecer el valor x.
Tin Luu
@TinLuu: solo hay una forma de establecer el valor de x. También hay una sola forma de establecer el valor de _x. El hecho de que sean lo mismo es un detalle de implementación. El usuario inteligente de esta clase no usa _x.
encendido
1
@TinLuu - Creo que los dos estamos diciendo lo mismo desde los extremos opuestos de la perspectiva. El usuario de la clase solo debería ver x. De una sola mano. Si el usuario de la clase se entera de _x, lo usa bajo su propio riesgo.
encendido
19

En términos generales, una propiedad y un atributo son lo mismo. Sin embargo, hay un decorador de propiedades en Python que proporciona acceso getter / setter a un atributo (u otros datos).

class MyObject(object):
    # This is a normal attribute
    foo = 1

    @property
    def bar(self):
        return self.foo

    @bar.setter
    def bar(self, value):
        self.foo = value


obj = MyObject()
assert obj.foo == 1
assert obj.bar == obj.foo
obj.bar = 2
assert obj.foo == 2
assert obj.bar == obj.foo
seis8
fuente
1
¿podría mencionar también el resultado esperado de este código?
Hasan Iqbal
2
¿Qué quieres decir? ¿No es eso al final del código?
six8
12

La propiedad le permite obtener y establecer valores como lo haría con los atributos normales, pero debajo hay un método que se llama traducirlo en un getter y setter para usted. En realidad, es solo una conveniencia reducir el límite de llamadas de getters y setters.

Digamos, por ejemplo, que tenía una clase que contenía algunas coordenadas x e y para algo que necesitaba. Para configurarlos es posible que desee hacer algo como:

myObj.x = 5
myObj.y = 10

Es mucho más fácil mirar y pensar que escribir:

myObj.setX(5)
myObj.setY(10)

El problema es, ¿qué pasa si un día su clase cambia de tal manera que necesita compensar su x e y por algún valor? Ahora necesitaría entrar y cambiar su definición de clase y todo el código que lo llama, lo que podría llevar mucho tiempo y ser propenso a errores. La propiedad le permite usar la sintaxis anterior mientras le brinda la flexibilidad de cambio de la segunda.

En Python, puede definir captadores, definidores y métodos de eliminación con la función de propiedad. Si solo desea la propiedad de lectura, también hay un decorador @property que puede agregar sobre su método.

http://docs.python.org/library/functions.html#property

falcojr
fuente
¡Solo responda eso tiene sentido práctico!
Amigo156
8

Aprendí 2 diferencias del sitio de Bernd Klein, en resumen:

1. La propiedad es una forma más conveniente de encapsular datos.

ej .: si tiene una longitud de atributo público de Object, más adelante, su proyecto requiere que lo encapsule, es decir: cámbielo a privado y proporcione getter y setter => debe cambiar muchos de los códigos que escribió antes:

#Old codes
obj1.length=obj1.length+obj2.length
#New codes(Using private attibutes and getter and setter)
obj1.set_lenght(obj1.get_length()+obj2.get_length()) #=> this is ugly

Si usa @property y @ lenght.setter => no necesita cambiar esos códigos antiguos

2. Una propiedad puede encapsular múltiples atributos

class Person:
  def __init__(self, name, physic_health, mental_health):
    self.name=name
    self.__physic_health=physic_health #physic_health is real value in range [0, 5.0]
    self.__mental_health=mental_health #mental_health is real value in range [0, 5.0]
  @property
  def condition(self):
    health=self.__physic_health+self.__mental_health
    if(health<5.0):
      return "I feel bad!"
    elif health<8.0:
      return "I am ok!"
    else:
      return "Great!"

En este ejemplo, __physic_healthy __mental_healthson privados y no se puede acceder directamente desde afuera, la única forma en que la clase externa interactúa con ellos es a través de la propiedadcondition

Tin Luu
fuente
8

También hay una diferencia no obvia que uso para almacenar en caché o actualizar datos, a menudo tenemos una función conectada al atributo de clase. Por ejemplo, necesito leer el archivo una vez y mantener el contenido asignado al atributo para que el valor se almacene en caché:

class Misc():
        def __init__(self):
            self.test = self.test_func()

        def test_func(self):
            print 'func running'
            return 'func value'

cl = Misc()
print cl.test
print cl.test

Salida:

func running
func value
func value

Accedimos al atributo dos veces, pero nuestra función se activó solo una vez. Cambiar el ejemplo anterior para usar la propiedad hará que el valor del atributo se actualice cada vez que acceda a él:

class Misc():

    @property
    def test(self):
        print 'func running'
        return 'func value'

cl = Misc()
print cl.test
print cl.test

Salida:

func running
func value
func running
func value
brc
fuente