¿Cómo funciona el decorador @ property?


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

Este ejemplo es de la 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.")

property's argumentos son getx, setx, delx y una cuerda de doc.

En el siguiente código property se utiliza como decorador. El objeto de la misma es la función x, pero en el código arriba no hay lugar para una función object 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 se crean los x.setter y x.deleter decoradores? Estoy confundido.

Author: Martijn Pieters, 2013-06-27

10 answers

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

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

Es este objeto el que tiene métodos extra :

>>> 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>

Es una copia del objeto antiguo, pero con una de las funciones reemplazada.

Recuerde, que la sintaxis @decorator 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)

So foo la función es reemplazada por property(foo), que vimos anteriormente es un objeto especial. Luego, cuando usa @foo.setter(), lo que está haciendo es llamar al método property().setter que le mostré anteriormente, que devuelve una nueva copia de la propiedad, pero esta vez con la función setter reemplazada por el método decorado.

La siguiente secuencia también crea una propiedad full-on, usando esos métodos decoradores.

Primero creamos algunas funciones y un objeto property con 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 método .setter() para agregar un setter:

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

Por último añadimos un deleter con el método .deleter():

>>> 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, el objeto property actúa como un objeto descriptor , por lo que tiene .__get__(), .__set__() y .__delete__() métodos para enganchar en la obtención, configuración y eliminación de atributos de instancia:

>>> 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 pure python sample implementation 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__)
 758
Author: Martijn Pieters,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-07-15 19:05:08

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

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

Es equivalente a

def getx(self):
    return self._x
x = property(getx)
 106
Author: J0HN,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-07-15 20:45:42

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 extender esta propiedad con un setter y un deleter. Y esto sucede con los métodos apropiados:

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

Devuelve una nueva propiedad que hereda todo del antiguo x más el setter dado.

x.deleter funciona de la misma manera.

 64
Author: glglgl,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2013-06-26 20:53:15

Aquí hay un ejemplo mínimo de cómo @property se puede implementar:

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

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

De lo contrario 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'
 54
Author: AlexG,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-02-15 00:46:29

Lo 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
 25
Author: Bill Moore,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-05-24 18:38:10

He leído todos los mensajes aquí y me di cuenta de que podemos necesitar un ejemplo de la vida real, ¿Por qué, en realidad, tenemos @propiedad? Por lo tanto, considere una aplicación de frasco donde se utiliza el sistema de autenticación. Usted declara un Usuario de 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 hemos "oculto" atributo password mediante el uso de @property que desencadena AttributeError aserción cuando intenta acceder directamente, mientras que utilizamos @propiedad.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()
...

Notice attribute password que proviene de un formulario de registro cuando un usuario llena el formulario. La confirmación de la contraseña ocurre en el front-end con EqualTo('password', message='Passwords must match') (en caso de que se lo esté preguntando, pero es un tema diferente relacionado con los formularios del frasco).

Espero que este ejemplo sea útil

 6
Author: Leo Skhrnkv,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2018-03-23 14:47:53

Empecemos con los decoradores Python.

Un decorador Python es una función que ayuda a agregar algunas funcionalidades adicionales a una función ya definida.

En Python todo es un objeto, En Python todo es un objeto. Las funciones en Python son objetos de primera clase, lo que significa que pueden ser referenciadas por una variable, agregadas en las listas, pasadas como argumentos a otra función, etc.

Considere el siguiente fragmento de código.

def decorator_func(fun):
    def wrapper_func():
        print("Wrapper function started")
        fun()
        print("Given function decorated")
        # Wrapper function add something to the passed function and decorator 
        # returns the wrapper function
    return wrapper_func

def say_bye():
    print("bye!!")

say_bye = decorator_func(say_bye)
say_bye()

# Output:
#  Wrapper function started
#  bye
#  Given function decorated

Aquí, podemos decir que la función decorator modificó nuestra función say_hello y agregó algunas líneas adicionales de código en ella.

Sintaxis de Python para decorador

def decorator_func(fun):
    def wrapper_func():
        print("Wrapper function started")
        fun()
        print("Given function decorated")
        # Wrapper function add something to the passed function and decorator 
        # returns the wrapper function
    return wrapper_func

@decorator_func
def say_bye():
    print("bye!!")

say_bye()

Vamos a concluir todo que con un escenario de caso, pero antes de eso vamos a hablar de algunos priniciples oops.

Los getters y setters se utilizan en muchos lenguajes de programación orientados a objetos para garantizar el principio de encapsulación de datos (se ve como la agrupación de datos con los métodos que operan en estos datos.)

Estos métodos son, por supuesto, el getter para recuperar los datos y el setter para cambiar los datos.

De acuerdo con este principio, los atributos de una clase se hacen privados para ocultarlos y protegerlos de otro código.

Sí, @property es básicamente una forma pitónica de usar getters y setters.

Python tiene un gran concepto llamado propiedad que hace que la vida de un programador orientado a objetos sea mucho más simple.

Let nosotros asumimos que usted decide hacer una clase que podría almacenar la temperatura en grados Celsius.

class Celsius:
def __init__(self, temperature = 0):
    self.set_temperature(temperature)

def to_fahrenheit(self):
    return (self.get_temperature() * 1.8) + 32

def get_temperature(self):
    return self._temperature

def set_temperature(self, value):
    if value < -273:
        raise ValueError("Temperature below -273 is not possible")
    self._temperature = value

Código refactorizado, Así es como podríamos haberlo logrado con la propiedad.

En Python, property() es una función integrada que crea y devuelve un objeto property.

Un objeto de propiedad tiene tres métodos, getter(), setter () y delete().

class Celsius:
def __init__(self, temperature = 0):
    self.temperature = temperature

def to_fahrenheit(self):
    return (self.temperature * 1.8) + 32

def get_temperature(self):
    print("Getting value")
    return self.temperature

def set_temperature(self, value):
    if value < -273:
        raise ValueError("Temperature below -273 is not possible")
    print("Setting value")
    self.temperature = value

temperature = property(get_temperature,set_temperature)

Aquí,

temperature = property(get_temperature,set_temperature)

Podría haberse desglosado como,

# make empty property
temperature = property()
# assign fget
temperature = temperature.getter(get_temperature)
# assign fset
temperature = temperature.setter(set_temperature)

Apuntar A Nota:

  • get_temperature sigue siendo una propiedad en lugar de un método.

Ahora puede acceder al valor de temperatura escribiendo.

C = Celsius()
C.temperature
# instead of writing C.get_temperature()

Podemos seguir adelante y no definir nombres get_temperature y set_temperature ya que son innecesarios y contaminan el espacio de nombres de la clase.

La forma pitónica para tratar el problema anterior es usar @property.

class Celsius:
    def __init__(self, temperature = 0):
        self.temperature = temperature

    def to_fahrenheit(self):
        return (self.temperature * 1.8) + 32

    @property
    def temperature(self):
        print("Getting value")
        return self.temperature

    @temperature.setter
    def temperature(self, value):
        if value < -273:
            raise ValueError("Temperature below -273 is not possible")
        print("Setting value")
        self.temperature = value

Puntos a tener en cuenta -

  1. Un método que se utiliza para obtener un valor está decorado con "@property".
  2. El método que tiene que funcionar como el setter está decorado con "@temperature.setter", si la función se hubiera llamado "x", tendríamos que decorarla con"@x.setter".
  3. Escribimos "dos" métodos con el mismo nombre y un número diferente de parámetros "def temperature(self)" y "def temperature(self,x)".

Como puedes ver, el código es definitivamente menos elegante.

Ahora,hablemos de un escenario práctico de la vida real.

Digamos que has diseñado una clase de la siguiente manera:

class OurClass:

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


y = OurClass(10)
print(y.x)

Ahora, supongamos además que nuestra clase se hizo popular entre los clientes y comenzaron a usarla en sus programas, hicieron todo tipo de tareas al objeto.

Y un fatídico día, un cliente de confianza vino a nosotros y sugirió que " x " tiene que ser un valor entre 0 y 1000, esto es realmente un escenario horrible!

Debido a propiedades es fácil: Creamos una versión de propiedad de "x".

class OurClass:

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

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

    @x.setter
    def x(self, x):
        if x < 0:
            self.__x = 0
        elif x > 1000:
            self.__x = 1000
        else:
            self.__x = x

Esto es genial, ¿no?: Puede comenzar con la implementación más simple imaginable, y puede migrar más tarde a una versión de propiedad sin tener que cambiar la interfaz. Así que las propiedades no son solo un reemplazo para los captadores y setter!

Puede comprobar esta Implementación aquí

 1
Author: Divyanshu Rawat,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2018-09-17 00:01:12

Una propiedad se puede declarar de dos maneras.

  • Crear los métodos getter, setter para un atributo y luego pasarlos como argumento a property function
  • Usando el decorador @property.

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

 0
Author: nvd,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-07-13 09:20:51

Aquí hay otro ejemplo:

##
## Python Properties Example
##
class GetterSetterExample( object ):
    ## Set the default value for x ( we reference it using self.x, set a value using self.x = value )
    __x = None


##
## On Class Initialization - do something... if we want..
##
def __init__( self ):
    ## Set a value to __x through the getter / setter... Since __x is defined above, this doesn't need to be set...
    self.x = 1234

    return None


##
## Define x as a property, ie a getter - All getters should have a default value arg, so I added it - it will not be passed in when setting a value, so you need to set the default here so it will be used..
##
@property
def x( self, _default = None ):
    ## I added an optional default value argument as all getters should have this - set it to the default value you want to return...
    _value = ( self.__x, _default )[ self.__x == None ]

    ## Debugging - so you can see the order the calls are made...
    print( '[ Test Class ] Get x = ' + str( _value ) )

    ## Return the value - we are a getter afterall...
    return _value


##
## Define the setter function for x...
##
@x.setter
def x( self, _value = None ):
    ## Debugging - so you can see the order the calls are made...
    print( '[ Test Class ] Set x = ' + str( _value ) )

    ## This is to show the setter function works.... If the value is above 0, set it to a negative value... otherwise keep it as is ( 0 is the only non-negative number, it can't be negative or positive anyway )
    if ( _value > 0 ):
        self.__x = -_value
    else:
        self.__x = _value


##
## Define the deleter function for x...
##
@x.deleter
def x( self ):
    ## Unload the assignment / data for x
    if ( self.__x != None ):
        del self.__x


##
## To String / Output Function for the class - this will show the property value for each property we add...
##
def __str__( self ):
    ## Output the x property data...
    print( '[ x ] ' + str( self.x ) )


    ## Return a new line - technically we should return a string so it can be printed where we want it, instead of printed early if _data = str( C( ) ) is used....
    return '\n'

##
##
##
_test = GetterSetterExample( )
print( _test )

## For some reason the deleter isn't being called...
del _test.x

Básicamente, lo mismo que el ejemplo de C( objeto ) excepto que estoy usando x en su lugar... Tampoco inicializo en _ _ init -... bien.. Sí, pero se puede quitar porque _ _ x se define como parte de la clase....

La salida es:

[ Test Class ] Set x = 1234
[ Test Class ] Get x = -1234
[ x ] -1234

Y si comento hacia fuera el uno mismo.x = 1234 in init entonces la salida es:

[ Test Class ] Get x = None
[ x ] None

Y si establezco el _default = None a _default = 0 en la función getter ( como todos los getters deberían tener un valor predeterminado, pero no es pasado por los valores de propiedad de lo que he visto, por lo que puede definirlo aquí, y en realidad no es malo porque puede definir el valor predeterminado una vez y usarlo en todas partes ) ie: def x (self, _default = 0):

[ Test Class ] Get x = 0
[ x ] 0

Nota: La lógica getter está ahí solo para que el valor sea manipulado por ella para asegurarse de que es manipulado por ella - lo mismo para las instrucciones print...

Nota: Estoy acostumbrado a Lua y ser capaz de crear dinámicamente 10 + helpers cuando llamo una sola función e hice algo similar para Python sin usar propiedades y funciona hasta cierto punto, pero, a pesar de que las funciones se crean antes de ser utilizadas, todavía hay problemas a veces con que se llamen antes de ser creadas, lo cual es extraño ya que no está codificado de esa manera... Prefiero la flexibilidad de las metatablas de Lua y el hecho de que puedo usar setters / getters reales en lugar de esencialmente acceder directamente a una variable... Me gusta lo rápido que se pueden construir algunas cosas. sin embargo, con Python, por ejemplo, programas gui. aunque uno que estoy diseñando puede no ser posible sin una gran cantidad de bibliotecas adicionales - si lo codifico en AutoHotkey puedo acceder directamente a las llamadas dll que necesito, y lo mismo se puede hacer en Java, C#, C++ y más - tal vez no he encontrado lo correcto todavía, pero para ese proyecto puedo cambiar de Python..

Nota: La salida de código en este foro está rota-Tuve que agregar espacios a la primera parte del código para que funcionara - al copiar / pegar asegúrese de convertir todos los espacios en pestañas.... Uso pestañas para Python porque en un archivo que tiene 10.000 líneas, el tamaño del archivo puede ser de 512 KB a 1 MB con espacios y de 100 a 200 KB con pestañas, lo que equivale a una enorme diferencia en el tamaño del archivo y la reducción en el tiempo de procesamiento...

Las pestañas también se pueden ajustar por usuario, por lo que si prefiere 2 espacios de ancho, 4, 8 o lo que sea que pueda hacerlo, lo que significa que es considerado para desarrolladores con déficits de vista.

Nota: Todas las funciones definidas en el la clase no está sangrada correctamente debido a un error en el software del foro - asegúrese de sangrarla si copia / pega

 0
Author: Acecool,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2018-08-07 11:08:27

A continuación hay otro ejemplo de cómo @property puede ayudar cuando uno tiene que refactorizar el código que se toma de aquí (solo lo resumo a continuación):

Imagine que creó una clase Money como esta:

class Money:
    def __init__(self, dollars, cents):
        self.dollars = dollars
        self.cents = cents

Y un usuario crea una biblioteca dependiendo de esta clase donde él / ella usa, por ejemplo,

money = Money(27, 12)

print("I have {} dollar and {} cents.".format(money.dollars, money.cents))
# prints I have 27 dollar and 12 cents.

Ahora supongamos que decide cambiar su clase Money y deshacerse de los atributos dollars y cents, pero en su lugar decide rastrear solo la cantidad total de céntimos:

class Money:
    def __init__(self, dollars, cents):
        self.total_cents = dollars * 100 + cents

Si el usuario mencionado anteriormente ahora intenta ejecutar su biblioteca como antes

money = Money(27, 12)

print("I have {} dollar and {} cents.".format(money.dollars, money.cents))

Resultará en un error

AttributeError: el objeto' Money 'no tiene atributo'dollars'

Eso significa que ahora todo el mundo que confía en su clase original Money tendría que cambiar todas las líneas de código donde dollars y cents se utilizan, lo que puede ser muy doloroso... Entonces, ¿cómo podría evitarse esto? Usando @property!

Eso es cómo:

class Money:
    def __init__(self, dollars, cents):
        self.total_cents = dollars * 100 + cents

    # Getter and setter for dollars...
    @property
    def dollars(self):
        return self.total_cents // 100

    @dollars.setter
    def dollars(self, new_dollars):
        self.total_cents = 100 * new_dollars + self.cents

    # And the getter and setter for cents.
    @property
    def cents(self):
        return self.total_cents % 100

    @cents.setter
    def cents(self, new_cents):
        self.total_cents = 100 * self.dollars + new_cents

Cuando ahora llamamos desde nuestra biblioteca

money = Money(27, 12)

print("I have {} dollar and {} cents.".format(money.dollars, money.cents))
# prints I have 27 dollar and 12 cents.

Funcionará como se esperaba y no tuvimos que cambiar una sola línea de código en nuestra biblioteca! De hecho, ni siquiera tendríamos que saber que la biblioteca de la que dependemos cambió.

También el setter funciona bien:

money.dollars += 2
print("I have {} dollar and {} cents.".format(money.dollars, money.cents))
# prints I have 29 dollar and 12 cents.

money.cents += 10
print("I have {} dollar and {} cents.".format(money.dollars, money.cents))
# prints I have 29 dollar and 22 cents.
 0
Author: Cleb,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2018-09-24 06:13:37