Prosseguindo com os tópicos mais teóricos, acredito que este pode ser o último tópico antes de começar a falar de programação orientada a objetos. Pra quem não viu o anterior, é importante entender sobre closures para entender decoradores, então vale uma revisada:
3.11 - Python - Introdução a decoradores - Closures - Tecnologia - Bastter.comQuem tá acompanhando o projeto web já viu a cara de um decorador. No projeto, estamos usando um decorador que tem esta cara:
@app.route('/')
Como foi visto no tópico de closures, ao definir uma função dentro de outra, a função interna tem acesso aos argumentos passados para a função externa ou a tudo que está no escopo da função externa. No tópico de closures usei este exemplo:
def media():
valores = []
def calcula_media(number):
valores.append(number)
return sum(valores)/len(valores)
return calcula_media
A função "calcula_media" tem acesso à lista criada dentro da função media, o que torna possível adicionar valores a essa lista.
Um decorador tem essa mesma cara da função "media" acima, com uma diferença. A função externa precisa receber como argumento uma outra função.
Mas antes, vamos contextualizar um pouco.
Já criamos exemplos de calculadoras nos nossos tópicos. Vamos criar um exemplo simples aqui:
# calculadora.py
def soma(x, y):
return x + y
def subtracao(x, y):
return x - y
def multiplicacao(x, y):
return x * y
def divisao(x, y):
return x / y
Então, usando nossa calculadora teremos um comportamento assim:
In [1]: soma(5, 7)
Out[1]: 12
In [2]: subtracao(12, 5)
Out[2]: 7
In [3]: multiplicacao(5, 2)
Out[3]: 10
In [4]: divisao(5, 2)
Out[4]: 2.5
Tudo certo, calculadora funcionando conforme esperado.
No entanto, eu quero uma calculadora, e uma calculadora só deve receber números e não strings. Se eu for burlar o funcionamento da calculadora passando strings para ela:
In [5]: soma('Bastter', '.com')
Out[5]: 'Bastter.com'
In [6]: subtracao('Bastter', '.com')
---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
<ipython-input-7-8508fc551ca1> in <module>
----> 1 subtracao('Bastter', '.com')
/run/media/thiago/Backup HDD/Programming/Bastter programação/Python/Posts/3.12/3.12.py in subtracao(x, y)
5
6 def subtracao(x, y):
----> 7 return x - y
8
9 def multiplicacao(x, y):
TypeError: unsupported operand type(s) for -: 'str' and 'str'
Ao usar a função de soma a calculadora vai concatenar as strings. Ao usar subtracao irá gerar um TypeError. Enfim, nossa calculadora não deve tentar fazer esse tipo de operação.
Uma forma é criar uma forma de validação dentro das funções da nossa calculadora. Algo assim:
# calculadora.py
from numbers import Number
def soma(x, y):
if not isinstance(x, Number) or not isinstance(y, Number):
return 'Você passou valores não numéricos, isso não é permitido'
return x + y
def subtracao(x, y):
if not isinstance(x, Number) or not isinstance(y, Number):
return 'Você passou valores não numéricos, isso não é permitido'
return x - y
def multiplicacao(x, y):
if not isinstance(x, Number) or not isinstance(y, Number):
return 'Você passou valores não numéricos, isso não é permitido'
return x * y
def divisao(x, y):
if not isinstance(x, Number) or not isinstance(y, Number):
return 'Você passou valores não numéricos, isso não é permitido'
return x / y
Tem forma muito melhor de fazer essa verificação de validação mas por enquanto vou deixar como está pois é uma forma fácil de entender.
_____________________________________________________________________________________
Breve disclaimer sobre o módulo numbers e a função isinstance:
Basicamente, acima eu usei o módulo numbers para ver se o que está sendo passado é número. E a função isinstance verifica se um valor é instância de um tipo específico:
In [1]: isinstance('Bastter', str)
Out[1]: True
In [2]: isinstance('Bastter', int)
Out[2]: False
In [3]: isinstance(5, int)
Out[3]: True
In [4]: isinstance(5, float)
Out[4]: False
In [5]: from numbers import Number
In [6]: isinstance(5, Number)
Out[6]: True
In [7]: isinstance(5.5, Number)
Out[7]: True
In [8]: isinstance('5.5', Number)
Out[8]: False
_____________________________________________________________________________________
Portanto, agora em cada função da calculadora há uma verificação pra saber se o que está sendo passado como argumento é valor numérico ou não:
In [1]: soma(5, 6)
Out[1]: 11
In [2]: soma('5', '6')
Out[2]: 'Você passou valores não numéricos, isso não é permitido'
In [3]: divisao('Bastter', '.com')
Out[3]: 'Você passou valores não numéricos, isso não é permitido'
Legal, a calculadora agora consegue validar o que é número do que não é.
No entanto, há muita repetição de código. Todas as funções têm este bloco de código que se repete:
if not isinstance(x, Number) or not isinstance(y, Number):
return 'Você passou valores não numéricos, isso não é permitido'
Daria pra criar uma função que faz essa validação. Mas aqui vou criar um decorador, que vai ter esta cara:
def decorador(func):
def decorada(a, b):
if not isinstance(a, Number) or not isinstance(b, Number):
return 'Você passou valores não numéricos, isso não é permitido'
return func(a, b)
return decorada
O que vai acontecer acima é que nosso decorador vai receber como argumento uma função "func", que será passada para a função interna "decorada", que será responsável por realizar a validação pra gente. Se a validação encontrar algum problema, irá retornar o erro, se não encontrar, ira retornar a execução da função nos argumentos passados "a" e "b".
Mudando agora nossas funções para esse formato:
# calculadora.py
from numbers import Number
def decorador(func):
def decorada(a, b):
if not isinstance(a, Number) or not isinstance(b, Number):
return 'Você passou valores não numéricos, isso não é permitido'
return func(a, b)
return decorada
@decorador
def soma(x, y):
return x + y
@decorador
def subtracao(x, y):
return x - y
@decorador
def multiplicacao(x, y):
return x * y
@decorador
def divisao(x, y):
return x / y
E testando:
In [1]: soma(5, 6)
Out[1]: 11
In [2]: soma('a', 'b')
Out[2]: 'Você passou valores não numéricos, isso não é permitido'
In [3]: divisao(10, 3.14)
Out[3]: 3.184713375796178
In [4]: divisao(10, '3.14')
Out[4]: 'Você passou valores não numéricos, isso não é permitido'
In [5]: multiplicacao('Bastter', 4)
Out[5]: 'Você passou valores não numéricos, isso não é permitido'
In [6]: multiplicacao('Bastter', '.com')
Out[6]: 'Você passou valores não numéricos, isso não é permitido'
In [7]: multiplicacao(10, 3.14)
Out[7]: 31.400000000000002
Notem que acabamos com a repetição de código usando os decoradores. E pra entender o funcionamento deles basta saber como uma closure funciona.
Sei que o assunto é relativamente complicado, eu mesmo demorei bastante pra entender como um decorador funciona, mas eles podem ser muito úteis para diminuir a repetição de código.