python - ¿Cómo funciona el decorador de propiedad?




5 Answers

documentation que 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)
python properties decorator python-decorators python-internals

Me gustaría entender cómo funciona la property función incorporada. Lo que me confunde es que esa 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 la documentation :

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.")

los argumentos de la property son getx , setx , delx y una cadena de documentos.

En el código de abajo se utiliza la property como decorador. El objeto de la misma es la función x , 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 se x.deleter decoradores x.setter y x.deleter ? Estoy confundido.




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

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, la 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'



Leí todas las publicaciones aquí y me di cuenta de que podemos necesitar un ejemplo de la vida real. ¿Por qué, en realidad, tenemos @property? Por lo tanto, considere una aplicación Flask donde use 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, hemos "escondido" la password atributo utilizando @property que activa la aserción AttributeError cuando intenta acceder a ella directamente, mientras que usamos @ property.setter para establecer la variable de instancia real password_hash .

Ahora en auth/views.py podemos crear una instancia de 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()
...

Observe la password atributo que proviene de un formulario de registro cuando un usuario completa el formulario. La confirmación de la contraseña ocurre en el extremo delantero 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 Flask).

Espero que este ejemplo sea de utilidad.




Vamos a empezar con los decoradores de Python.

Un decorador de 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 referenciados por una variable, agregados en las listas, pasados ​​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 decoradora 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()

Concluyamos todo que con un caso concreto, pero antes hablemos de algunos de los principios de los ops.

Los captadores y definidores se utilizan en muchos lenguajes de programación orientados a objetos para garantizar el principio de la encapsulación de datos (se considera el conjunto de datos con los métodos que operan con estos datos).

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

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

Sí, @property es básicamente una forma pythonic 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.

Supongamos que decide hacer una clase que pueda 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 Refactored, aquí es cómo podríamos haberlo logrado con la propiedad.

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

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 haber sido desglosado como,

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

Punto a tener en cuenta:

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

Ahora puedes acceder al valor de la temperatura escribiendo.

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

Podemos continuar y no definir los nombres get_temperature y set_temperature, ya que son innecesarios y contaminar el espacio de nombres de la clase.

La manera en que Pythonic trata 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 se decora con "@property".
  2. El método que tiene que funcionar como 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 usted ha diseñado una clase de la siguiente manera:

class OurClass:

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


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

Ahora, asumamos que nuestra clase se hizo popular entre los clientes y comenzaron a usarla en sus programas. Hicieron todo tipo de asignaciones al objeto.

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

Debido a las 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 es así? Puede comenzar con la implementación más simple que se pueda imaginar, y puede migrar posteriormente 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 acomodadores!

Puedes consultar esta implementación here




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 en el ejemplo C (objeto), excepto que estoy usando x en su lugar ... Tampoco inicializo en __init - ... bueno ... lo hago, pero se puede eliminar 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 el self.x = 1234 en init , la salida es:

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

y si configuro _default = None en _default = 0 en la función getter (ya que todos los getters deberían tener un valor predeterminado, pero los valores de propiedad no lo pasan de lo que he visto para que pueda definirlo aquí, y en realidad no es malo porque puedes definir el valor predeterminado una vez y usarlo en todas partes) es decir: def x (self, _default = 0):

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

Nota: La lógica del captador está ahí para que el valor sea manipulado por él para garantizar que sea manipulado por él, lo mismo para las declaraciones de impresión ...

Nota: estoy acostumbrado a Lua y puedo crear dinámicamente más de 10 ayudantes cuando llamo a una sola función e hice algo similar para Python sin usar propiedades y funciona hasta cierto punto, pero, aunque las funciones se están creando antes siendo utilizados, a veces hay problemas con ellos para ser llamados antes de ser creados, lo cual es extraño ya que no está codificado de esa manera ... Prefiero la flexibilidad de las meta-tablas de Lua y el hecho de que puedo usar los setters / getters reales Sin embargo, en lugar de acceder directamente a una variable ... me gusta la rapidez con la que se pueden construir algunas cosas con Python, por ejemplo, los programas gui. aunque una que estoy diseñando puede no ser posible sin muchas bibliotecas adicionales: si la 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. aún no he encontrado lo correcto, pero para ese proyecto puedo cambiar de Python ...

Nota: La salida del código en este foro está rota. Tuve que agregar espacios a la primera parte del código para que funcione. Cuando copie / pegue, asegúrese de convertir todos los espacios en pestañas ... Utilizo las 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 de tamaño de archivo y reducción del 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 hacer, significa que los desarrolladores con déficit visual deben considerarlos.

Nota: Todas las funciones definidas en la clase no están sangradas correctamente debido a un error en el software del foro. Asegúrese de sangrar si copia / pega.




Related