Pregunta ¿Cómo funciona el decorador @property?


Me gustaría entender cómo funciona la función incorporada property trabajos. Lo que me confunde es que property también se puede usar como decorador, pero solo toma argumentos cuando se usa como función incorporada y no cuando se usa como decorador.

Este ejemplo es de documentación:

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

    def getx(self):
        return self._x
    def setx(self, value):
        self._x = value
    def delx(self):
        del self._x
    x = property(getx, setx, delx, "I'm the 'x' property.")

propertyLos argumentos de getx, setx, delx y una cadena de documentación.

En el código a continuación property se usa como decorador El objetivo es el x función, pero en el código anterior no hay lugar para una función de objeto en los argumentos.

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

Y, ¿cómo están x.setter y x.deleter decoradores creados? Estoy confundido.


660
2018-06-26 20:47


origen


Respuestas:


los property() la función devuelve un especial objeto descriptor:

>>> property()
<property object at 0x10ff07940>

Es este objeto el que tiene extra métodos:

>>> property().getter
<built-in method getter of property object at 0x10ff07998>
>>> property().setter
<built-in method setter of property object at 0x10ff07940>
>>> property().deleter
<built-in method deleter of property object at 0x10ff07998>

Estos actúan como decoradores también. Devuelven un nuevo objeto de propiedad:

>>> property().getter(None)
<property object at 0x10ff079f0>

eso es una copia del objeto viejo, pero con una de las funciones reemplazadas.

Recuerde, que el @decorator la sintaxis es solo azúcar sintáctica; la sintaxis

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

realmente significa lo mismo que

def foo(self): return self._foo
foo = property(foo)

asi que foo la función es reemplazada por property(foo), que vimos arriba es un objeto especial. Entonces cuando usas @foo.setter(), lo que estás haciendo es llamar eso property().setter método que te mostré arriba, que devuelve una nueva copia de la propiedad, pero esta vez con la función setter reemplazada con el método decorado.

La siguiente secuencia también crea una propiedad completa, utilizando esos métodos de decorador.

Primero creamos algunas funciones y property objeto con solo un getter:

>>> def getter(self): print 'Get!'
... 
>>> def setter(self, value): print 'Set to {!r}!'.format(value)
... 
>>> def deleter(self): print 'Delete!'
... 
>>> prop = property(getter)
>>> prop.fget is getter
True
>>> prop.fset is None
True
>>> prop.fdel is None
True

Luego usamos el .setter() método para agregar un colocador:

>>> prop = prop.setter(setter)
>>> prop.fget is getter
True
>>> prop.fset is setter
True
>>> prop.fdel is None
True

Por último, agregamos un eliminador con el .deleter() método:

>>> prop = prop.deleter(deleter)
>>> prop.fget is getter
True
>>> prop.fset is setter
True
>>> prop.fdel is deleter
True

Por último, pero no menos importante, property el objeto actúa como un objeto descriptor, entonces tiene .__get__(), .__set__() y .__delete__() métodos para enganchar en la instancia de obtención de atributos, configuración y eliminación:

>>> class Foo(object): pass
... 
>>> prop.__get__(Foo(), Foo)
Get!
>>> prop.__set__(Foo(), 'bar')
Set to 'bar'!
>>> prop.__delete__(Foo())
Delete!

El Descriptor Howto incluye un implementación de muestra pura python del property() tipo:

class Property(object):
    "Emulate PyProperty_Type() in Objects/descrobject.c"

    def __init__(self, fget=None, fset=None, fdel=None, doc=None):
        self.fget = fget
        self.fset = fset
        self.fdel = fdel
        if doc is None and fget is not None:
            doc = fget.__doc__
        self.__doc__ = doc

    def __get__(self, obj, objtype=None):
        if obj is None:
            return self
        if self.fget is None:
            raise AttributeError("unreadable attribute")
        return self.fget(obj)

    def __set__(self, obj, value):
        if self.fset is None:
            raise AttributeError("can't set attribute")
        self.fset(obj, value)

    def __delete__(self, obj):
        if self.fdel is None:
            raise AttributeError("can't delete attribute")
        self.fdel(obj)

    def getter(self, fget):
        return type(self)(fget, self.fset, self.fdel, self.__doc__)

    def setter(self, fset):
        return type(self)(self.fget, fset, self.fdel, self.__doc__)

    def deleter(self, fdel):
        return type(self)(self.fget, self.fset, fdel, self.__doc__)

740
2018-06-26 20:54



La documentación dice es solo un atajo para crear propiedades de solo lectura. Asi que

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

es equivalente a

def getx(self):
    return self._x
x = property(getx)

102
2018-06-26 20:52



La primera parte es simple:

@property
def x(self): ...

es lo mismo que

def x(self): ...
x = property(x)
  • que, a su vez, es la sintaxis simplificada para crear un property con solo un getter.

El siguiente paso sería ampliar esta propiedad con un setter y un eliminador. Y esto sucede con los métodos apropiados:

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

devuelve una nueva propiedad que hereda todo de la antigua x más el setter dado.

x.deleter funciona de la misma manera.


62
2018-06-26 20:53



Aquí hay un ejemplo mínimo de cómo @property puede ser implementado:

class Thing:
    def __init__(self, my_word):
        self._word = my_word 
    @property
    def word(self):
        return self._word

>>> print( Thing('ok').word )
'ok'

De otra manera word sigue siendo un método en lugar de una propiedad.

class Thing:
    def __init__(self, my_word):
        self._word = my_word
    def word(self):
        return self._word

>>> print( Thing('ok').word() )
'ok'

48
2018-02-15 00:46



Esto siguiente:

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

Es lo mismo que:

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

    def _x_get(self):
        return self._x

    def _x_set(self, value):
        self._x = value

    def _x_del(self):
        del self._x

    x = property(_x_get, _x_set, _x_del, 
                    "I'm the 'x' property.")

Es lo mismo que:

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

    def _x_get(self):
        return self._x

    def _x_set(self, value):
        self._x = value

    def _x_del(self):
        del self._x

    x = property(_x_get, doc="I'm the 'x' property.")
    x = x.setter(_x_set)
    x = x.deleter(_x_del)

Es lo mismo que:

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

    def _x_get(self):
        return self._x
    x = property(_x_get, doc="I'm the 'x' property.")

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

    def _x_del(self):
        del self._x
    x = x.deleter(_x_del)

Que es lo mismo que:

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

24
2018-05-24 18:38



Leí todas las publicaciones aquí y me di cuenta de que podríamos necesitar un ejemplo de la vida real. ¿Por qué, en realidad, tenemos @property? Por lo tanto, considere una aplicación Flask donde utiliza el sistema de autenticación. Usted declara un usuario modelo en models.py:

class User(UserMixin, db.Model):
    __tablename__ = 'users'
    id = db.Column(db.Integer, primary_key=True)
    email = db.Column(db.String(64), unique=True, index=True)
    username = db.Column(db.String(64), unique=True, index=True)
    password_hash = db.Column(db.String(128))

    ...

    @property
    def password(self):
        raise AttributeError('password is not a readable attribute')

    @password.setter
    def password(self, password):
        self.password_hash = generate_password_hash(password)

    def verify_password(self, password):
        return check_password_hash(self.password_hash, password)

En este código, tenemos el atributo "oculto" passwordmediante el uso @property que desencadena AttributeError aserción cuando intenta acceder directamente, mientras utilizamos @ property.setter para establecer la variable de instancia real password_hash.

Ahora en auth/views.py podemos instanciar un usuario con:

...
@auth.route('/register', methods=['GET', 'POST'])
def register():
    form = RegisterForm()
    if form.validate_on_submit():
        user = User(email=form.email.data,
                    username=form.username.data,
                    password=form.password.data)
        db.session.add(user)
        db.session.commit()
...

Aviso de atributo password que proviene de un formulario de registro cuando un usuario llena el formulario. La confirmación de la contraseña ocurre en la parte delantera con EqualTo('password', message='Passwords must match') (en caso de que se lo esté preguntando, pero se trata de un tema diferente relacionado con los formularios de Flask).

Espero que este ejemplo sea útil


4
2018-03-23 14:47



Una propiedad se puede declarar de dos maneras.

  • Crear los métodos getter, setter para un atributo y luego pasarlos como argumento para propiedad función
  • Utilizando el @propiedad decorador.

Puedes echar un vistazo a algunos ejemplos sobre los que he escrito propiedades en python.


1
2017-07-13 09:20