Qual é a maneira pitônica de usar getters e setters?
O caminho "pitônico" não é usar "getters" e "setters", mas usar atributos simples, como demonstra a pergunta, e del
para excluir (mas os nomes são alterados para proteger os inocentes ... embutidos):
value = 'something'
obj.attribute = value
value = obj.attribute
del obj.attribute
Se, posteriormente, você desejar modificar a configuração e obter, poderá fazê-lo sem precisar alterar o código do usuário, usando o comando 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 do decorador copia e atualiza o objeto de propriedade anterior, observe que você deve usar o mesmo nome para cada conjunto, obter e excluir função / método.
Após definir o acima, a configuração original, obtendo e excluindo o código é a mesma:
obj = Obj()
obj.attribute = value
the_value = obj.attribute
del obj.attribute
Você deve evitar isso:
def set_property(property,value):
def get_property(property):
Em primeiro lugar, o acima não funciona, porque você não fornece um argumento para a instância em que a propriedade seria configurada para (geralmente self
), que seria:
class Obj:
def set_property(self, property, value): # don't do this
...
def get_property(self, property): # don't do this either
...
Em segundo lugar, isso duplica o objetivo de dois métodos especiais, __setattr__
e __getattr__
.
Em terceiro lugar, também temos as funções setattr
e getattr
builtin.
setattr(object, 'property_name', value)
getattr(object, 'property_name', default_value) # default is optional
O @property
decorador é para criar getters e setters.
Por exemplo, podemos modificar o comportamento da configuração para colocar restrições no valor que está sendo definido:
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
Em geral, queremos evitar o uso de property
e apenas usar atributos diretos.
É o que se espera dos usuários de Python. Seguindo a regra da menor surpresa, você deve tentar dar a seus usuários o que eles esperam, a menos que você tenha uma razão muito convincente para o contrário.
Demonstração
Por exemplo, digamos que precisávamos que o atributo protegido do nosso objeto fosse um número inteiro entre 0 e 100, inclusive, e evite sua exclusão, com mensagens apropriadas para informar o usuário sobre o uso adequado:
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")
(Observe que __init__
se refere a, self.protected_value
mas os métodos de propriedade se referem self._protected_value
. Isso é feito para __init__
usar a propriedade por meio da API pública, garantindo que ela esteja "protegida".)
E 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
Os nomes importam?
Sim eles fazem . .setter
e .deleter
faça cópias da propriedade original. Isso permite que as subclasses modifiquem adequadamente o comportamento sem alterar o comportamento no pai.
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
Agora, para que isso funcione, você deve usar os respectivos nomes:
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
Não tenho certeza de onde isso seria útil, mas o caso de uso é se você deseja uma propriedade get, set e / ou delete-only. Provavelmente é melhor manter semanticamente a mesma propriedade com o mesmo nome.
Conclusão
Comece com atributos simples.
Se, posteriormente, você precisar de funcionalidade em torno da configuração, obtenção e exclusão, adicione-a com o decorador de propriedades.
Evite funções nomeadas set_...
e get_...
- é para isso que servem as propriedades.