Funções
Funções em Python são blocos de código reutilizáveis que realizam uma tarefa específica. Elas permitem organizar, estruturar e tornar o código mais eficiente, além de melhorar a legibilidade e facilitar a manutenção.
Entrada: As funções podem receber argumentos, que são valores passados ao chamá-las.
Saída: Podem retornar um valor usando a instrução return. As funções se "transformam" no resultado. Se for vazio, encerra a função e retorna: None
Parametro vs Argumento:
- Parametro é o nome dos dados que estão sendo passados
- Argumentos são os valores dados reais, os dados passados para a função.
Quando se apaga a referencia da função com a palavra del, não apaga a função em si, pois isso é feito pelo trash quando nenhuma outra variável estiver apontando para essa função.
# A Variável nome, nesse caso, é chamada de parâmetro
def saudacao(nome):
return "Olá, " + nome
# Os dados passados são chamados de argumento. Nesse caso Pedro.
print(saudacao("Pedro"))
# Usa o método lower da String para tornar minúsculo, se o retorno fosse uma lista poderia usar qualquer método das listas
saudacao("DIOGO").lower()
# Função com retorno booleno
def maior_que_cinco(num):
return num > 5
# Insere/referência o espaço de memória da função em uma variável
boas_vindas = saudacao
# Apago a referencia da função
del saudacao
# Usa o a nova referencia para chamar a função
print(boas_vindas("Diogo"))
def hello():
return 'Hi Jose!'
def other(func):
print('Other code would go here')
print(func())
other(hello)
As funções em Python são objetos de primeira classe, o que significa que podem ser passadas como valores/argumentos.
Função Recursiva
Uma função que chama a si propria é conhecida como função recursiva.
def fatorial(n):
# Condição de parada
if n == 0 or n == 1:
return 1
else:
# Chamada recursiva
return n * fatorial(n - 1)
# Saída: 120
print(fatorial(5))
Ordem dos Argumentos
Argumentos posicionais: Quando existe mais de um argumento, a ordem na qual é passada será recebida nos parâmetros na mesma ordem.
Argumentos de palavras-chave (keyword arguments): Você pode definir em quais parâmetros seus argumentos podem ser inseridos em ordem aleatoria.
Quando uma funcão é definida com valores padrões, não é necessário envia-los ao chamar a função.
| Ordem dos Argumentos
def calculo(a, b, c):
print(f"{a},{b},{c}")
calculo(15,12,9) # Saída: a=15, b=12 e c=9
def calculo(a, b, c):
print(f"{a},{b},{c}")
calculo(c=15, b=12, a=9) # Saída: 9,12,15
def soma(a=1, b=2):
return a + b
# Não é necessário passar nada caso não queira alterar o valor
print(soma()) # Saída: 3
print(soma(a=6)) # Saída: 8
Argumentos Variáveis
Em Python, você pode passar um número variável de argumentos para uma função. Existem duas formas principais de fazer isso:
*args(Argumentos Posicionais Variáveis)**kwargs(Argumentos de Palavra-Chave Variáveis)
obs.: as palavras args e kwargs é uma convenção, mas você pode usar qualquer nome, desde que o prefixo * ou ** seja mantido
Argumentos de Posicionais Variáveis (*args)
Permite que a função receba um número variável de argumentos posicionais (não nomeados), em forma de tuplas.
Argumentos de Palavra-Chave Variáveis (**kwargs)
Permite que a função receba um número variável de argumentos nomeados (ou seja, pares chave-valor), em forma de dicionario.
| Exemplos
def soma(*args):
# Imprime uma tupla
print(args)
return sum(args)
# Passando 4 argumentos
resultado = soma(1, 2, 3, 4)
print(resultado)
lista_argumentos_variaveis = [1,2,3]
def soma(*args):
# Imprime uma tupla
print(args) # Saída: (1,2,3)
return sum(args)
resultado = soma(*lista_argumentos_variaveis) # Saída: 6
O operador * nesse contexto é usado para desempacotar a lista e passar seus elementos como argumentos individuais para a função.
def imprime_info(**kwargs):
# imprime um dicionario
print(kwargs)
for chave, valor in kwargs.items():
print(f"{chave}: {valor}")
imprime_info(nome="Maria", idade=30, cidade="São Paulo")
def imprime_info(**kwargs):
# imprime um dicionario
print(kwargs)
for chave, valor in kwargs.items():
print(f"{chave}: {valor}")
dicionario_argumentos_variaveis = {
'nome':'Diogo',
'idade': 32,
'cidade':'São Paulo',
}
imprime_info(**dicionario_argumentos_variaveis)
O operador ** nesse contexto é usado para desempacotar o dicionário e passar seus pares como argumentos individuais para a função.
def funcao_completa(*args, **kwargs):
print("Args:", args)
print("Kwargs:", kwargs)
funcao_completa(1, 2, 3, nome="Maria", idade=30)
# saída: Args: (1, 2, 3) Kwargs: {'nome': 'Maria', 'idade': 30}
class Carro:
def __init__(self, **kwargs):
self.modelo = kwargs["modelo"]
self.marca = kwargs.get("marca")
meu_carro = Carro(modelo="Corsa")
print(meu_carro.modelo)
print(meu_carro.marca) # Saída será None pois não foi inicializado no construtor.
Exemplo de como criar uma classe que recebe kwargs para inicializar. Inicializar as variáveis do objeto pelo colchetes [], o argumento torna-se obrigatório ao criar o objeto, pelo metodo get torna-se opcional (caso não declare o valor é None).
Função Lambda
Uma função lambda é uma função anônima e pequena, definida diretamente dentro de uma expressão, elas geralmente são utilizadas quando você precisa de uma função simples e rápida, que será usada apenas uma vez ou em poucas situações.
Sintaxe:
lambda argumentos: expressão
A função lambda não suporta varias expressões
| Comparativo Lambda
soma = lambda x,y: x + y
print(soma(10,5))
def a(x,y):
return x ** 2
a(10,5)
Escopo e Namespace
Escopo de uma variável ou função é o conjunto de código em que está definida, ou seja, define a "visibilidade" de um nome em uma parte específica do código. Escopos seguem a regra LEGB (Local, Enclosing, Global, Built-in) para a hierarquia de busca de nomes, e são usados para controlar quais namespaces podem acessar ou modificar certos nomes.
-
Local (L): procura às variáveis definidas dentro de uma função ou bloco.
-
Enclosing (E): Refere-se ao escopo das funções externas (não globais) que envolvem a função onde a variável está sendo referenciada. Aplicável em casos de funções aninhadas.
-
Global (G): Refere-se às variáveis definidas no nível global do módulo, fora de qualquer função ou classe
-
Built-in (B): Refere-se ao escopo das funções e nomes pré-definidos pelo Python, como
len(),print(),str(), entre outros.
Namespace (ou "espaço de nomes") é um sistema que mapeia nomes para objetos. Em outras palavras, um namespace funciona como uma tabela que relaciona um nome com o objeto que ele representa (variáveis, funções, classes, etc.). Tudo que você da um nome tem um namespace e é valido dentro de um escopo.
Em Python, não há escopo de bloco, Ou seja, estruturas de controle como if, for, e while não criam novos escopos. Variáveis definidas dentro desses blocos ainda pertencem ao escopo que as envolve, local da função ou global do módulo.
# x está no namespace global e escopo global
x = 10
def outer_function():
# y está no namespace e escopo local de outer_function
y = 20
def inner_function():
# z está no namespace e escopo local de inner_function
z = 30
# Acessa x no escopo global
print(x)
# Acessa y no escopo de enclosing
print(y)
# Acessa z no escopo local de inner_function
print(z)
# Aqui podemos acessar y mas não z
inner_function()
outer_function()
| Comparativo Escopo
x = 10
def outer_function():
# declara que x é global e pode ser modificado
global x
# agora x é modificado
x = 20
print(x)
outer_function()
É possível modificar uma variável global a partir de um escopo local em Python, mas isso requer o uso explícito da palavra-chave global. Sem essa palavra-chave, o Python interpretará qualquer atribuição como a criação de uma nova variável local.
Obs.: É possível pode visualizar a variável global dentro do escopo local, assim como pode-se modificar o conteúdo de dicionários, listas e outros objetos mutáveis dentro de escopos locais, mesmo sem usar a palavra-chave global. Isso ocorre porque alterar o conteúdo de um objeto mutável não é o mesmo que reatribuir a variável em si.
x = 10
def outer_function():
return x + 10
# modifiquei o valor para 20 sem precisar usar o global
x = outer_function()
print(x)
Não é uma boa pratica ficar modificando variáveis globais dentro dos escopos locais, uma boa solução seria retornar o valor.
# Imprime as variáveis de escopo local
print(locals())
# Imprime as variáveis de escopo global
print(globals())
Funções Embutidas (Built-in)
São funções disponíveis globalmente na linguagem de programação e não estão vinculados a um objeto específico.
Em Python, callables são objetos que podem ser chamados como se fossem funções, ou seja, você pode invocar ou "chamar" esses objetos com parênteses (). Isso inclui funções, métodos, e até objetos que implementam o método especial __call__.
MAP vs FILTER
| Característica | map |
filter |
|---|---|---|
| Objetivo | Transformar os elementos do iterável. | Selecionar elementos com base em uma condição. |
| Retorno | Um iterável com os elementos transformados. | Um iterável com os elementos filtrados. |
| Função usada | Aceita uma função que transforma valores. | Aceita uma função que retorna True ou False. |
| Entrada | Um iterável. | Um iterável. |
| Exemplo comum | Aplicar transformações matemáticas ou modificar strings. | Filtrar números pares, palavras curtas, etc. |
saudacao = "Hello"
lista_inteiro = [1, 2, 3]
# Recebe dados do usuário
input("entrada")
# Imprime na tela
print(saudacao)
# Mostra todos os métodos embutido de algum objeto,
dir(saudacao)
# chamada sem argumento lista os nomes (variáveis, funções etc) do escopo atual
dir()
# Retorna o comprimento do objeto ou a quantidade de itens em uma lista
len(saudacao)
# Soma um iterável ou gerador de números
sum(lista_inteiro)
# Trabalha com inteiros e strings
max(lista_inteiro)
dados = {"a": 10, "b": 5, "c": 20}
print(max(dados, key=dados.get)) # Saída: 'c' (maior valor: 20)
palavras = ["maçã", "banana", "uva"]
print(max(palavras, key=len)) # Saída: 'banana'
# Funciona como a função max, mas retorna o menor valor
min(lista_inteiro)
# Arredonda casas decimais, o segundo parâmetro é a casa decimal que deve ser arredondada
round(8/3, 2)
# compara tipos
isinstance(saudacao, int)
# Confere se o valor (10.5) é um inteiro OU flutuante
isinstance(10.5,(int,float))
valores = [1,1.5,2]
resultado_lista_bool = [isinstance(valor,int) for valor in valores]
print(resultado_lista_bool) # Saída: [True, False, True]
booleanos = [True,False,True]
# confere se todos os valores passados são verdadeiros
all(booleanos) # Saída: False
print(all(isinstance(valor,int) for valor in valores)) # Saída: False
# confere se algum valor passado é verdadeiro
any(booleanos) # Saída: True
print(any(isinstance(valor,int) for valor in valores)) # Saída: True
# Retorna o próximo item de um iterador ou gerador.
next()
numeros = [1, 3, 7, 8, 10]
# Itera pelos números e encontrar o primeiro par
primeiro_par = next((num for num in numeros if num % 2 == 0), None)
print(primeiro_par)
# Retorna o dicionário de atributos de uma instância de objeto ou de uma classe.
print(vars(Objeto)) # Se for vazio, equivale a função local
# Verifica se um objeto é callable (implementa __call__())
def apresentacao(): return "Olá"
print(callable(apresentacao)) # True, porque `apresentacao` é uma função
print(callable(42)) # False, porque `42` não é callable
# atribuir dinamicamente um valor a um atributo de um objeto
setattr(objeto, 'nome_do_atributo', valor)
isinstance compara tipos e descobre subtipos. obs.: True e False são inteiros, pois internamente são 1 e 0
next é muito útil para obter o primeiro item de uma sequência que atenda a uma condição, ou para obter um item de uma lista, tupla ou gerador.
setattr serve para atribuir dinamicamente um valor a um atributo de um objeto. Se o atributo já existir, setattr() sobrescreve. Se não existir, ele é criado dinamicamente no objeto.
saudacao = "Hello"
# retorna o tipo do objeto
type(saudacao)
# Retorna o resultado em String
str(inteiro)
# Retorna o resultado em inteiro
int("string")
# inverte uma sequência (que seja reversível)
reversed(range(10))
O type, str, int e reversed são, ao mesmo tempo uma classe (especificamente, a metaclasse base de todas as classes em Python) e uma função built-in (devido ao seu uso prático e à maneira como é chamada diretamente no código). Essa dualidade é um exemplo da flexibilidade e do poder do design de Python, onde classes podem ser "chamadas" como funções graças ao método especial __call__.
class Chamador:
def __call__(self):
print("Chamado com parênteses!")
chamador = Chamador()
# Chama o objeto como se fosse uma função, sem precisar usar o .
chamador()
É possível criar um objeto de uma classe e torná-lo callable implementando o método especial __call__.
def somar_dois(num):
return num+2
lista_num = [6,4,5]
# Recebe uma lista e uma função para aplicar a função a cada elemento
mapa = map(somar_dois,lista_num)
# Mostra o espaço de memória
print(mapa)
# Consome o iterador
print(list(mapa))
# Vazio (pois foi consumido), mas ainda está no mesmo espaço de memoria
print(list(mapa))
# Maneira concisa de escrever usando lambda
somar_dois_lambda = map(lambda x: x + 2, lista_num)
print(list(somar_dois_lambda))
A função map (uma alternativa para compreensão em lista) é usada para aplicar uma função a cada elemento de um iterável (como uma lista, tupla, etc.) e retorna um objeto do tipo map, que é um iterador. Ele não contém os resultados concretos imediatamente, pois eles são gerados conforme você os consome.
def maior_que_5(num):
return num > 5
lista_num = [2, 8, 3, 9, 4]
# Retorna um objeto iterador filter
filtrados = filter(maior_que_5, lista_num)
print(list(filtrados))
A função filter é semelhante a map, mas ela filtra elementos com base em uma função condicional booleana.
| Comparativo Sorted Lambda
# retorna uma lista ordenada!
sorted(conjunto := {10, -1, 4, 3})
# Recebe um dicionário e retorna o valor da chave 'idade'
def get_idade(pessoa):
return pessoa['idade']
# Lista de dicionários
pessoas = [
{"nome": "João", "idade": 25},
{"nome": "Maria", "idade": 22},
{"nome": "Pedro", "idade": 30}
]
# Ordena a lista de dicionários pela chave 'idade', chamando a função externa get_idade
pessoas_ordenadas = sorted(pessoas, key=get_idade)
# Exibe a lista ordenada
print(pessoas_ordenadas)
O segundo argumento pode ser reverse=true (ordena ao contrário)
# Lista de dicionários
pessoas = [
{"nome": "João", "idade": 25},
{"nome": "Maria", "idade": 22},
{"nome": "Pedro", "idade": 30},
]
# Função lambda para acessar a chave diretamente
pessoas_ordenadas = sorted(pessoas, key=lambda pessoa: pessoa['idade'])
# Exibe a lista ordenada
print(pessoas_ordenadas)
Funções de Ordem Superior (Higher-Order Function)
São funções que recebem e/ou retornam outras funções. É importante salientar que em python as funções são tratadas como objetos e podem ser passadas para outras funções.
Um CLOSURE ocorre quando uma função interna lembra e acessa as variáveis da função externa, mesmo após a execução da função externa ter terminado.
def numero_premiado():
return 2
def exibe_funcao(funcao):
# Exibe o espaço de memória da função recebida (número premiado)
print(f"objeto de função recebido: {funcao}")
print(f'Nome da função recebida: {funcao.__name__}')
exibe_funcao(numero_premiado)
Nesse caso exibe_funcao é a Função de Ordem Superior
| Exemplos Aninhamento de Funções
def func_externa(x):
def func_interna():
return x + 2
# Estou apontando para função interna (uma referencia)
objeto_func_interna = func_interna
print(objeto_func_interna) # Saída: <function func_externa.<locals>.func_interna at 0x000002ABEE30E7A0>
# Executa a função e salvo na variável valor
valor = func_interna()
return valor
resultado = func_externa(2)
print(resultado) # Saída: 4
def func_exter(x):
def func_inter(y):
return x + y + 1
# Retorna a função interna sem chama-la
return func_inter
# resultado inicializa a função externa com o valor de x = 2 e recebe a referencia de memoria da função interna (no return)
resultado = func_exter(2)
resultado(4)
Exemplo de aninhamento retornando a referencia de função interna.
Quando chamada a função externa, a func_inter é retornada como um closure, mantendo uma referência à variável x da função externa.
Decoradores @
Um decorador em Python é uma função que permite modificar ou estender o comportamento de outra função ou método. Ele é frequentemente utilizado para adicionar funcionalidades, como registro, autenticação, controle de acesso, entre outros, sem modificar o código original da função.
O decorador adiciona código extra antes, na e/ou depois da função original, sem que você precise modificar essa função diretamente. Isso é útil para reutilizar comportamentos comuns (como validação, logging, etc.) em várias funções, sem precisar duplicar o código em cada uma delas.
obs.: A função envolvente (wrapper) serve como um "intermediário" para adicionar funcionalidades extras à função original.
O símbolo @ em Python é utilizado para aplicar um decorador de forma direta e simplificada. ficará local acima da função e precisa ter o nome da nova função que adicionará funcionalidades à original
| Exemplo Decoradores
import time
def fazer_duas_vezes(f):
def meu_pacote(*args, **kwargs):
_ = f(*args, **kwargs)
retorno = f(*args, **kwargs)
return retorno
return meu_pacote
def medir_tempo(f):
def meu_pacote(*args, **kwargs):
inicio = time.time()
retorno = f(*args, **kwargs)
final = time.time()
print(f'A função {f.__name__} levou {round(final - inicio, 5)} segundos para executar.')
return retorno
return meu_pacote
from meus_decoradores import fazer_duas_vezes, medir_tempo
# chamo o decorador para a função abaixo
@fazer_duas_vezes
def imprimir_exclamacoes():
print('!!!')
imprimir_exclamacoes()
@medir_tempo
def imprimir_interrogacoes():
for _ in range(100000):
_ = _ + 1
imprimir_interrogacoes()
| Decorador Simplificado
def tornar_maiusculo(func):
def wrapper(*args, **kwargs): # Aqui wrapper recebe ("diogo",) e kw vazio {}
retorno = func(*args, **kwargs) # Aqui esses argumentos são os de wrapper acima, no caso "diogo" e o * simboliza o desempacotamento da tupla args.
return retorno.upper() # transforma todo o retorno em maiúsculo
return wrapper # retorna a referencia para a função wrapper
import decorador_maiusculo
def dizer_ola(nome):
return f'Olá {nome}'
# --- trecho simplificado para entendimento --- #
# aqui tornar_maiusculo passa dizer_ola() para warpper e depois retorna wrapper para dizer_ola().
dizer_ola = tornar_maiusculo(dizer_ola) # agora dizer_ola é na verdade wrapper
dizer_ola('Diogo')
# Esse trecho com @ seria
@tornar_maiusculo
def dizer_ola(nome):
return f'Olá {nome}'
dizer_ola("Diogo")