Qual é a maneira pitônica de usar getters e setters?


340

Estou fazendo assim:

def set_property(property,value):  
def get_property(property):  

ou

object.property = value  
value = object.property

Eu sou novo no Python, por isso ainda estou explorando a sintaxe e gostaria de alguns conselhos para fazer isso.

Respostas:


684

Tente isto: Propriedade Python

O código de exemplo é:

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

2
O setter para x é chamado no inicializador ao instanciar _x?
Casey #

7
@Casey: Não. As referências a ._x(que não são uma propriedade, apenas um atributo comum) ignoram o propertyempacotamento. Somente referências para .xpassar pelo property.
ShadowRanger

273

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 delpara 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 setattre getattrbuiltin.

setattr(object, 'property_name', value)
getattr(object, 'property_name', default_value)  # default is optional

O @propertydecorador é 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_valuemas 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 . .settere .deleterfaç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.


Além de duplicar a funcionalidade disponível, por que evitar seus próprios setters e getters? Entendo que pode não ser o caminho pitonico, mas existem problemas realmente sérios que alguém pode encontrar de outra maneira?
user1350191

4
Na sua demonstração, o __init__método se refere a, self.protected_valuemas o getter e setter se referem self._protected_value. Poderia explicar como isso funciona? Testei seu código e ele funciona como está - portanto, não é um erro de digitação.
Code283

2
@codeforester Eu esperava responder na minha resposta mais cedo, mas até que eu possa, esse comentário deve ser suficiente. Espero que você possa ver que ele usa a propriedade através da API pública, garantindo que ela seja "protegida". Não faria sentido "protegê-lo" com uma propriedade e, em vez disso, use a API não pública na __init__mesma?
Aaron Hall

2
Sim, @AaronHall conseguiu agora. Eu não sabia que self.protected_value = start_protected_valueestava realmente chamando a função setter; Eu pensei que era uma tarefa.
Codeforester 01/08/19

11
imho esta deve ser a resposta aceita, se eu entendi corretamente python leva exatamente o ponto oposto em comparação com, por exemplo, java. Em vez de fazer tudo privados por padrão e escrever algum código extra quando for necessário publicamente em python, você pode fazer tudo público e adicionar privacidade mais tarde
idclev 463035818

27
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

17

O uso @propertye @attribute.setterajuda você a não apenas usar a maneira "pitônica", mas também para verificar a validade dos atributos ao criar o objeto e ao alterá-lo.

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

Com isso, você realmente 'esconde' o _nameatributo dos desenvolvedores do cliente e também realiza verificações no tipo de propriedade do nome. Observe que, seguindo esta abordagem, mesmo durante a iniciação, o setter é chamado. Assim:

p = Person(12)

Levará a:

Exception: Invalid value for name

Mas:

>>>p = person('Mike')
>>>print(p.name)
Mike
>>>p.name = 'George'
>>>print(p.name)
George
>>>p.name = 2.3 # Causes an exception

16

Confira o @propertydecorador .


33
Esta é praticamente uma resposta apenas de link.
Aaron Hall


7
Como isso é uma resposta completa? Um link não é uma resposta.
Andy_A̷n̷d̷y̷ 22/08

Eu acho que é uma boa resposta, porque a documentação indica claramente como usá-la (e permanecerá atualizada se a implementação do python mudar, e direciona o OP para o método sugerido pelo respondente. @ Jean-FrançoisCorbett afirmou claramente 'como' é uma resposta completa.
HunnyBear 30/10/19

De qualquer forma, essa resposta não adiciona nada útil a outras respostas e, idealmente, deve ser excluída.
Georgy

5

Você pode usar acessadores / mutadores (ie @attr.settere @property) ou não, mas o mais importante é ser consistente!

Se você estiver usando @propertypara simplesmente acessar um atributo, por exemplo,

class myClass:
    def __init__(a):
        self._a = a

    @property
    def a(self):
        return self._a

use-o para acessar todos os * atributos! Seria uma má prática acessar alguns atributos usando @propertye deixar outras propriedades públicas (ou seja, nome sem sublinhado) sem um acessador, por exemplo , não

class myClass:
    def __init__(a, b):
        self.a = a
        self.b = b

    @property
    def a(self):
        return self.a

Observe que self.bnão há um acessador explícito aqui, mesmo que seja público.

Da mesma forma com os levantadores (ou mutadores ), fique à vontade para usar, @attribute.settermas seja consistente! Quando você faz, por exemplo

class myClass:
    def __init__(a, b):
        self.a = a
        self.b = b 

    @a.setter
    def a(self, value):
        return self.a = value

É difícil para mim adivinhar sua intenção. Por um lado, você está dizendo que ambos ae bsão públicos (sem sublinhado principal em seus nomes); portanto, teoricamente eu deveria ter permissão para acessar / alterar (obter / definir) ambos. Mas então você especifica apenas um mutador explícito para a, o que me diz que talvez eu não deva ser capaz de definir b. Como você forneceu um mutador explícito, não tenho certeza se a falta de accessor explícito ( @property) significa que eu não deveria poder acessar nenhuma dessas variáveis ​​ou você estava simplesmente sendo frugal em usá-lo @property.

* A exceção é quando você deseja explicitamente tornar algumas variáveis ​​acessíveis ou mutáveis, mas não as duas, ou deseja executar alguma lógica adicional ao acessar ou alterar um atributo. É quando estou usando pessoalmente @propertye @attribute.setter(caso contrário, não há acessores / mutadores explícitos para atributos públicos).

Por fim, as sugestões do PEP8 e do Guia de Estilo do Google:

PEP8, Designing for Herança, diz:

Para atributos simples de dados públicos, é melhor expor apenas o nome do atributo, sem métodos complicados de acessador / mutador . Lembre-se de que o Python fornece um caminho fácil para aprimoramentos futuros, caso você descubra que um atributo de dados simples precisa aumentar o comportamento funcional. Nesse caso, use propriedades para ocultar a implementação funcional atrás da sintaxe de acesso a atributos de dados simples.

Por outro lado, de acordo com as Regras / Propriedades da linguagem Python do Guia de Estilo do Google, a recomendação é:

Use propriedades no novo código para acessar ou definir dados nos quais você normalmente usaria métodos simples e leves de acessador ou configurador. As propriedades devem ser criadas com o @propertydecorador.

Os profissionais dessa abordagem:

A legibilidade é aumentada ao eliminar chamadas explícitas de método get e set para acesso simples a atributos. Permite que os cálculos sejam preguiçosos. Considerada a maneira Pythonic de manter a interface de uma classe. Em termos de desempenho, permitir propriedades ignora a necessidade de métodos acessadores triviais quando um acesso direto a variável é razoável. Isso também permite que métodos acessadores sejam adicionados no futuro sem interromper a interface.

e contras:

Deve herdar do objectPython 2. Pode ocultar efeitos colaterais como sobrecarga do operador. Pode ser confuso para subclasses.


11
Eu discordo fortemente. Se eu tenho 15 atributos no meu objeto e quero que um seja computado @property, fazer o resto também usar @propertyparece uma péssima decisão.
Quelklef 13/01

Concorde, mas somente se houver algo específico sobre esse atributo específico que você precisa @property(por exemplo, executar alguma lógica especial antes de retornar um atributo). Caso contrário, por que você decoraria um atributo com @properye não outros?
Tomasz Bartkowiak 13/01

@Quelklef veja a nota lateral na postagem (marcada com asterisco).
Tomasz Bartkowiak

Bem ... Se você não está fazendo uma das coisas mencionadas na nota @propertylateral, não deveria estar usando para começar, certo? Se o seu getter é return this._xe seu setter é this._x = new_x, então usar @propertyé meio bobo.
Quelklef 13/01

11
Hmm, talvez. Pessoalmente, eu diria que não está bem - é totalmente supérfluo. Mas eu posso ver de onde você está vindo. Acho que acabei de ler sua postagem dizendo que "a coisa mais importante ao usar @propertyé ser consistente".
Quelklef 14/01

-1

Você pode usar os métodos mágicos __getattribute__e __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

Esteja ciente de que __getattr__e __getattribute__não é o mesmo. __getattr__é invocado apenas quando o atributo não é encontrado.

Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.