Exemplo do mundo real sobre como usar o recurso de propriedade em python?

142

Estou interessado em como usar @propertyem Python. Eu li os documentos python e o exemplo lá, na minha opinião, é apenas um código de brinquedo:

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

Não sei que benefícios posso obter ao envolver o _xpreenchido com o decorador da propriedade. Por que não apenas implementar como:

class C(object):
    def __init__(self):
        self.x = None

Eu acho que o recurso de propriedade pode ser útil em algumas situações. Mas quando? Alguém poderia me dar alguns exemplos do mundo real?

Obrigado.

xiao 啸
fonte
9
Este é o melhor e mais limpo explicação que eu encontrei sobre decorador propriedade [clique aqui ]
Sumudu
2
@ Anubis no último exemplo no link que você forneceu, definindo c = Celsius (-500) não lançou nenhum ValueError, o que eu acho que não está atingindo o resultado pretendido.
Sajuuk
Concorde com @Anubis. Foi implementado corretamente aqui: python-course.eu/python3_properties.php
anon01 26/01

Respostas:

91

Outros exemplos seriam a validação / filtragem dos atributos do conjunto (forçando-os a ficar dentro dos limites ou aceitáveis) e a preguiçosa avaliação de termos complexos ou que mudam rapidamente.

Cálculo complexo oculto atrás de um atributo:

class PDB_Calculator(object):
    ...
    @property
    def protein_folding_angle(self):
        # number crunching, remote server calls, etc
        # all results in an angle set in 'some_angle'
        # It could also reference a cache, remote or otherwise,
        # that holds the latest value for this angle
        return some_angle

>>> f = PDB_Calculator()
>>> angle = f.protein_folding_angle
>>> angle
44.33276

Validação:

class Pedometer(object)
    ...
    @property
    def stride_length(self):
        return self._stride_length

    @stride_length.setter
    def stride_length(self, value):
        if value > 10:
            raise ValueError("This pedometer is based on the human stride - a stride length above 10m is not supported")
        else:
            self._stride_length = value
benosteen
fonte
1
Gosto do exemplo do PDB_Calculator - coisas complicadas são abstraídas, tudo funciona e o usuário pode aproveitar a simplicidade!
Adam Kurkiewicz
2
possivelmente, do ponto de vista profissional, esses são exemplos muito bons. Mas, como noobie, acho esses exemplos bastante ineficazes. meu mal ... :(
kmonsoor
77

Um caso de uso simples será definir um atributo de instância somente leitura, pois você sabe que levar um nome de variável com um sublinhado _xem python geralmente significa que é privado (uso interno), mas às vezes queremos ler o atributo de instância e não escrever para que possamos usar propertypara isso:

>>> class C(object):

        def __init__(self, x):
            self._x = x

        @property
        def x(self):
            return self._x

>>> c = C(1)
>>> c.x
1
>>> c.x = 2
AttributeError        Traceback (most recent call last)

AttributeError: can't set attribute
mouad
fonte
6
Ainda é possível definir c._x, se o usuário quiser. Na verdade, o Python não possui atributos privados reais.
20

Dê uma olhada neste artigo para um uso muito prático. Em resumo, ele explica como, em Python, você geralmente pode abandonar o método getter / setter explícito, pois se precisar deles em algum momento, poderá usá-lo propertypara uma implementação perfeita.

Eli Bendersky
fonte
15

Uma coisa para a qual usei é o armazenamento em cache de valores de busca lenta, mas imutáveis, armazenados em um banco de dados. Isso generaliza para qualquer situação em que seus atributos exijam computação ou alguma outra operação longa (por exemplo, verificação de banco de dados, comunicação de rede) que você deseja executar somente sob demanda.

class Model(object):

  def get_a(self):
    if not hasattr(self, "_a"):
      self._a = self.db.lookup("a")
    return self._a

  a = property(get_a)

Isso ocorreu em um aplicativo da Web em que qualquer visualização de página específica pode precisar apenas de um atributo específico desse tipo, mas os próprios objetos subjacentes podem ter vários atributos - inicializá-los todos na construção seria um desperdício, e as propriedades permitem que eu seja flexível no qual atributos são preguiçosos e quais não são.

detly
fonte
1
Você não pode usar @cached_propertypara isso?
adarsh
@adarsh ​​- Parece interessante. Onde fica isso?
detly
Eu tenho usado, mas esqueci que não era um built-in, mas você pode usá-lo com isso, pypi.python.org/pypi/cached-property/0.1.5
adarsh
2
Interessante. Acho que foi publicado pela primeira vez após esta resposta, mas qualquer pessoa que esteja lendo isso provavelmente deve usá-lo.
detly
10

Lendo as respostas e comentários, o tema principal parece ser que as respostas parecem estar faltando um exemplo simples, mas útil. Eu incluí um aqui muito simples que demonstra o uso simples do @propertydecorador. É uma classe que permite ao usuário especificar e obter medições de distância usando uma variedade de unidades diferentes, in_feetou seja, ou in_metres.

class Distance(object):
    def __init__(self):
        # This private attribute will store the distance in metres
        # All units provided using setters will be converted before
        # being stored
        self._distance = 0.0

    @property
    def in_metres(self):
        return self._distance

    @in_metres.setter
    def in_metres(self, val):
        try:
            self._distance = float(val)
        except:
            raise ValueError("The input you have provided is not recognised "
                             "as a valid number")

    @property
    def in_feet(self):
        return self._distance * 3.2808399

    @in_feet.setter
    def in_feet(self, val):
        try:
            self._distance = float(val) / 3.2808399
        except:
            raise ValueError("The input you have provided is not recognised "
                             "as a valid number")

    @property
    def in_parsecs(self):
        return self._distance * 3.24078e-17

    @in_parsecs.setter
    def in_parsecs(self, val):
        try:
            self._distance = float(val) / 3.24078e-17
        except:
            raise ValueError("The input you have provided is not recognised "
                             "as a valid number")

Uso:

>>> distance = Distance()
>>> distance.in_metres = 1000.0
>>> distance.in_metres
1000.0
>>> distance.in_feet
3280.8399
>>> distance.in_parsecs
3.24078e-14
Mike
fonte
para mim, pessoalmente, os melhores exemplos de getters / setters são mostrar às pessoas o tipo de mudanças que você precisa fazer mais tarde, mas obviamente, isso leva um pouco mais de tempo.
dtc
7

A propriedade é apenas uma abstração em torno de um campo que oferece mais controle sobre as maneiras pelas quais um campo específico pode ser manipulado e fazer cálculos de middleware. Poucos usos que vêm à mente são validação e inicialização prévia e restrição de acesso

@property
def x(self):
    """I'm the 'x' property."""
    if self._x is None:
        self._x = Foo()

    return self._x
Stanislav Ageev
fonte
6

Sim, para o exemplo original publicado, a propriedade funcionará exatamente da mesma maneira que simplesmente ter uma variável de instância 'x'.

Essa é a melhor coisa sobre as propriedades do python. Do lado de fora, eles funcionam exatamente como variáveis ​​de instância! O que permite que você use variáveis ​​de instância de fora da classe.

Isso significa que seu primeiro exemplo pode realmente usar uma variável de instância. Se as coisas mudarem e você decidir alterar sua implementação e uma propriedade for útil, a interface da propriedade ainda será a mesma do código fora da classe. Uma mudança da variável de instância para propriedade não afeta o código fora da classe.

Muitas outras linguagens e cursos de programação instruirão que um programador nunca deve expor variáveis ​​de instância e, em vez disso, usar 'getters' e 'setters' para que qualquer valor seja acessado de fora da classe, mesmo no caso simples, conforme citado na pergunta.

Código fora da classe com muitas linguagens (por exemplo, Java)

object.get_i()
    #and
object.set_i(value)

#in place of (with python)
object.i
    #and 
object.i = value

E ao implementar a classe, existem muitos 'getters' e 'setters' que fazem exatamente como o seu primeiro exemplo: replicar uma variável de instância simplesmente. Esses getters e setters são necessários porque, se a implementação da classe mudar, todo o código fora da classe precisará ser alterado. Mas as propriedades python permitem que o código fora da classe seja o mesmo que nas variáveis ​​de instância. Portanto, o código fora da classe não precisa ser alterado se você adicionar uma propriedade ou tiver uma variável de instância simples. Portanto, diferente da maioria das linguagens orientadas a objetos, para o seu exemplo simples, você pode usar a variável de instância em vez de 'getters' e 'setters' que realmente não são necessários, com a certeza de que, se você mudar para uma propriedade no futuro, o código usando sua classe não precisa mudar.

Isso significa que você só precisa criar propriedades se houver um comportamento complexo e, para o caso simples muito comum em que, conforme descrito na pergunta, basta uma variável de instância simples, basta usar a variável de instância.

innov8
fonte
6

outro recurso interessante das propriedades sobre o uso de setters e getters, que permitem que você continue usando operadores OP = (por exemplo, + =, - =, * = etc) em seus atributos, mantendo ainda qualquer validação, controle de acesso, armazenamento em cache, etc. os setters e getters forneceriam.

por exemplo, se você escreveu a classe Personcom um setter setage(newage)e um getter getage(), para aumentar a idade, você teria que escrever:

bob = Person('Robert', 25)
bob.setage(bob.getage() + 1)

mas se você fez ageuma propriedade, pode escrever o muito mais limpo:

bob.age += 1
quizdog
fonte
5

A resposta curta para sua pergunta é que, no seu exemplo, não há benefício. Você provavelmente deve usar o formulário que não envolve propriedades.

A razão pela qual as propriedades existem é que, se o seu código mudar no futuro, e você precisar repentinamente fazer mais com seus dados: armazenar valores em cache, proteger o acesso, consultar algum recurso externo ... seja qual for, você pode modificar facilmente sua classe para adicionar getters e setters para os dados sem alterar a interface, para que você não precise encontrar em todo lugar do código onde esses dados são acessados ​​e alterá-los também.

SpoonMeiser
fonte
4

Algo que muitos não percebem a princípio é que você pode criar suas próprias subclasses de propriedade. Isso eu achei muito útil para expor atributos de objeto somente leitura ou atributo que você pode ler e escrever, mas não remover. É também uma excelente maneira de agrupar funcionalidades, como rastrear modificações nos campos de objetos.

class reader(property):
    def __init__(self, varname):
        _reader = lambda obj: getattr(obj, varname)
        super(reader, self).__init__(_reader)

class accessor(property):
    def __init__(self, varname, set_validation=None):
        _reader = lambda obj: getattr(obj, varname)
        def _writer(obj, value):
            if set_validation is not None:
               if set_validation(value):
                  setattr(obj, varname, value)
        super(accessor, self).__init__(_reader, _writer)

#example
class MyClass(object):
   def __init__(self):
     self._attr = None

   attr = reader('_attr')

fonte
Eu gosto disso. Estou lendo isso corretamente, pois o leitor é somente leitura enquanto o acessador é de leitura / gravação sem capacidade de exclusão? Como você adicionaria a validação de dados? Sou bastante novo em Python, mas estou pensando que provavelmente há uma maneira de adicionar um retorno de chamada à attr = reader('_attr')linha ou alguma forma de pré-precificação attr = if self.__isValid(value): reader('_attr'). Sugestões?
Gabe Spradlin
Desculpe, acabei de perceber que estava perguntando sobre a validação de dados para uma variável somente leitura. Mas, obviamente, isso se aplicaria apenas à parte setter da classe acessadora. Então mude attr = reader('_attr')para attr = accessor('_attr'). Obrigado
Gabe Spradlin
Você está certo que, se quisesse a validação, adicionaria uma função para validar e gerar a exceção se inválida (ou qualquer comportamento que você gostasse, incluindo não fazer nada) ao init . Modifiquei o acima com um padrão possível. O validador deve retornar True | False para orientar se o conjunto acontece ou não.