¿Cuál es la forma pitónica de usar getters y setters?
La forma "Pythonic" no es usar "getters" y "setters", sino usar atributos simples, como lo demuestra la pregunta, y del
para eliminar (pero los nombres se cambian para proteger a los inocentes ... builtins):
value = 'something'
obj.attribute = value
value = obj.attribute
del obj.attribute
Si más tarde, desea modificar la configuración y la obtención, puede hacerlo sin tener que alterar el código de usuario, utilizando el property
decorador:
class Obj:
"""property demo"""
#
@property # first decorate the getter method
def attribute(self): # This getter method name is *the* name
return self._attribute
#
@attribute.setter # the property decorates with `.setter` now
def attribute(self, value): # name, e.g. "attribute", is the same
self._attribute = value # the "value" name isn't special
#
@attribute.deleter # decorate with `.deleter`
def attribute(self): # again, the method name is the same
del self._attribute
(Cada uso de decorador copia y actualiza el objeto de propiedad anterior, así que tenga en cuenta que debe usar el mismo nombre para cada conjunto, obtener y eliminar función / método.
Después de definir lo anterior, la configuración original, obtener y eliminar el código es el mismo:
obj = Obj()
obj.attribute = value
the_value = obj.attribute
del obj.attribute
Debes evitar esto:
def set_property(property,value):
def get_property(property):
En primer lugar, lo anterior no funciona, porque no proporciona un argumento para la instancia de que la propiedad se establecería en (generalmente self
), que sería:
class Obj:
def set_property(self, property, value): # don't do this
...
def get_property(self, property): # don't do this either
...
En segundo lugar, esto duplica el propósito de dos métodos especiales, __setattr__
y __getattr__
.
En tercer lugar, también tenemos las funciones incorporadas setattr
y getattr
.
setattr(object, 'property_name', value)
getattr(object, 'property_name', default_value) # default is optional
El @property
decorador es para crear captadores y colocadores.
Por ejemplo, podríamos modificar el comportamiento de la configuración para colocar restricciones al valor que se establece:
class Protective(object):
@property
def protected_value(self):
return self._protected_value
@protected_value.setter
def protected_value(self, value):
if acceptable(value): # e.g. type or range check
self._protected_value = value
En general, queremos evitar usar property
y solo usar atributos directos.
Esto es lo que esperan los usuarios de Python. Siguiendo la regla de la menor sorpresa, debe intentar dar a sus usuarios lo que esperan a menos que tenga una razón muy convincente de lo contrario.
Demostración
Por ejemplo, supongamos que necesitamos que el atributo protegido de nuestro objeto sea un número entero entre 0 y 100 inclusive, y evitar su eliminación, con mensajes apropiados para informar al usuario sobre su uso adecuado:
class Protective(object):
"""protected property demo"""
#
def __init__(self, start_protected_value=0):
self.protected_value = start_protected_value
#
@property
def protected_value(self):
return self._protected_value
#
@protected_value.setter
def protected_value(self, value):
if value != int(value):
raise TypeError("protected_value must be an integer")
if 0 <= value <= 100:
self._protected_value = int(value)
else:
raise ValueError("protected_value must be " +
"between 0 and 100 inclusive")
#
@protected_value.deleter
def protected_value(self):
raise AttributeError("do not delete, protected_value can be set to 0")
(Tenga en cuenta que se __init__
refiere a self.protected_value
pero los métodos de propiedad se refieren self._protected_value
. Esto es para que __init__
use la propiedad a través de la API pública, asegurando que esté "protegida").
Y uso:
>>> p1 = Protective(3)
>>> p1.protected_value
3
>>> p1 = Protective(5.0)
>>> p1.protected_value
5
>>> p2 = Protective(-5)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 3, in __init__
File "<stdin>", line 15, in protected_value
ValueError: protectected_value must be between 0 and 100 inclusive
>>> p1.protected_value = 7.3
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 17, in protected_value
TypeError: protected_value must be an integer
>>> p1.protected_value = 101
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 15, in protected_value
ValueError: protectected_value must be between 0 and 100 inclusive
>>> del p1.protected_value
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 18, in protected_value
AttributeError: do not delete, protected_value can be set to 0
¿Importan los nombres?
Si lo hacen . .setter
y .deleter
hacer copias de la propiedad original. Esto permite que las subclases modifiquen correctamente el comportamiento sin alterar el comportamiento en el padre.
class Obj:
"""property demo"""
#
@property
def get_only(self):
return self._attribute
#
@get_only.setter
def get_or_set(self, value):
self._attribute = value
#
@get_or_set.deleter
def get_set_or_delete(self):
del self._attribute
Ahora para que esto funcione, debes usar los nombres respectivos:
obj = Obj()
# obj.get_only = 'value' # would error
obj.get_or_set = 'value'
obj.get_set_or_delete = 'new value'
the_value = obj.get_only
del obj.get_set_or_delete
# del obj.get_or_set # would error
No estoy seguro de dónde sería útil, pero el caso de uso es si desea una propiedad get, set y / o delete-only. Probablemente sea mejor quedarse semánticamente con la misma propiedad que tenga el mismo nombre.
Conclusión
Comience con atributos simples.
Si más tarde necesita funcionalidad en torno a la configuración, la obtención y la eliminación, puede agregarla con el decorador de propiedades.
Evite las funciones nombradas set_...
y get_...
, para eso están las propiedades.