define - python get and set attributes
¿Cómo funciona el decorador de propiedad? (11)
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.
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 here (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 donde se usan dollars
y cents
lo que puede ser muy doloroso ... Entonces, ¿cómo podría evitarse esto? ¡Usando @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.
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.
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 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 nota que no he usado corchetes "()" para llamar a la función. Esto es lo básico que estaba buscando para el decorador @property.
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
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 también actúan como decoradores. Devuelven un nuevo objeto de propiedad:
>>> property().getter(None)
<property object at 0x10ff079f0>
eso es una copia del objeto antiguo, pero con una de las funciones reemplazadas.
Recuerde que la sintaxis de @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)
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 de decoración.
Primero creamos algunas funciones y un objeto de property
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 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 puro 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 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 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.
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.
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 ver algunos ejemplos que he escrito sobre propiedades en python .
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 -
- Un método que se utiliza para obtener un valor se decora con "@property".
- 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".
- 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
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)