¿Cómo funciona el decorador de propiedad?

Me gustaría entender cómo funciona la property función incorporada. Lo que me confunde es que la property también se puede usar como decorador, pero solo toma argumentos cuando se usa como función integrada 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.") 

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.

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

 >>> property()  

Es este objeto el que tiene métodos extra :

 >>> property().getter  >>> property().setter  >>> property().deleter  

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

 >>> property().getter(None)  

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

Recuerde que la syntax de @decorator es solo azúcar sintáctica; la syntax:

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

Realmente significa lo mismo que

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

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

La siguiente secuencia también crea una propiedad completa, al usar esos métodos decorativos.

Primero creamos algunas funciones y un objeto de property con solo un captador:

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

A continuación 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, agregamos un eliminador 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 de property actúa como un objeto descriptor , por lo que tiene los métodos .__get__() , .__set__() y .__delete__() para enganchar los atributos de instancia, 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 una implementación de ejemplo de python pura del tipo de property() :

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

La documentación dice 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) 

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 syntax simplificada para crear una property con solo un captador.

El siguiente paso sería extender 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 anterior x más el definidor dado.

x.deleter funciona de la misma manera.

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' 

Este 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 

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

Imagina que creaste una clase de Money como esta:

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

y un usuario crea una biblioteca en función de esta clase en la que 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 de Money y deshacerse de los atributos de dollars y cents pero en su lugar decide solo realizar un seguimiento de la cantidad total de centavos:

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

Si el usuario mencionado anteriormente 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 ‘Dinero’ no tiene atributo ‘dólares’

Eso significa que ahora todos los que confían en su clase de Money original tendrían que cambiar todas las líneas de código en las que se usan dollars y cents lo que puede ser muy doloroso … Entonces, ¿cómo podría evitarse esto? Mediante el uso de @property !

Así es como:

 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 ha cambiado.

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. 

Leí todas las publicaciones aquí y me di cuenta de que es posible que necesitemos un ejemplo de la vida real. 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 “ocultado” 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.

Este punto ha sido aclarado por muchas personas allá arriba, pero aquí hay un punto directo que estaba buscando. Esto es lo que creo que es importante comenzar con el decorador @property. p.ej:-

 class UtilityMixin(): @property def get_config(self): return "This is property" 

La llamada a la función “get_config ()” funcionará así.

 util = UtilityMixin() print(util.get_config) 

Si observa que no he usado corchetes “()” para llamar a la función. Esto es lo básico que estaba buscando para el decorador @property. Para que puedas usar tu función como una variable.

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 progtwigció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 progtwigdor 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(yx) 

Ahora, asummos que nuestra clase se hizo popular entre los clientes y comenzaron a usarla en sus progtwigs. 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 establecedores!

Puedes consultar esta Implementación aquí.

Una propiedad puede ser declarada de dos maneras.

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

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

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 deben tener un valor predeterminado, pero los valores de propiedad no lo pasan de lo que he visto, por lo que puede 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 crean antes. siendo usados, 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 progtwigs gui. aunque es posible que una que estoy diseñando no sea posible sin muchas bibliotecas adicionales, si la codifico en AutoHotkey puedo acceder directamente a las llamadas a 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 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 tabs … Utilizo las tabs 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 tabs, lo que equivale a una enorme diferencia de tamaño de archivo y reducción del tiempo de procesamiento …

Las tabs 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.

Un comentario: para mí, para Python 2.x, @property no funcionó como se anunciaba cuando no heredé el objeto de formulario:

 class A(): pass 

pero trabajó cuando:

 class A(object): pass 

para Pyhton 3, trabajó siempre