define - python get and set attributes
¿Cuál es la forma pitónica de usar getters y setters? (6)
¿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 deler para desreferenciación (pero los nombres se cambian para proteger a los inocentes ... incorporados):
value = ''something''
obj.attribute = value
value = obj.attribute
del obj.attribute
Si más adelante, desea modificar la configuración y la obtención, puede hacerlo sin tener que modificar el código de usuario, utilizando el decorador de property
:
class Obj:
"""property demo"""
#
@property
def attribute(self): # implements the get - this name is *the* name
return self._attribute
#
@attribute.setter
def attribute(self, value): # name must be the same
self._attribute = value
#
@attribute.deleter
def attribute(self): # again, name must be the same
del self._attribute
(Cada decorador copia y actualiza el objeto de propiedad anterior, así que tenga en cuenta que probablemente debería usar el mismo nombre para cada función, método de obtención y eliminación).
Después de definir lo anterior, la configuración, obtención y eliminación originales son las mismas:
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 en la que la propiedad se establecería en (normalmente, 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 decorador @property
es para crear getters y setters.
Por ejemplo, podríamos modificar el comportamiento de configuración para colocar restricciones en el 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 el uso de property
y solo usar atributos directos.
Esto es lo que esperan los usuarios de Python. Siguiendo la regla de la menor sorpresa, debe intentar darles a sus usuarios lo que esperan a menos que tenga una razón muy convincente de lo contrario.
Demostración
Por ejemplo, supongamos que necesitábamos que el atributo protegido de nuestro objeto fuera un número entero entre 0 y 100, e impidiera su eliminación, con los mensajes adecuados para informar al usuario de su uso adecuado:
class Protective(object):
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")
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
¿Los nombres importan?
Si lo hacen .setter
y .deleter
hacen copias de la propiedad original. Esto permite que las subclases modifiquen adecuadamente 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, tienes que 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 de obtener, establecer y / o eliminar solo. Probablemente es mejor atenerse a la misma propiedad semántica que tenga el mismo nombre.
Conclusión
Comience con atributos simples.
Si más tarde necesita la funcionalidad en torno a la configuración, obtención y eliminación, puede agregarla con el decorador de propiedades.
Evite las funciones llamadas set_...
y get_...
- para eso son las propiedades.
Lo estoy haciendo como
def set_property(property,value):
def get_property(property):
o
object.property = value
value = object.property
Soy nuevo en Python, así que todavía estoy explorando la sintaxis, y me gustaría recibir algunos consejos para hacerlo.
Echa un vistazo a la decoradora de @property
.
El uso de @property
y @attribute.setter
ayuda no solo a usar la forma "pythonic", sino también a verificar la validez de los atributos al crear el objeto y al alterarlo.
class Person(object):
def __init__(self, p_name=None):
self.name = p_name
@property
def name(self):
return self._name
@name.setter
def name(self, new_name):
if type(new_name) == str: #type checking for name property
self._name = new_name
else:
raise Exception("Invalid value for name")
Por esto, en realidad, usted ''oculta'' el atributo _name
de los desarrolladores de clientes y también realiza comprobaciones en el tipo de propiedad del nombre. Tenga en cuenta que al seguir este enfoque, incluso durante la iniciación, se llama al setter. Asi que:
p = Person(12)
Dará lugar a:
Exception: Invalid value for name
Pero:
>>>p = person(''Mike'')
>>>print(p.name)
Mike
>>>p.name = ''George''
>>>print(p.name)
George
>>>p.name = 2.3 # Causes an exception
Prueba esto: Python Property
El código de muestra es:
class C(object):
def __init__(self):
self._x = None
@property
def x(self):
"""I''m the ''x'' property."""
print("getter of x called")
return self._x
@x.setter
def x(self, value):
print("setter of x called")
self._x = value
@x.deleter
def x(self):
print("deleter of x called")
del self._x
c = C()
c.x = ''foo'' # setter called
foo = c.x # getter called
del c.x # deleter called
Puedes usar los métodos mágicos __getattribute__
y __setattr__
.
class MyClass:
def __init__(self, attrvalue):
self.myattr = attrvalue
def __getattribute__(self, attr):
if attr == "myattr":
#Getter for myattr
def __setattr__(self, attr):
if attr == "myattr":
#Setter for myattr
Tenga en cuenta que __getattr__
y __getattribute__
no son lo mismo. __getattr__
solo se invoca cuando no se encuentra el atributo.
In [1]: class test(object):
def __init__(self):
self.pants = ''pants''
@property
def p(self):
return self.pants
@p.setter
def p(self, value):
self.pants = value * 2
....:
In [2]: t = test()
In [3]: t.p
Out[3]: ''pants''
In [4]: t.p = 10
In [5]: t.p
Out[5]: 20