Modulo 014 – Criando suas funções em Python

 – Criando suas funções em Python – Functions no Python 

Diversas vezes usamos funções nos nossos códigos. O Python já nos fornece dezenas delas! Mas e se eu faço uma operação frequentemente? Será que poderíamos criar uma função que atenda essa atividade?
Veja o exemplo ao lado. Um simples, apenas para entendermos o conceito.
Imagine que sempre precisamos calcular a media de 2 notas. Vamos criar uma função calcular_media que nos permita realizar essa operação.

 #Operação rotineira: Cálculo da média 
 nota1 = input('Insira nota 1:'
 nota2 = input('Insira nota 2:'
 media = (float(nota1) + float(nota2))/2 
 print('A média das notas é : {}.'.format(media)) 
___________________
 Insira nota 1:7 
 Insira nota 2:6 
 A média das notas é : 6,5. 

 #Criando uma função calcular_media: 
 def calcular_media(nota1,nota2): 
     nota1 = input('Insira nota 1:'
     nota2 = input('Insira nota 2:'
     media = (float(nota1) + float(nota2))/2 
     return print('A média das notas é : {}.'.format(media))  

 #Chamando a função que acabamos de criar 
 calcular média(10,5
______________________
 Insira nota 1:5 
 Insira nota 2:6 
 A média das notas é : 5,5. 

Função calcular_media criada para esse programa que necessita de 2 parâmetros para rodar, nota1 (Ex:10) e nota2 (Ex:5).
Vamos entender a estrutura no Python para a criação de uma função.
def indica que uma função será definida.

 def nome_da_função(argumento1, argumento2, argumentoN): 
     Ação 1 a ser realizada pela função; 
     Ação 2 a ser realizada pela função; 
     Ação 2 a ser realizada pela função; 
     return O que a função retornará ao rodar a função 

– Retornar um valor na Function

Vamos voltar para nosso exemplo anterior onde calculamos a média de duas notas.
Primeiros vamos entender o que é “Retornar um valor”.
Como estamos falando em functions, estamos falando de criarmos um código que a partir de algum input ela processa os dados e retorna um resultado.
No nosso caso por exemplo:
Inputs → nota1 e nota2
Resultado retornado → Média das notas

Se olharmos o código da nossa função calcular_media, veremos que ao final temos uma linha RETURN. Essa linha informa o que será retornado nessa função. No nosso exemplo, um print com a média das notas. Essa resposta geralmente são: Listas, variáveis, dicionários, etc...

– Argumentos e parâmetros em uma Function

Como citamos no slide anterior, para que essa função funcione são necessários dois inputs:
• nota1;
• nota2.
Esses inputs são chamados de argumentos ou parâmetros.
Importante dizer que os argumentos não são obrigatórios.
Os argumentos serão sempre definidos dentro dos parênteses da nossa linha de código que se inicia com def.

– Aplicação em um Exemplo de argumento

Agora vamos ver um exemplo onde teremos que criar uma function para resolver o desafio. O desafio consiste em analisar uma lista de produtos e enviar instruções dizendo quais produtos devem ser enviados para a área de bebidas alcóolicas.
Cada produto tem um código, por exemplo:
Vinho → BEB12302
Guaraná → BSA11104
As bebidas não alcóolicas começam com BSA, enquanto
as bebidas alcóolicas começam com BEB.

 def ehalcoolico(bebida): 
     bebida = bebida.upper() 
     if 'BEB' in bebida: 
         return True 
     else: 
         return False 
     
 produtos = ['beb46275','TFA23962','TFA64715','TFA69555','TFA56743','BSA45510','TFA44968','CAR75448','CAR23596','CAR13490','BEB21365','BEB31623','BSA62419','BEB73344','TFA20079','BEB80694','BSA11769','BEB19495','TFA14792','TFA78043','BSA33484','BEB97471','BEB62362','TFA27311','TFA17715','BEB85146','BEB48898','BEB79496','CAR38417','TFA19947','TFA58799','CAR94811','BSA59251','BEB15385','BEB24213','BEB56262','BSA96915','CAR53454','BEB75073'
                     
 #percorrer toda a minha lista de produtos 
 #pra cada produto, verificar se ele é bebida alcoolica 
 #se for bebida alcoolica, exibir a mensagem Enviar.... 
                     
 for produto in produtos: 
     if ehalcoolico(produto): 
         print('Enviar {} para setor de bebidas alcóolicas'.format(produto)) 
____________________________________
 Enviar beb46275 para setor de bebidas alcóolicas 
 Enviar BEB21365 para setor de bebidas alcóolicas 
 Enviar BEB31623 para setor de bebidas alcóolicas 
 Enviar BEB73344 para setor de bebidas alcóolicas 
 Enviar BEB80694 para setor de bebidas alcóolicas 
 Enviar BEB19495 para setor de bebidas alcóolicas 
 Enviar BEB97471 para setor de bebidas alcóolicas 
 Enviar BEB62362 para setor de bebidas alcóolicas 
 Enviar BEB85146 para setor de bebidas alcóolicas 
 Enviar BEB48898 para setor de bebidas alcóolicas 
 Enviar BEB79496 para setor de bebidas alcóolicas 
 Enviar BEB15385 para setor de bebidas alcóolicas 
 Enviar BEB24213 para setor de bebidas alcóolicas 
 Enviar BEB56262 para setor de bebidas alcóolicas 
 Enviar BEB75073 para setor de bebidas alcóolicas 

• Function que retorna True se a bebida for alcóolica e False se a bebida não for alcóolica.
• A lista de produtos é percorrida e cada produto é enviado como argumento da função ehalcoolico e testado se é uma bebida alcóolica.
• Caso seja uma bebida alcóolica, uma mensagem com o código do produto será printada.

– Vários parâmetros e tipos de parâmetros

Na nossa função Calcular_media, já usamos mais de um parâmetro.
Mas vamos entender alguns detalhes de como trabalhar com mais de um parâmetro de uma função.
Existem duas formas distintas de informar parâmetros para funções:
1) Em ordem (positional argument);
2) Com o nome do argumento (keyword argument)
Vamos entender essa diferença melhor olhando o exemplo ao lado:
Aqui temos uma função que classifica produtos baseado na sua categoria. Em especial, as bebidas, se são alcóolicas ou não.

 def eh_da_categoria(bebida, cod_categoria): 
     bebida = bebida.upper() 
     if cod_categoria in bebida: 
         return True 
     else: 
         return False 

Argumentos na mesma ordem:
 #Usando ordem: 
 for produto in produtos: 
     if eh_da_categoria(produto,'BEB'): 
         print('Enviar {} para setor de bebidas alcóolicas'.format(produto)) 
     elif eh_da_categoria(produto, 'BSA'): 
         print('Enviar {} para setor de bebidas não alcóolicas'.format(produto)) 

Argumentos por palavra chave:
 #Usando palavra chave: 
 for produto in produtos: 
     if eh_da_categoria(cod_categoria = 'BEB', bebida=produto): 
         print('Enviar {} para setor de bebidas alcóolicas'.format(produto)) 
     elif eh_da_categoria(produto, 'BSA'): 
         print('Enviar {} para setor de bebidas não alcóolicas'.format(produto)) 

ERRO!:
 #Só podemos usar um método, o caso abaixo retornará erro: 
 for produto in produtos: 
     if eh_da_categoria(cod_categoria = 'BEB', produto): 
         print('Enviar {} para setor de bebidas alcóolicas'.format(produto)) 
     elif eh_da_categoria(produto, 'BSA'): 
         print('Enviar {} para setor de bebidas não alcóolicas'.format(produto)) 

Vamos entender um pouco melhor o erro do slide anterior.
Quando usamos um parâmetro de keyword no início da declaração de parâmetros, devemos seguir usando Keywords para todos os outros argumentos.
Vamos entender um caso onde podemos usar tanto os argumentos de posição quanto de palavras-chaves em uma mesma linha de código. Vamos aproveitar para aprender um novo argumento: sep =
Este argumento nos ajuda a apresentar os dados separando-os conforme a informação que seja fornecida por nós.
Vamos para o exemplo:

 qtde_produtos = len(produtos) 
 print('Quantidade total de produtos:', qtde_produtos, 'texto2''texto3', sep = '\n'
___________________
 Quantidade total de produtos: 
 39 
 texto2 
 texto3 

– Dicas e Exemplos com Argumentos em Function

Nessa aula vamos ver alguns exemplos de funções que já usamos e seus respectivos parâmetros (de palavras-chaves, de posicionamento ou sem parâmetros).
• upper() → não tem parâmetros
Se tentarmos passar algum valor como parâmetro no upper(), ocorrerá um erro.

 cod_produto = 'beb12304' 
 print(cod_produto.upper()) 
_______________
 BEB12304 

• sort()  apenas parâmetros keyword
Ordenando uma lista em ordem decrescente através do método sort(). Para isso, devemos escrever: reverse = True.
O argumento reverse é um argumento de palavra-chave.

 vendas_ano = [100, 200, 50, 90, 240, 300, 55, 10, 789, 60
 vendas_ano.sort(reverse = True
 print(vendas_ano) 
___________________
 [789, 300, 240, 200, 100, 90 60, 55, 50, 10] 

Para saber mais sobre uma determinada função e quais tipos de parâmetros essa função aceita, o recomendado é que você faça uma pesquisa na documentação dessa função.
• extend(lista)  1 parâmetro obrigatório
O extend possui 1 parâmetro de posição que recebe uma lista vendas_novdez e adiciona os itens dessa lista dentro da lista vendas_ano

 vendas_ano = [1002005090240300551078960
 vendas_novdez = [500, 1555
 vendas_ano.extend(vendas_novdez) 
 print(vendas_ano) 
___________________
 [100, 200, 50, 90, 240, 300, 55, 10, 789, 60, 500, 1555] 

• Nossa função eh_da_categoria(bebida, cod_categoria)  2 parâmetros de posição obrigatórios
A função eh_da_categoria possui 2 parâmetros de posição.
Dica: Quando você não se lembrar de quais valores determinada função precisa para funcionar, no jupyter e com o cursor no nome da função, você deverá pressionar SHIFT + TAB para as informações sobre essa função serem mostradas.

 def eh_da_categoria(bebida, cod_categoria): 
     bebida = bebida.upper() 
     if cod_categoria in bebida: 
         return True 
     else: 
         return False 

 if eh_da_categoria(cod_produto, 'BEB'): 
     print('É uma bebida alcoolica'
___________________
 É uma bebida alcoolica 

– Valores padrões para argumentos

Já reparou que em algumas funções existem padrões já estabelecidos no Python? Não? Vamos para um caso:
Método .sort(). Sempre que fazemos este método, o padrão do Python é ordenar os dados de forma crescente como na figura ao lado. No entanto, podemos forçar o Python a não usar o padrão fornecendo a ele uma palavra-chave: sort(reverse = True)
Assim, a nossa lista será ordenada de forma decrescente. O que acontece aqui é que o padrão do Python omite sua configuração padrão: sort(reverse=False)
Isso acontece pois a função sort foi programada dessa forma!

 vendas = [100,30,70,94,15,65,35
 vendas.sort() 
 print('Ordem crescente'
 print(vendas) 
 vendas.sort(reverse=True) 
 print('Ordem decrescente'
 print(vendas) 
_______________
 Ordem crescente 
 [15,30,35,65,70,94,100] 
 Ordem decrescente 
 [100,94,70,65,35,30,15] 

O mais interessante, é que podemos fazer exatamente a mesma coisa nas funções criadas
por nós! Vamos para um exemplo que cria códigos de produtos. No entanto, temos duas possibilidades de código:
• Todo em letras maiúsculas (M);
• Todo em letras minúsculas (m).
Nosso padrão será minúsculas, mas o usuário poderá usar a palavra chave padrão=‘M’ para alterar para letras maiúsculas. Perceba que usamos o IF para definir o que
acontecerá dependendo do padrão fornecido pelo usuário.
Ao lado apresentamos a função e o resultado dos casos. Perceba que no primeiro caso o usuário não fornece nenhum argumento e o padrão MINÚSCULO é atendido.

 def padronizar_codigos(lista_codigos, padrao='m'): 
     for i, item in enumerate(lista_codigos): 
         item = item.replace('  ', ' '
         item = item.strip() 
         if padrao == 'm'
             item = item.casefold() 
         elif padrao == 'M'
             item = item.upper() 
         lista_codigos[i] = item 
     return lista_codigos 
            
 cod_produtos = [' ABC12 ', 'abc34', 'AbC37'
 print(padronizar_codigos(cod_produtos)) 
____________________
 ['abc12', 'abc34', 'abc37'] 

 cod_produtos = [' ABC12 ''abc34''AbC37'
 print(padronizar_codigos(cod_produtos,padrao='M')) 
____________________
 ['ABC12', 'ABC34', 'ABC37']

– Falando um pouco mais dos returns das functions

Como vimos anteriormente o return nos retorna sempre o “resultado” da nossa função.
É possível retornar booleans, integers, floats, variáveis, etc.
No entanto, é muito importante explicar que o return funciona como um FIM de uma função. Ou seja, se a função chegar até a linha de código RETURN ela irá encerrar a função mesmo que se trate de um FOR como no exemplo ao lado.
Essa função não faz muito sentido de existir, mas podemos ver que apesar de termos definido que nossa função retorna o valor i com um range de 5, apenas o valor 0(PRIMEIRA ITERAÇÃO DO FOR) foi retornado. Por que? Porque ao chegar ao return, a nossa função chega ao FIM.

 def fazer_for(): 
     for i in range(5): 
             return
    
 print(fazer_for()) 
________________
 0 

Ao chegarmos nessa linha de código, a função será interrompida. Ou seja, só haverá UMA iteração no FOR e não CINCO como definimos no RANGE
Retorno esperado: 0 ,1,2,3,4
Retorno obtido: 0  Apenas a primeira iteração do FOR

– Return com mais de uma informação

Vamos aprofundar um pouco mais no RETURN. Nossas funções até agora, em geral, são simples e possuem retornos igualmente simples com apenas um valor por exemplo.
No entanto, é possível que o retorno necessário seja um pouco mais complexo com mais
resultados. Ao lado temos uma função ainda bem simples, mas que nos permite entender o conceito. Dentro da nossa função operações_básicas calculamos resultados como soma, diferença, multiplicação e divisão.
Uma informação importante é que o retorno é uma TUPLA. Isso nos permitirá trabalhar melhor com esses dados.

 def operacoes_basicas(num1, num2): 
     soma = num1 + num2 
     diferenca = num1 - num2 
     mult = num1 * num2 
     divisao = num1 / num2 
     return soma, diferenca, mult, divisao 
                   
 print(operacoes_basicas(10, 2)) 
______________________
 (12, 8, 20, 5.0) 

• Ao invés de apenas 1 resultado, temos 4 resultados da nossa função.
• Retorno de todas as variáveis calculadas dentro da função em uma TUPLA.

Vamos para um caso um pouco mais complexo.
Nesse caso estamos avaliando quais vendedores bateram a meta. Podemos perceber que nossa função retorna uma tupla com 2 valores:
• % que bateram a meta(perc_baterammeta);
• Pessoas que bateram a meta (vendedores_acima_media)

 meta = 1000 
 vendas =
     'João':15000
     'Julia':27000
     'Marcus':9900
     'Maria':3750
     'Ana':10300
     'Alon':7870
 } 
       
 def calculo_meta(meta,vendas): 
     bateram_meta = [] 
     for vendedor in vendas: 
          if vendas[vendedor]>=meta: 
              bateram_meta,append(vendedor) 
     perc baterammeta = len(bateram_meta)/len(vendas) 
     return perc_baterammeta, bateram_meta 
                
 p_meta, vendedores_acima_meta = calculo_meta(meta,vendas) 
 print(p_meta) 
 print(vendedores_acima_meta) 
_________________
 0.5 
 ['João',  'Julia', ' Ana'] 

• Retorno da função será uma TUPLA

– Docstrings e annotations

Não obrigatoriamente todas as funções criadas serão usadas apenas por você, por isso é
importante que elas sejam organizadas e compreensíveis por outras pessoas. Essa organização passa pelos:
Docstrings : Iniciados por ‘’’ permitem strings de mais de uma linha. Aqui você pode descrever o que sua função faz, argumentos que ela possui, etc. Você pode criar o seu padrão, mas caso não possua um, temos um exemplo no print ao lado.

 def minha_funcao(arg1,arg2,arg3): 
     ''' O que minha função faz. 
                  
     Meus argumentos: 
     arg1(tipo do meu argumento):Comentário sobre o argumento 
     arg2(tipo do meu argumento):Comentário sobre o argumento 
     arg3(tipo do meu argumento):Comentário sobre o argumento 
                  
     return 
     O retorno da minha função 
     ''' 
               
     soma = arg1+arg2+arg3 
     return soma 
            
 minha_função() 

• Informações podem ser acessadas usando SHIFT+TAB quando o cursor estiver na função desejada

 Signature: minha_funcao(arg1, arg2, arg3) 
 Docstring:
 O que minha função faz. 
          
 Meus argumentos: 
 arg1(tipo do meu argumento):Comentário sobre o argumento 
 arg2(tipo do meu argumento):Comentário sobre o argumento 
 arg3(tipo do meu argumento):Comentário sobre o argumento 

Não obrigatoriamente todas as funções criadas serão usadas apenas por você, por isso é
importante que elas sejam organizadas e compreensíveis por outras pessoas.
Essa organização passa pelos:
Annotations : É uma versão mais simplificada de um docstrings. As informações sobre a função são escritas dentro da própria função ao ser definida.

 def minha_funcao(arg1: int , arg2: float , arg3: int) -> float
     return arg1 + arg2 + arg3 
                   
 minha_funcao(1,5.1,3
___________________
 9.1 

– Exceções e erros em funções

Até agora, sempre que nossos códigos acusavam algum erro, significava que seu programa seria encerrado. No entanto, pode ocorrer uma situação em que o resultado ou o valor inserido pelo usuário está errado, mas você não quer que ele pare nesse momento, e sim que o corrija.
Para esses casos podemos usar o TRY e o EXCEPT.
SEMPRE que o TRY for utilizado, deverá haver um EXCEPT

 try:
     o que eu quero tentar fazer 
 except:
     o que vou fazer caso dê erro 

• Indentação indica que essas linhas pertencem ao TRY e EXCEPT

Primeiro vamos entender o que são os erros na realidade. Sabemos que são um pouco assustadores, principalmente no começo. Milhões de linhas de código que não são fáceis de entender. Vamos pegar um exemplo para analisar e depois vamos voltar para nosso TRY e EXCEPT e como usá-lo.

 print(erro) 
_____________
 NameError: name 'erro' is not defined 

Bem, agora que entendemos um pouco mais sobre a estrutura de um erro fornecido pelo Python, vamos usar o Try e Except para criar o nosso próprio erro, que nos permita informar ao usuário. Vamos criar uma função que indica qual o tipo de e-mail utilizado pelo usuário. Perceba que no bloco EXCEPT usamos ao invés do return, o RAISE. Isso fará com que um erro seja exibido ao usuário. Veremos isso melhor:

 def descobrir_servidor(email): 
     try:
         posicao_a = email.index('@')
     except:
         raise ValueError('Email digitado não tem @, digite novamente')
     else:
         servidor = email[posicao_a:]
         if 'gmail' in servidor:
             return 'gmail'
         elif 'hotmail' in servidor or 'outlook' in servidor or 'live' in servidor:
             return 'hotmail'
         elif 'yahoo' in servidor:
             return 'yahoo'
         elif 'uol' in servidor or 'bol' in servidor:
             return 'uol'
         else:
             return 'não determinado'

 email = input('Qual o seu e-mail?'
 print(descobrir_servidor(email)) 

Bem, agora que entendemos um pouco mais sobre a estrutura de um erro fornecido pelo Python, vamos usar o Try e Except para criar o nosso próprio erro, que nos permita informar ao usuário. Vamos criar uma função que indica qual o tipo de e-mail utilizado pelo usuário. Perceba que no bloco EXCEPT usamos ao invés do return, o RAISE. Isso fará com que um erro seja exibido ao usuário. Veremos isso melhor:

 def descobrir_servidor(email): 
     try
         posicao_a = email.index('@'
     except
         raise ValueError('Email digitado não tem @, digite novamente'
     else: 
____________________________
 Qual seu email?hashtaggmail.com 
--------------------------------------------------------
 ValueError                                Traceback (most recent call last) 
 Input In [6], in descobrir_servidor(email) 
       2 try
 ----> 3     posicao_a = email.index('@') 
       4 except
                     
 ValueError: substring not found 
                    
 During handling of the above exception, another exception occurred: 
                         
 ValueError                                Traceback (most recent call last) 
 Input In [7], in <cell line: 2>() 
       1 email = input('Qual o seu e-mail?'
 ----> 2 print(descobrir_servidor(email)
              
 Input In [6], in descobrir_servidor(email) 
          posicao_a = email.index('@'
       4 except
 ----> 5     raise ValueError('Email digitado não tem @, digite novamente'
       6 else
           servidor = email[posicao_a:] 
                       
 ValueError: Email digitado não tem @, digite novamente 
              ↖  Usuário digita e-mail sem @. Erro personalizado 

– Múltiplos Argumentos para uma Function

Voltando para o nosso exemplo de cálculo de média das notas. No caso anterior criamos 2
argumentos nota1 e nota2, existe uma outra forma que podemos fazer essa declaração de
variáveis. Você verá em alguns lugares durante pesquisas o termo *args.
Usando o * antes da variável indicamos que não estamos tratando de um argumento único e sim um argumento que poderá receber um número ilimitado de valores.
Podemos ver no exemplo que ao usarmos *notas permitimos não só ter apenas 2 notas mas 3, 4 ,5 ou 10...

• Ao invés de limitar o número de notas, criamos um argumento de tamanho variável

 def calcular_media(*notas) 
     soma=0 
     for nota in notas: 
         soma += nota 
     return soma/(len(notas)) 
                    
 print(calcular_media(10,0,5,6,7,8,9,1,0,4,8,6,4,8,9,4,2,2,2,3,4,5,8,9)) 
 print(calcular_media(10,0)) 
_________________
 5.166666666666667 
 5.0 

Como vimos, temos tanto argumentos de posição como argumentos Keywords (palavras-chave). O conceito é exatamente o mesmo usado no exemplo anterior. Ao usarmos o ** antes da palavra-chave, temos uma função que aceita receber diversas palavras chaves na função. Esse conceito é comumente visto com **kwargs.

Ao lado temos um exemplo deste caso. Aqui o usuário poderá (ou não) passar informações para que influenciem no preço final do produto. No caso dos **kwargs, é importante entender o que a função faz. Olhando dentro da nossa função preco_final, vemos que existem três variáveis que influenciam o preço:
• desconto;
• garantia_extra;
• Imposto.

 def preco_final(preco, **adicionais): 
     print(adicionais)
     if 'desconto' in adicionais: 
         preco *= (1 - adicionais['desconto']) 
     if 'garantia_extra' in adicionais: 
         preco += adicionais['garantia_extra']  
     if 'imposto' in adicionais: 
         preco *= (1 + adicionais['imposto']) 
     return preco 

 print(preco_final(1000, desconto=0.1, garantia_extra = 100, imposto=0.3)) 
____________________________
 {'desconto': 0.1, 'garantia_extra': 100, 'imposto': 0.3}
1300.0 

• Perceba que mesmo sem usar todos os argumentos nosso valor final foi calculado.

– Ordem dos Tipos de Argumento

Ok. Entendi que tenho liberdade de ter *args ou **kwargs, mas por enquanto só vimos isso separadamente. E se eu tiver um caso mais confuso com 3 kwargs, 2 args? Tem um jeito certo de fazer? Ou é só sair colocando?
Temos 2 regras para seguir:
1) Primeiro colocamos os argumentos de posição (args), depois os de palavra chave (kwargs).
2) Seguindo a ordem anterior, primeiro fazemos os argumentos simples (sem * ou **) e depois os múltiplos (com * ou **).
Vamos dar uma olhada no estrutura abaixo para entendermos melhor.

 def minha_funcao(arg1, arg2, arg3, arg4, *args, k = kwarg1, k2 = kwarg2, k3 = kwarg3, **kwargs):

Nenhum comentário:

Postar um comentário