Python @property decorador
Python @property decorador
Neste tutorial, você aprenderá sobre o decorador @property do Python; uma maneira Python de usar getters e setters na programação orientada a objetos.
A programação Python nos fornece um @property
integrado decorador que torna o uso de getters 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 armazena a temperatura em graus Celsius. 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 dessa classe e manipular o
temperature
atributo como desejamos:
# 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())
Saída
37 98.60000000000001
As casas decimais extras ao converter em Fahrenheit são devido ao erro aritmético de ponto flutuante. Para saber mais, visite Erro aritmético de ponto flutuante do Python.
Sempre que atribuirmos ou recuperarmos qualquer atributo de objeto como
temperature
como mostrado acima, o Python o pesquisa no __dict__
interno do objeto atributo de dicionário.
>>> human.__dict__
{'temperature': 37}
Portanto,
man.temperature
internamente se torna man.__dict__['temperature']
. Usando getters e setters
Suponha que queremos estender a usabilidade do Celsius classe 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
(torne-o privado) e defina 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 introduz dois novos
get_temperature()
e set_temperature()
métodos. Além disso,
temperature
foi 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())
Saída
37 98.60000000000001 Traceback (most recent call last): File "<string>", line 30, in <module> File "<string>", line 16, in set_temperature ValueError: Temperature below -273.15 is not possible.
Esta atualização implementou com sucesso a nova restrição. Não podemos mais definir a temperatura abaixo de -273,15 graus Celsius.
Observação :As variáveis privadas não existem realmente em Python. Há simplesmente normas a serem seguidas. A linguagem 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.temperature
para obj.get_temperature()
e todas as expressões como obj.temperature = val
para obj.set_temperature(val)
. Essa refatoração pode causar problemas ao lidar com centenas de milhares de linhas de código.
Em suma, nossa nova atualização não era compatível com versões anteriores. É aqui que
@property
vem resgatar. A classe da propriedade
Uma maneira Python de lidar com o problema acima é usar o
property
classe. 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 um
print()
função dentro de get_temperature()
e set_temperature()
observar claramente que eles estão sendo executados. A última linha do código cria um objeto de propriedade
temperature
. Simplificando, a propriedade anexa algum código (get_temperature
e set_temperature
) aos acessos de atributo de membro (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
Saída
Setting value... Getting value... 37 Getting value... 98.60000000000001 Setting value... Traceback (most recent call last): File "<string>", line 31, in <module> File "<string>", line 18, in set_temperature ValueError: Temperature below -273 is not possible
Como podemos ver, qualquer código que recupere o valor de
temperature
chamará automaticamente get_temperature()
em vez de uma pesquisa de dicionário (__dict__). Da mesma forma, qualquer código que atribua um valor a temperature
chamará 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ê?
A razão é 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.temperature
chama automaticamente get_temperature()
. Isso é o 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
Usando
property
, podemos ver que nenhuma modificação é necessária na implementação da restrição de valor. Assim, nossa implementação é compatível com versões anteriores. Observação :O valor real da temperatura é armazenado no
_temperature
privado variável. O temperature
attribute é um objeto de propriedade que fornece uma interface para essa variável privada. O decorador @property
Em Python,
property()
é uma função interna que cria e retorna um property
objeto. A sintaxe desta função é:
property(fget=None, fset=None, fdel=None, doc=None)
Onde,
fget
é função para obter o valor do atributofset
é função para definir o valor do atributofdel
é função para deletar o atributodoc
é uma string (como um comentário)
Como visto na implementação, esses argumentos de função são opcionais. Assim, um objeto de propriedade pode simplesmente ser criado da seguinte maneira.
>>> property()
<property object at 0x0000000003239B38>
Um objeto de propriedade tem três métodos,
getter()
, setter()
e deleter()
para especificar fget
, fset
e fdel
em um momento posterior. Isso significa que a linha:
temperature = property(get_temperature,set_temperature)
pode ser decomposto como:
# make empty property
temperature = property()
# assign fget
temperature = temperature.getter(get_temperature)
# assign fset
temperature = temperature.setter(set_temperature)
Esses dois pedaços de códigos são equivalentes.
Programadores familiarizados com Python Decorators podem reconhecer que a construção acima pode ser implementada como decoradores.
Nem podemos definir os nomes
get_temperature
e set_temperature
pois eles são desnecessários e poluem o namespace da classe. Para isso, reutilizamos o
temperature
name ao definir nossas funções getter e setter. Vamos ver como implementar isso como um 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)
Saída
Setting value... Getting value... 37 Getting value... 98.60000000000001 Setting value... Traceback (most recent call last): File "", line 29, in File " ", line 4, in __init__ File " ", line 18, in temperature ValueError: Temperature below -273 is not possible
A implementação acima é simples e eficiente. É a maneira recomendada de usar
property
. python