Vamos começar com os decoradores de Python.
Um decorador Python é uma função que ajuda a adicionar algumas funcionalidades adicionais a uma função já definida.
No Python, tudo é um objeto. Funções em Python são objetos de primeira classe, o que significa que podem ser referenciadas por uma variável, adicionadas nas listas, passadas como argumentos para outra função etc.
Considere o seguinte trecho de código.
def decorator_func(fun):
def wrapper_func():
print("Wrapper function started")
fun()
print("Given function decorated")
# Wrapper function add something to the passed function and decorator
# returns the wrapper function
return wrapper_func
def say_bye():
print("bye!!")
say_bye = decorator_func(say_bye)
say_bye()
# Output:
# Wrapper function started
# bye
# Given function decorated
Aqui, podemos dizer que a função decorador modificou nossa função say_hello e acrescentou algumas linhas de código extras.
Sintaxe Python para decorador
def decorator_func(fun):
def wrapper_func():
print("Wrapper function started")
fun()
print("Given function decorated")
# Wrapper function add something to the passed function and decorator
# returns the wrapper function
return wrapper_func
@decorator_func
def say_bye():
print("bye!!")
say_bye()
Vamos concluir tudo do que com um cenário de caso, mas antes disso vamos falar sobre alguns princípios do oops.
Getters e setters são usados em muitas linguagens de programação orientadas a objetos para garantir o princípio do encapsulamento de dados (é visto como o agrupamento de dados com os métodos que operam nesses dados).
Esses métodos são, obviamente, o getter para recuperar os dados e o setter para alterar os dados.
De acordo com esse princípio, os atributos de uma classe são privados para ocultar e protegê-los de outro código.
Sim, @property é basicamente uma maneira pitônica de usar getters e setters.
O Python possui um ótimo conceito chamado property, que simplifica muito a vida de um programador orientado a objetos.
Vamos supor que você decida fazer uma aula que possa armazenar a temperatura em graus Celsius.
class Celsius:
def __init__(self, temperature = 0):
self.set_temperature(temperature)
def to_fahrenheit(self):
return (self.get_temperature() * 1.8) + 32
def get_temperature(self):
return self._temperature
def set_temperature(self, value):
if value < -273:
raise ValueError("Temperature below -273 is not possible")
self._temperature = value
Código refatorado, veja como poderíamos ter conseguido isso com propriedade.
No Python, property () é uma função interna que cria e retorna um objeto de propriedade.
Um objeto de propriedade possui três métodos, getter (), setter () e delete ().
class Celsius:
def __init__(self, temperature = 0):
self.temperature = temperature
def to_fahrenheit(self):
return (self.temperature * 1.8) + 32
def get_temperature(self):
print("Getting value")
return self.temperature
def set_temperature(self, value):
if value < -273:
raise ValueError("Temperature below -273 is not possible")
print("Setting value")
self.temperature = value
temperature = property(get_temperature,set_temperature)
Aqui,
temperature = property(get_temperature,set_temperature)
poderia ter sido quebrado como,
# make empty property
temperature = property()
# assign fget
temperature = temperature.getter(get_temperature)
# assign fset
temperature = temperature.setter(set_temperature)
Ponto a observar:
- get_temperature permanece uma propriedade em vez de um método.
Agora você pode acessar o valor da temperatura escrevendo.
C = Celsius()
C.temperature
# instead of writing C.get_temperature()
Podemos continuar adiante e não definir os nomes get_temperature e set_temperature , pois são desnecessários e poluir o namespace da classe.
A maneira pitônica de lidar com o problema acima é usar @property .
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):
if value < -273:
raise ValueError("Temperature below -273 is not possible")
print("Setting value")
self.temperature = value
Pontos a serem observados -
- Um método usado para obter um valor é decorado com "@property".
- O método que deve funcionar como setter é decorado com "@ temperature.setter". Se a função tivesse sido chamada "x", teríamos que decorá-la com "@ x.setter".
- Escrevemos "dois" métodos com o mesmo nome e um número diferente de parâmetros "def temperature (self)" e "def temperature (self, x)".
Como você pode ver, o código é definitivamente menos elegante.
Agora, vamos falar sobre um cenário prático da vida real.
Digamos que você tenha projetado uma classe da seguinte maneira:
class OurClass:
def __init__(self, a):
self.x = a
y = OurClass(10)
print(y.x)
Agora, vamos supor que nossa classe ficou popular entre os clientes e eles começaram a usá-la em seus programas. Eles fizeram todos os tipos de atribuições ao objeto.
E um dia fatídico, um cliente confiável veio até nós e sugeriu que "x" deve ser um valor entre 0 e 1000, esse é realmente um cenário horrível!
Devido às propriedades, é fácil: criamos uma versão de propriedade de "x".
class OurClass:
def __init__(self,x):
self.x = x
@property
def x(self):
return self.__x
@x.setter
def x(self, x):
if x < 0:
self.__x = 0
elif x > 1000:
self.__x = 1000
else:
self.__x = x
Isso é ótimo, não é: você pode começar com a implementação mais simples que se possa imaginar e poderá migrar posteriormente para uma versão de propriedade sem precisar alterar a interface! Portanto, as propriedades não são apenas um substituto para getters e setter!
Você pode verificar esta implementação aqui