Python @property: como usá-lo e por quê? - Programiz

Neste tutorial, você aprenderá sobre o decorador Python @property; uma maneira pythônica de usar getters e setters na programação orientada a objetos.

A programação Python nos fornece um @propertydecorador embutido que torna o uso de getter e setters muito mais fácil na Programação Orientada a Objetos.

Antes de entrar em detalhes sobre o que @propertyé decorador, vamos primeiro construir uma intuição sobre por que ele seria necessário em primeiro lugar.

Classe sem getters e setters

Vamos supor que decidimos fazer uma classe que armazene a temperatura em graus Celsius. Ele também implementaria um método para converter a temperatura em graus Fahrenheit. Uma maneira de fazer isso é a seguinte:

 class Celsius: def __init__(self, temperature = 0): self.temperature = temperature def to_fahrenheit(self): return (self.temperature * 1.8) + 32

Podemos fazer objetos fora desta classe e manipular o temperatureatributo como quisermos:

 # Basic method of setting and getting attributes in Python class Celsius: def __init__(self, temperature=0): self.temperature = temperature def to_fahrenheit(self): return (self.temperature * 1.8) + 32 # Create a new object human = Celsius() # Set the temperature human.temperature = 37 # Get the temperature attribute print(human.temperature) # Get the to_fahrenheit method print(human.to_fahrenheit())

Resultado

 37 98.60000000000001

As casas decimais extras ao converter para Fahrenheit são devidas ao erro aritmético de ponto flutuante. Para saber mais, visite Python Floating Point Arithmetic Error.

Sempre que atribuímos ou recuperamos qualquer atributo de objeto temperaturecomo mostrado acima, o Python pesquisa-o no __dict__atributo de dicionário embutido do objeto .

 >>> human.__dict__ ('temperature': 37)

Portanto, man.temperatureinternamente se torna man.__dict__('temperature').

Usando getters e setters

Suponha que desejamos estender a usabilidade da classe Celsius definida acima. Sabemos que a temperatura de qualquer objeto não pode chegar abaixo de -273,15 graus Celsius (Zero Absoluto em Termodinâmica)

Vamos atualizar nosso código para implementar essa restrição de valor.

Uma solução óbvia para a restrição acima será ocultar o atributo temperature(torná-lo privado) e definir novos métodos getter e setter para manipulá-lo. Isso pode ser feito da seguinte forma:

 # Making Getters and Setter methods class Celsius: def __init__(self, temperature=0): self.set_temperature(temperature) def to_fahrenheit(self): return (self.get_temperature() * 1.8) + 32 # getter method def get_temperature(self): return self._temperature # setter method def set_temperature(self, value): if value < -273.15: raise ValueError("Temperature below -273.15 is not possible.") self._temperature = value

Como podemos ver, o método acima apresenta dois novos métodos get_temperature()e set_temperature().

Além disso, temperaturefoi substituído por _temperature. Um sublinhado _no início é usado para denotar variáveis ​​privadas em Python.

Agora, vamos usar esta implementação:

 # Making Getters and Setter methods class Celsius: def __init__(self, temperature=0): self.set_temperature(temperature) def to_fahrenheit(self): return (self.get_temperature() * 1.8) + 32 # getter method def get_temperature(self): return self._temperature # setter method def set_temperature(self, value): if value < -273.15: raise ValueError("Temperature below -273.15 is not possible.") self._temperature = value # Create a new object, set_temperature() internally called by __init__ human = Celsius(37) # Get the temperature attribute via a getter print(human.get_temperature()) # Get the to_fahrenheit method, get_temperature() called by the method itself print(human.to_fahrenheit()) # new constraint implementation human.set_temperature(-300) # Get the to_fahreheit method print(human.to_fahrenheit())

Resultado

 37 98.60000000000001 Traceback (última chamada mais recente): Arquivo "", linha 30, no Arquivo "", linha 16, em set_temperature ValueError: Temperatura abaixo de -273,15 não é possível.

Esta atualização implementou com sucesso a nova restrição. Não temos mais permissão para definir a temperatura abaixo de -273,15 graus Celsius.

Nota : As variáveis ​​privadas não existem realmente no Python. Existem simplesmente normas a serem seguidas. O idioma em si não aplica nenhuma restrição.

 >>> human._temperature = -300 >>> human.get_temperature() -300

No entanto, o maior problema com a atualização acima é que todos os programas que implementaram nossa classe anterior precisam modificar seu código de obj.temperaturepara obj.get_temperature()e todas as expressões semelhantes obj.temperature = vala obj.set_temperature(val).

Essa refatoração pode causar problemas ao lidar com centenas de milhares de linhas de códigos.

Em suma, nossa nova atualização não era compatível com versões anteriores. É aqui que @propertyvem o resgate.

A classe de propriedade

Uma maneira pitônica de lidar com o problema acima é usar a propertyclasse. Aqui está como podemos atualizar nosso código:

 # using property class class Celsius: def __init__(self, temperature=0): self.temperature = temperature def to_fahrenheit(self): return (self.temperature * 1.8) + 32 # getter def get_temperature(self): print("Getting value… ") return self._temperature # setter def set_temperature(self, value): print("Setting value… ") if value < -273.15: raise ValueError("Temperature below -273.15 is not possible") self._temperature = value # creating a property object temperature = property(get_temperature, set_temperature)

Adicionamos uma print()função dentro get_temperature()e set_temperature()para observar claramente que estão sendo executados.

A última linha do código cria um objeto de propriedade temperature. Simplificando, a propriedade anexa algum código ( get_temperaturee set_temperature) ao atributo de membro accesses ( temperature).

Vamos usar este código de atualização:

 # using property class class Celsius: def __init__(self, temperature=0): self.temperature = temperature def to_fahrenheit(self): return (self.temperature * 1.8) + 32 # getter def get_temperature(self): print("Getting value… ") return self._temperature # setter def set_temperature(self, value): print("Setting value… ") if value < -273.15: raise ValueError("Temperature below -273.15 is not possible") self._temperature = value # creating a property object temperature = property(get_temperature, set_temperature) human = Celsius(37) print(human.temperature) print(human.to_fahrenheit()) human.temperature = -300

Resultado

 Definindo valor… Obtendo valor… 37 Obtendo valor… 98.60000000000001 Definindo valor… Traceback (última chamada mais recente): Arquivo "", linha 31, em Arquivo "", linha 18, em set_temperature ValueError: Temperatura abaixo de -273 não é possível

Como podemos ver, qualquer código que recupera o valor de temperaturechamará automaticamente em get_temperature()vez de uma consulta de dicionário (__dict__). Da mesma forma, qualquer código que atribua um valor a temperatureserá chamado automaticamente set_temperature().

Podemos até ver acima que set_temperature()foi chamado mesmo quando criamos um objeto.

 >>> human = Celsius(37) Setting value… 

Você consegue adivinhar por quê?

O motivo é que, quando um objeto é criado, o __init__()método é chamado. Este método tem a linha self.temperature = temperature. Esta expressão chama automaticamente set_temperature().

Da mesma forma, qualquer acesso como c.temperaturechamadas automaticamente get_temperature(). É isso que a propriedade faz. Aqui estão mais alguns exemplos.

 >>> human.temperature Getting value 37 >>> human.temperature = 37 Setting value >>> c.to_fahrenheit() Getting value 98.60000000000001

Ao usar property, podemos ver que nenhuma modificação é necessária na implementação da restrição de valor. Portanto, nossa implementação é compatível com versões anteriores.

Note: The actual temperature value is stored in the private _temperature variable. The temperature attribute is a property object which provides an interface to this private variable.

The @property Decorator

In Python, property() is a built-in function that creates and returns a property object. The syntax of this function is:

 property(fget=None, fset=None, fdel=None, doc=None)

where,

  • fget is function to get value of the attribute
  • fset is function to set value of the attribute
  • fdel is function to delete the attribute
  • doc is a string (like a comment)

As seen from the implementation, these function arguments are optional. So, a property object can simply be created as follows.

 >>> property() 

A property object has three methods, getter(), setter(), and deleter() to specify fget, fset and fdel at a later point. This means, the line:

 temperature = property(get_temperature,set_temperature)

can be broken down as:

 # make empty property temperature = property() # assign fget temperature = temperature.getter(get_temperature) # assign fset temperature = temperature.setter(set_temperature)

Esses dois códigos são equivalentes.

Os programadores familiarizados com os decoradores Python podem reconhecer que a construção acima pode ser implementada como decoradores.

Não podemos nem definir os nomes get_temperaturee set_temperaturecomo eles são desnecessários e poluem o namespace da classe.

Para isso, reutilizamos o temperaturenome ao definir nossas funções getter e setter. Vejamos como implementar isso como decorador:

 # Using @property decorator class Celsius: def __init__(self, temperature=0): self.temperature = temperature def to_fahrenheit(self): return (self.temperature * 1.8) + 32 @property def temperature(self): print("Getting value… ") return self._temperature @temperature.setter def temperature(self, value): print("Setting value… ") if value < -273.15: raise ValueError("Temperature below -273 is not possible") self._temperature = value # create an object human = Celsius(37) print(human.temperature) print(human.to_fahrenheit()) coldest_thing = Celsius(-300)

Resultado

 Definindo valor… Obtendo valor… 37 Obtendo valor… 98.60000000000001 Definindo valor… Traceback (última chamada mais recente): Arquivo "", linha 29, em Arquivo "", linha 4, em __init__ Arquivo "", linha 18, em temperatura ValueError: Temperatura abaixo de -273 não é possível

A implementação acima é simples e eficiente. É a forma de uso recomendada property.

Artigos interessantes...