Funções são um dos recursos mais importantes da programação em Python. Elas permitem organizar um programa em blocos reutilizáveis, reduzir código duplicado e separar responsabilidades.

Se você já conhece a sintaxe básica de def, parâmetros e return, o próximo passo é entender o que realmente torna as funções poderosas.

Neste guia, vamos aprofundar o assunto com exemplos de funções em Python, começando pelos conceitos fundamentais e avançando para parâmetros posicionais e nomeados, *args, **kwargs, escopo, funções de primeira classe, lambda, funções de ordem superior, closures, decorators, type hints, recursividade, generators e yield.

Todos os exemplos foram preparados para Python 3.12 ou superior e não dependem de bibliotecas externas.


ebook de python

O que são funções em Python?

Uma função é um bloco de código criado para executar uma determinada tarefa.

Em vez de escrever a mesma lógica várias vezes, podemos colocá-la dentro de uma função e reutilizá-la sempre que necessário.

Veja um exemplo:

def saudacao():
    print("Olá, seja bem-vindo!")

saudacao()
saudacao()
saudacao()

A função saudacao() contém uma tarefa específica: exibir uma mensagem.

Quando escrevemos:

saudacao()

estamos chamando a função.

Uma função pode:

  • receber dados;
  • processar esses dados;
  • retornar um resultado;
  • modificar objetos quando isso fizer parte de sua responsabilidade;
  • ser armazenada em uma variável;
  • ser passada para outra função;
  • retornar outra função.

É justamente essa flexibilidade que faz das funções uma parte central de Python.


Continue estudando Python

📘 Quer continuar aprendendo Python?

Confira os e-books disponíveis no Entre Bugs e Soluções e encontre materiais práticos para continuar sua evolução.

Ver e-books de programação →


Como criar uma função em Python

A palavra-chave def é utilizada para definir uma função.

A estrutura básica é:

def nome_da_funcao():
    # código da função

Por exemplo:

def mostrar_mensagem():
    print("Aprendendo Python")

mostrar_mensagem()

A definição da função não executa imediatamente o código.

Primeiro Python cria o objeto função. A execução acontece quando fazemos a chamada:

mostrar_mensagem()

Podemos pensar no processo assim:

definir função
      ↓
Python cria a função
      ↓
chamar função
      ↓
código é executado

A documentação oficial do Python apresenta def como a construção utilizada para definir funções e também recomenda o uso de docstrings para documentá-las.


Funções com parâmetros

Uma função pode receber informações através de parâmetros.

def saudacao(nome):
    print(f"Olá, {nome}!")

saudacao("Tiago")
saudacao("Maria")

Nesse exemplo:

  • nome é o parâmetro;
  • "Tiago" é um argumento;
  • "Maria" é outro argumento.

O parâmetro é definido na declaração da função:

def saudacao(nome):

O argumento é fornecido durante a chamada:

saudacao("Tiago")

Essa diferença entre parâmetro e argumento parece pequena, mas se torna importante quando começamos a trabalhar com funções mais complexas.


Parâmetros e argumentos em Python

Considere:

def calcular_total(preco, quantidade):
    return preco * quantidade

resultado = calcular_total(50, 3)

print(resultado)

Temos dois parâmetros:

preco
quantidade

E dois argumentos:

50
3

O resultado será:

150

A função recebe os dados, executa uma operação e retorna o resultado.

Essa separação é uma das principais vantagens de uma função: podemos reutilizar a mesma lógica com valores diferentes.


return: retornando valores de uma função

Uma função pode produzir um resultado utilizando return.

def somar(a, b):
    return a + b

resultado = somar(10, 20)

print(resultado)

Resultado:

30

Uma diferença importante:

def somar(a, b):
    print(a + b)

não é igual a:

def somar(a, b):
    return a + b

No primeiro caso, a função exibe o resultado.

No segundo, ela devolve o resultado para quem chamou a função.

Isso permite fazer:

resultado = somar(10, 20)

dobro = resultado * 2

print(dobro)

Resultado:

60

Uma boa regra prática é pensar:

print() mostra um valor. return entrega um valor para o restante do programa.


O que acontece quando uma função não possui return?

Se uma função termina sem retornar explicitamente um valor, Python retorna None.

def mostrar_nome(nome):
    print(nome)

resultado = mostrar_nome("Tiago")

print(resultado)

Saída:

Tiago
None

Isso é importante porque None pode ser utilizado para representar ausência de um resultado.


Retornando vários valores

Python permite retornar múltiplos valores.

def calcular(a, b):
    soma = a + b
    multiplicacao = a * b

    return soma, multiplicacao

resultado_soma, resultado_multiplicacao = calcular(10, 5)

print(resultado_soma)
print(resultado_multiplicacao)

Resultado:

15
50

Tecnicamente, a expressão:

return soma, multiplicacao

retorna uma tupla.

Podemos verificar:

def calcular(a, b):
    return a + b, a * b

resultado = calcular(10, 5)

print(type(resultado))

Resultado:

<class 'tuple'>

Parâmetros com valores padrão

Podemos definir um valor padrão para um parâmetro.

def saudacao(nome, mensagem="Olá"):
    print(f"{mensagem}, {nome}!")

saudacao("Tiago")
saudacao("Maria", "Bem-vinda")

Resultado:

Olá, Tiago!
Bem-vinda, Maria!

Nesse caso, se mensagem não for fornecida, Python utiliza:

"Olá"

Valores padrão são úteis quando existe um comportamento comum que pode ser alterado opcionalmente.


Cuidado com valores mutáveis como padrão

Um dos erros mais importantes ao estudar funções em Python é utilizar objetos mutáveis como valores padrão sem compreender seu comportamento.

Evite:

def adicionar_item(item, lista=[]):
    lista.append(item)
    return lista

Isso pode produzir resultados surpreendentes:

print(adicionar_item("Python"))
print(adicionar_item("Java"))

A lista padrão é criada uma vez e pode continuar sendo reutilizada entre chamadas.

Uma abordagem segura é utilizar None:

def adicionar_item(item, lista=None):
    if lista is None:
        lista = []

    lista.append(item)

    return lista

Agora cada chamada sem uma lista explícita recebe uma nova lista.

Esse é um dos exemplos clássicos de comportamento que todo desenvolvedor Python deve conhecer.


Argumentos posicionais

Por padrão, os argumentos podem ser fornecidos pela posição.

def apresentar(nome, idade):
    print(f"{nome} tem {idade} anos.")

apresentar("Tiago", 30)

Python associa:

"Tiago" → nome
30      → idade

A ordem importa.

apresentar(30, "Tiago")

pode produzir um resultado semanticamente incorreto, mesmo que o código seja sintaticamente válido.


Argumentos nomeados

Também podemos informar explicitamente o nome do parâmetro.

def apresentar(nome, idade):
    print(f"{nome} tem {idade} anos.")

apresentar(nome="Tiago", idade=30)

A vantagem é que a chamada fica mais explícita.

Além disso, podemos alterar a ordem:

apresentar(idade=30, nome="Tiago")

Isso continua funcionando porque os parâmetros foram identificados pelos nomes.


Misturando argumentos posicionais e nomeados

Também podemos combinar os dois:

def produto(nome, preco, quantidade):
    total = preco * quantidade

    print(f"{nome}: R$ {total:.2f}")

produto("Notebook", preco=3500, quantidade=2)

Existe, entretanto, uma regra importante:

Argumentos posicionais devem aparecer antes dos argumentos nomeados.

Isto é válido:

produto("Notebook", preco=3500, quantidade=2)

Isto não é válido:

produto(nome="Notebook", 3500, quantidade=2)

Parâmetros somente posicionais com /

Python permite definir parâmetros que só podem ser informados por posição.

def dividir(a, b, /):
    return a / b

Podemos fazer:

dividir(10, 2)

Mas não:

dividir(a=10, b=2)

O símbolo / indica que os parâmetros anteriores são somente posicionais.

Esse recurso pode ser especialmente útil em APIs e funções cuja assinatura não deve depender dos nomes dos parâmetros.


Parâmetros somente nomeados com *

Também podemos exigir que determinados parâmetros sejam fornecidos por nome.

def criar_usuario(nome, *, idade, cidade):
    print(nome)
    print(idade)
    print(cidade)

Agora:

criar_usuario(
    "Tiago",
    idade=30,
    cidade="São Paulo"
)

é válido.

Já:

criar_usuario("Tiago", 30, "São Paulo")

não é válido.

O * indica que os parâmetros seguintes são somente nomeados.

Podemos combinar os três tipos:

def exemplo(posicional, /, normal, *, nomeado):
    pass

A assinatura define:

posicional → somente posição
normal     → posição ou nome
nomeado    → somente nome

Esse recurso é particularmente importante quando queremos tornar a API de uma função mais clara e controlada.


*args: recebendo quantidade variável de argumentos

Imagine uma função que precise somar qualquer quantidade de números.

Uma alternativa seria:

def somar(a, b):
    return a + b

Mas isso limita a função a dois argumentos.

Com *args:

def somar(*args):
    return sum(args)

print(somar(10, 20))
print(somar(10, 20, 30))
print(somar(1, 2, 3, 4, 5))

Resultado:

30
60
15

Dentro da função, args é uma tupla.

def mostrar(*args):
    print(args)
    print(type(args))

mostrar(10, 20, 30)

Resultado:

(10, 20, 30)
<class 'tuple'>

O nome args não é obrigatório. O que importa é o *.

Por exemplo:

def mostrar(*valores):
    print(valores)

funciona da mesma maneira.


**kwargs: recebendo argumentos nomeados variáveis

**kwargs permite receber uma quantidade variável de argumentos nomeados.

def mostrar_usuario(**kwargs):
    print(kwargs)

mostrar_usuario(
    nome="Tiago",
    idade=30,
    cidade="São Paulo"
)

Resultado:

{
    'nome': 'Tiago',
    'idade': 30,
    'cidade': 'São Paulo'
}

Dentro da função, kwargs é um dicionário.

Podemos acessar os valores normalmente:

def mostrar_usuario(**kwargs):
    print(kwargs["nome"])

mostrar_usuario(nome="Tiago", idade=30)

Combinando *args e **kwargs

Podemos utilizar ambos:

def registrar(*args, **kwargs):
    print("Posicionais:", args)
    print("Nomeados:", kwargs)

registrar(
    10,
    20,
    nome="Tiago",
    cidade="São Paulo"
)

Resultado conceitual:

Posicionais: (10, 20)
Nomeados: {'nome': 'Tiago', 'cidade': 'São Paulo'}

Isso é muito utilizado em funções genéricas, wrappers e decorators.


Desempacotando argumentos com * e **

O mesmo mecanismo também pode ser utilizado na direção inversa.

Considere:

def somar(a, b, c):
    return a + b + c

numeros = [10, 20, 30]

print(somar(*numeros))

O * desempacota a lista.

Para dicionários:

def apresentar(nome, idade):
    print(nome, idade)

dados = {
    "nome": "Tiago",
    "idade": 30
}

apresentar(**dados)

O ** utiliza as chaves do dicionário como nomes dos parâmetros.


Escopo de variáveis em funções

Uma variável criada dentro de uma função normalmente possui escopo local.

def exemplo():
    mensagem = "Olá"
    print(mensagem)

exemplo()

A variável mensagem existe dentro do escopo da função.

Tentar acessá-la fora:

print(mensagem)

gera NameError.

Isso acontece porque mensagem não existe naquele escopo.


Variáveis globais

Uma variável criada fora da função pertence ao escopo global do módulo.

mensagem = "Olá"

def mostrar():
    print(mensagem)

mostrar()

A função consegue consultar a variável global.

Entretanto, modificar variáveis globais dentro de funções exige atenção.

contador = 0

def incrementar():
    global contador
    contador += 1

O global informa que estamos trabalhando com a variável global.

Embora seja válido, abusar de global geralmente torna o programa mais difícil de entender e testar.

Na maioria dos casos, é melhor passar dados como parâmetros e retornar resultados.


nonlocal: modificando uma variável de um escopo externo

nonlocal aparece principalmente em funções aninhadas e closures.

def contador():
    valor = 0

    def incrementar():
        nonlocal valor
        valor += 1
        return valor

    return incrementar

contar = contador()

print(contar())
print(contar())
print(contar())

Resultado:

1
2
3

A variável valor pertence ao escopo da função externa, mas a função interna consegue modificá-la utilizando nonlocal.

Esse conceito será importante quando chegarmos às closures.


Funções são objetos em Python

Uma característica poderosa de Python é que funções são objetos.

Isso significa que podemos atribuir uma função a uma variável.

def saudacao():
    return "Olá!"

mensagem = saudacao

print(mensagem())

Observe que fizemos:

mensagem = saudacao

e não:

mensagem = saudacao()

A primeira versão armazena a própria função.

A segunda executa a função e armazena seu resultado.

Essa distinção é fundamental.


Passando funções como argumentos

Como funções são objetos, podemos passá-las para outras funções.

def aplicar(funcao, valor):
    return funcao(valor)

def dobrar(numero):
    return numero * 2

resultado = aplicar(dobrar, 10)

print(resultado)

Resultado:

20

Aqui:

dobrar

foi passado como argumento para:

aplicar()

Esse conceito é chamado de função de ordem superior quando uma função recebe ou retorna outras funções.


Retornando funções

Também podemos retornar uma função.

def criar_multiplicador(fator):

    def multiplicar(numero):
        return numero * fator

    return multiplicar

dobrar = criar_multiplicador(2)
triplicar = criar_multiplicador(3)

print(dobrar(10))
print(triplicar(10))

Resultado:

20
30

Esse padrão nos leva diretamente ao conceito de closure.


Funções lambda

Uma lambda é uma pequena função anônima.

Por exemplo:

dobrar = lambda numero: numero * 2

print(dobrar(5))

Resultado:

10

Uma função tradicional equivalente seria:

def dobrar(numero):
    return numero * 2

A lambda é especialmente útil quando precisamos de uma função pequena e temporária.

Por exemplo:

numeros = [1, 2, 3, 4, 5]

dobrados = list(map(lambda numero: numero * 2, numeros))

print(dobrados)

Resultado:

[2, 4, 6, 8, 10]

Entretanto, não devemos utilizar lambda simplesmente porque podemos.

Se a lógica começar a ficar complexa, uma função tradicional normalmente será mais legível.


map()

map() aplica uma função a cada elemento de um iterável.

numeros = [1, 2, 3, 4]

resultado = map(lambda numero: numero * 2, numeros)

print(list(resultado))

Resultado:

[2, 4, 6, 8]

O resultado de map() é um iterador, por isso usamos list() para materializar os valores.

Uma alternativa bastante legível é uma compreensão de lista:

resultado = [numero * 2 for numero in numeros]

Não existe uma regra dizendo que map() é sempre melhor. Escolha a abordagem que deixe a intenção mais clara.


filter()

filter() seleciona elementos de acordo com uma condição.

numeros = [1, 2, 3, 4, 5, 6]

pares = filter(lambda numero: numero % 2 == 0, numeros)

print(list(pares))

Resultado:

[2, 4, 6]

Uma compreensão de lista equivalente seria:

pares = [numero for numero in numeros if numero % 2 == 0]

Novamente, legibilidade deve orientar a escolha.


reduce()

reduce() pertence ao módulo functools.

Ele aplica uma função cumulativamente aos elementos de um iterável, reduzindo-os a um único resultado.

from functools import reduce

numeros = [1, 2, 3, 4]

resultado = reduce(
    lambda acumulador, numero: acumulador + numero,
    numeros
)

print(resultado)

Resultado:

10

O processamento acontece conceitualmente assim:

1 + 2 = 3
3 + 3 = 6
6 + 4 = 10

reduce() é poderoso, mas não significa que seja sempre a melhor solução.

Quando uma operação simples pode ser expressa de forma mais clara com:

sum(numeros)

prefira a solução mais direta.


O que são closures em Python?

Uma closure acontece quando uma função interna mantém acesso a variáveis do escopo onde foi criada, mesmo depois que a função externa terminou.

Veja:

def criar_saudacao(nome):

    def saudacao():
        return f"Olá, {nome}!"

    return saudacao

saudacao_tiago = criar_saudacao("Tiago")

print(saudacao_tiago())

Resultado:

Olá, Tiago!

A função saudacao() continua tendo acesso a:

nome

mesmo depois que criar_saudacao() terminou.

Closures podem ser úteis para:

  • encapsular estado;
  • criar funções especializadas;
  • construir decorators;
  • criar fábricas de funções;
  • implementar comportamentos configuráveis.

Closures com estado

Podemos utilizar uma closure para manter um estado privado.

def criar_contador():

    contador = 0

    def incrementar():
        nonlocal contador
        contador += 1
        return contador

    return incrementar

contador = criar_contador()

print(contador())
print(contador())
print(contador())

Resultado:

1
2
3

O estado fica armazenado no ambiente da closure.

Esse padrão é interessante porque mostra como funções podem encapsular comportamento e dados.


O que são decorators em Python?

Decorators permitem modificar ou estender o comportamento de uma função sem alterar diretamente seu código.

Um decorator normalmente:

  1. recebe uma função;
  2. cria uma função wrapper;
  3. adiciona comportamento;
  4. retorna o wrapper.

Exemplo:

def registrar_execucao(funcao):

    def wrapper():
        print("Função iniciada")
        funcao()
        print("Função finalizada")

    return wrapper

Podemos aplicar o decorator:

@registrar_execucao
def saudacao():
    print("Olá!")

saudacao()

Resultado:

Função iniciada
Olá!
Função finalizada

A sintaxe:

@registrar_execucao

é uma forma conveniente de aplicar o decorator à função.


Decorators com argumentos

Um decorator mais realista precisa lidar com argumentos da função original.

Podemos utilizar *args e **kwargs:

from functools import wraps

def registrar_execucao(funcao):

    @wraps(funcao)
    def wrapper(*args, **kwargs):
        print(f"Executando {funcao.__name__}")

        resultado = funcao(*args, **kwargs)

        print("Execução concluída")

        return resultado

    return wrapper

Agora podemos decorar uma função com parâmetros:

@registrar_execucao
def somar(a, b):
    return a + b

resultado = somar(10, 20)

print(resultado)

Resultado:

Executando somar
Execução concluída
30

O @wraps é importante porque preserva metadados da função original, como nome e docstring.


Quando decorators são úteis?

Decorators são comuns para implementar comportamentos transversais, como:

  • logging;
  • autenticação;
  • autorização;
  • medição de tempo;
  • validação;
  • cache;
  • controle de acesso;
  • instrumentação.

Um exemplo simples de medição:

from functools import wraps
from time import perf_counter

def medir_tempo(funcao):

    @wraps(funcao)
    def wrapper(*args, **kwargs):
        inicio = perf_counter()

        resultado = funcao(*args, **kwargs)

        fim = perf_counter()

        print(f"Tempo: {fim - inicio:.6f}s")

        return resultado

    return wrapper

Uso:

@medir_tempo
def calcular():
    return sum(range(1_000_000))

print(calcular())

O decorator adiciona uma responsabilidade sem modificar o corpo de calcular().


Type hints em funções

Type hints permitem documentar os tipos esperados pelos parâmetros e pelo retorno.

def somar(a: int, b: int) -> int:
    return a + b

Temos:

a: int
b: int
-> int

Isso indica que esperamos inteiros como argumentos e um inteiro como retorno.

Outro exemplo:

def apresentar(nome: str, idade: int) -> str:
    return f"{nome} tem {idade} anos."

Type hints ajudam:

  • editores de código;
  • ferramentas de análise estática;
  • documentação;
  • manutenção;
  • leitura do código;
  • identificação antecipada de possíveis incompatibilidades.

É importante entender que type hints não transformam automaticamente Python em uma linguagem de tipagem estática e, por si só, não impedem a passagem de valores incompatíveis em tempo de execução.


Type hints para funções que recebem funções

Podemos indicar que um parâmetro espera uma função.

from collections.abc import Callable

def aplicar(
    funcao: Callable[[int], int],
    valor: int
) -> int:
    return funcao(valor)

Nesse exemplo, Callable[[int], int] indica uma função que recebe um int e retorna um int.

Isso se torna especialmente útil em código que trabalha com callbacks, funções de ordem superior e decorators.

Python 3.12 também possui recursos modernos para tipagem genérica de funções, tornando possível escrever APIs tipadas de forma mais expressiva.


Docstrings: documentando funções

Uma docstring é uma string colocada logo no início da função para documentar seu comportamento.

def calcular_media(notas):
    """Calcula a média aritmética de uma lista de notas."""
    return sum(notas) / len(notas)

Podemos consultar a documentação:

print(calcular_media.__doc__)

Uma docstring mais detalhada:

def calcular_media(notas: list[float]) -> float:
    """
    Calcula a média aritmética das notas.

    Args:
        notas: Lista contendo as notas.

    Returns:
        A média das notas.
    """
    return sum(notas) / len(notas)

Docstrings são particularmente importantes quando uma função começa a fazer parte de um projeto maior ou de uma biblioteca reutilizável.


Funções recursivas

Uma função recursiva é aquela que chama a si própria.

Um exemplo clássico é o cálculo do fatorial:

def fatorial(numero):
    if numero <= 1:
        return 1

    return numero * fatorial(numero - 1)

Executando:

print(fatorial(5))

O processo pode ser entendido como:

5 * fatorial(4)
      ↓
4 * fatorial(3)
      ↓
3 * fatorial(2)
      ↓
2 * fatorial(1)
      ↓
1

Depois os resultados retornam:

1
2
6
24
120

O ponto mais importante de uma função recursiva é o caso-base:

if numero <= 1:
    return 1

Sem ele, a função poderia continuar chamando a si mesma indefinidamente.


Quando evitar recursividade?

Recursividade é útil para determinados problemas, especialmente estruturas naturalmente hierárquicas.

Porém, não significa que seja sempre melhor que um loop.

Para um problema simples:

def contar():
    for numero in range(1, 6):
        print(numero)

não há motivo para criar uma versão recursiva apenas para demonstrar recursividade.

Também precisamos considerar o limite de profundidade da pilha de chamadas do Python.

Para grandes quantidades de iterações, um loop geralmente é mais apropriado.


Geradores e yield

yield transforma uma função em um gerador.

Considere:

def numeros():
    yield 1
    yield 2
    yield 3

Podemos percorrer:

for numero in numeros():
    print(numero)

Resultado:

1
2
3

Diferentemente de uma função que constrói uma lista inteira, um generator produz valores conforme são solicitados.


return versus yield

Compare:

def gerar_lista():
    return [1, 2, 3, 4, 5]

com:

def gerar_numeros():
    yield 1
    yield 2
    yield 3
    yield 4
    yield 5

A primeira retorna uma lista.

A segunda retorna um objeto gerador.

Isso é particularmente interessante quando trabalhamos com grandes quantidades de dados.

Por exemplo:

def contar_ate(limite):
    numero = 1

    while numero <= limite:
        yield numero
        numero += 1

Podemos fazer:

for numero in contar_ate(5):
    print(numero)

O generator não precisa construir antecipadamente uma lista com todos os valores.


Funções geradoras e memória

Considere:

def gerar_numeros(limite):
    for numero in range(limite):
        yield numero

Podemos processar:

for numero in gerar_numeros(1_000_000):
    if numero == 10:
        break

A função produz os valores conforme eles são solicitados.

Esse modelo é chamado de avaliação preguiçosa, porque os valores não precisam ser produzidos todos antecipadamente.

Generators são especialmente úteis para processamento sequencial e grandes fluxos de dados.


Funções de primeira classe

Dizer que funções são objetos de primeira classe significa que podemos tratá-las como outros valores.

Podemos:

Armazenar uma função

def somar(a, b):
    return a + b

operacao = somar

print(operacao(10, 20))

Passar uma função

def executar(funcao):
    return funcao()

def mensagem():
    return "Python"

print(executar(mensagem))

Retornar uma função

def criar_saudacao():
    def saudacao():
        return "Olá!"

    return saudacao

Esse conceito é a base para várias técnicas avançadas da linguagem.


Boas práticas para criar funções em Python

Uma função bem projetada não deve apenas funcionar. Ela também precisa ser fácil de entender, testar e modificar.

1. Dê nomes claros

Evite:

def calc(x, y):
    ...

Prefira:

def calcular_total(preco, quantidade):
    ...

O nome deve comunicar a intenção.


2. Faça uma função ter uma responsabilidade clara

Evite criar uma função que:

lê dados
valida dados
salva arquivo
envia e-mail
calcula valores
imprime relatório

Tudo ao mesmo tempo.

É melhor separar responsabilidades:

def validar_usuario():
    ...

def calcular_total():
    ...

def salvar_dados():
    ...

def enviar_email():
    ...

Isso facilita testes e manutenção.


3. Evite funções excessivamente grandes

Se uma função possui dezenas ou centenas de linhas e várias responsabilidades, provavelmente existe uma oportunidade de decomposição.

Uma função menor geralmente é mais fácil de:

  • entender;
  • testar;
  • reutilizar;
  • corrigir;
  • modificar.

4. Prefira parâmetros explícitos

Compare:

def processar(dados, configuracao):
    ...

com uma função que depende de várias variáveis globais.

Parâmetros explícitos tornam as dependências mais visíveis.


5. Evite efeitos colaterais desnecessários

Uma função como:

def somar(a, b):
    return a + b

é previsível.

Ela recebe dados e retorna um resultado.

Já uma função que altera várias variáveis globais, grava arquivos e imprime mensagens inesperadamente possui mais efeitos colaterais.

Isso pode dificultar os testes.


Erros comuns ao trabalhar com funções

Confundir chamada com referência

Isto:

funcao

é uma referência ao objeto função.

Isto:

funcao()

é uma chamada.

Essa diferença é essencial quando trabalhamos com callbacks e decorators.


Esquecer o return

Considere:

def somar(a, b):
    resultado = a + b

Agora:

valor = somar(10, 20)

print(valor)

produzirá:

None

Se precisamos devolver o resultado:

def somar(a, b):
    return a + b

Confundir print() com return

Uma função que apenas imprime:

def somar(a, b):
    print(a + b)

não retorna automaticamente o resultado.

Se outro trecho do programa precisa utilizar o valor, prefira:

def somar(a, b):
    return a + b

Usar estado global sem necessidade

Isto:

total = 0

def adicionar(valor):
    global total
    total += valor

funciona, mas cria uma dependência global.

Em muitos casos, é melhor:

def adicionar(total, valor):
    return total + valor

Agora o estado é explícito.


Criar funções que fazem coisas demais

Uma função que possui muitas responsabilidades pode indicar que o código precisa ser dividido.

Pergunte:

Esta função pode ser descrita claramente em uma única frase?

Se a resposta for difícil, talvez ela esteja fazendo coisas demais.


Exemplos práticos de funções em Python

Agora vamos combinar vários conceitos em exemplos mais próximos de situações reais.

Exemplo 1 — Validação de idade

def pode_acessar(idade: int) -> bool:
    return idade >= 18

idade = 21

if pode_acessar(idade):
    print("Acesso permitido.")
else:
    print("Acesso negado.")

A função possui uma responsabilidade simples: verificar uma regra.


Exemplo 2 — Calculadora de desconto

def calcular_desconto(
    preco: float,
    percentual: float
) -> float:
    desconto = preco * (percentual / 100)
    return preco - desconto

preco_final = calcular_desconto(1000, 10)

print(f"Preço final: R$ {preco_final:.2f}")

Resultado:

Preço final: R$ 900.00

A função pode ser reutilizada para diferentes produtos.


Exemplo 3 — Validação de senha

def senha_valida(senha: str) -> bool:
    if len(senha) < 8:
        return False

    return True

senha = "python123"

if senha_valida(senha):
    print("Senha válida.")
else:
    print("Senha inválida.")

Podemos evoluir essa função posteriormente para verificar letras, números e caracteres especiais.


Exemplo 4 — Função para calcular média

def calcular_media(notas: list[float]) -> float:
    if not notas:
        return 0.0

    return sum(notas) / len(notas)

notas = [8.5, 7.0, 9.0, 10.0]

media = calcular_media(notas)

print(f"Média: {media:.2f}")

O teste:

if not notas:

evita uma divisão por zero.


Pratique com um projeto

💻 Quer colocar funções em prática?

Depois de aprender os conceitos deste guia, pratique criando um sistema de menus utilizando funções, listas e laços de repetição.

Criar um Sistema de Menus em Python com Funções e Listas →


Organizando um pequeno programa com funções

Imagine um sistema simples de menu.

Podemos separar cada responsabilidade:

def mostrar_menu():
    print("\n=== MENU ===")
    print("1 - Cadastrar")
    print("2 - Listar")
    print("3 - Sair")


def cadastrar():
    print("Cadastro realizado.")


def listar():
    print("Lista de registros.")


def executar():
    while True:
        mostrar_menu()

        opcao = input("Escolha: ")

        if opcao == "1":
            cadastrar()

        elif opcao == "2":
            listar()

        elif opcao == "3":
            print("Programa encerrado.")
            break

        else:
            print("Opção inválida.")


executar()

Esse padrão mostra uma das maiores vantagens das funções: cada parte do programa possui uma responsabilidade definida.

Esse tipo de organização também aparece no conteúdo do Entre Bugs e Soluções sobre criação de sistemas de menus com funções e listas.


Funções e estruturas de repetição

Funções e loops trabalham muito bem juntos.

Por exemplo:

def exibir_numeros(limite):
    for numero in range(1, limite + 1):
        print(numero)

exibir_numeros(5)

Também podemos usar funções dentro de um while.

def solicitar_opcao():
    return input("Digite uma opção: ")

while True:
    opcao = solicitar_opcao()

    if opcao == "sair":
        break

Se você ainda está consolidando while, esse conceito pode ser aprofundado no artigo sobre criação de um sistema de login simples do Entre Bugs e Soluções.


Funções e operadores lógicos

Funções também são excelentes para encapsular regras.

def acesso_permitido(usuario, senha):
    return usuario == "admin" and senha == "1234"

Agora:

if acesso_permitido("admin", "1234"):
    print("Login realizado.")

A regra fica isolada dentro da função.

Isso facilita sua reutilização em outras partes do programa.

Para aprofundar and, or e not, consulte o conteúdo sobre operadores lógicos do blog.


Continue sua trilha de Python

🚀 Já domina funções? Continue avançando.

Explore outros conteúdos de Python do Entre Bugs e Soluções para conectar funções a estruturas de repetição, operadores e outros fundamentos da linguagem.

Ver conteúdos de Python →


Como testar uma função simples

Uma função pode ser testada diretamente com assert.

def somar(a, b):
    return a + b


assert somar(2, 3) == 5
assert somar(10, 20) == 30

Se alguma condição for falsa, Python gera AssertionError.

Podemos testar também casos específicos:

def eh_par(numero):
    return numero % 2 == 0


assert eh_par(2) is True
assert eh_par(4) is True
assert eh_par(3) is False

Esse tipo de teste é uma forma simples de verificar se uma função mantém o comportamento esperado.

Em projetos maiores, frameworks de testes como unittest e pytest permitem estruturar testes de maneira mais completa.


Tabela: principais recursos de funções em Python

Recurso Para que serve Exemplo
def Criar função def somar():
return Retornar resultado return total
Parâmetro Receber dados def somar(a, b)
Valor padrão Definir comportamento padrão nome="Visitante"
Argumento nomeado Tornar chamada explícita idade=30
/ Exigir argumento posicional def f(a, /)
* Exigir argumento nomeado def f(*, nome)
*args Receber vários argumentos posicionais def f(*args)
**kwargs Receber vários argumentos nomeados def f(**kwargs)
lambda Criar função pequena e anônima lambda x: x * 2
Closure Preservar estado do escopo externo função interna
Decorator Estender comportamento @decorator
Type hint Documentar tipos esperados x: int
yield Produzir valores sob demanda yield valor
Recursividade Função chamar a si mesma fatorial(n)

Tabela: return, print() e yield

Recurso Função principal Pode ser reutilizado pelo chamador?
print() Exibir informação Não diretamente
return Devolver resultado Sim
yield Produzir valores gradualmente Sim, através de um generator

Um erro comum de iniciantes é utilizar print() quando na verdade precisam de return.

Uma função deve retornar dados quando outra parte do programa precisa trabalhar com esses dados.


Exercícios de funções em Python

Depois de estudar os conceitos, pratique.

Exercício 1 — Saudação

Crie uma função:

saudacao(nome)

que receba um nome e retorne:

Olá, Tiago!

Exercício 2 — Número par

Crie:

eh_par(numero)

A função deve retornar True se o número for par e False caso contrário.


Exercício 3 — Maior número

Crie uma função:

maior(a, b, c)

que retorne o maior entre três números.


Exercício 4 — Média

Crie:

calcular_media(notas)

A função deve receber uma lista de notas e retornar a média.

Trate também o caso em que a lista estiver vazia.


Exercício 5 — Argumentos variáveis

Crie:

somar_todos(*numeros)

A função deve aceitar qualquer quantidade de números e retornar a soma.

Exemplo:

print(somar_todos(1, 2, 3, 4, 5))

Resultado:

15

Exercício 6 — **kwargs

Crie uma função:

mostrar_dados(**dados)

que percorra os dados recebidos e mostre:

nome: Tiago
idade: 30
cidade: São Paulo

Exercício 7 — Closure

Crie uma função:

criar_multiplicador(fator)

que retorne outra função capaz de multiplicar um número pelo fator recebido.

Exemplo:

dobrar = criar_multiplicador(2)

print(dobrar(10))

Resultado:

20

Exercício 8 — Decorator

Crie um decorator que mostre:

Iniciando função...

antes da execução e:

Função finalizada.

depois.

O decorator deve funcionar com funções que recebem argumentos.


Desafio final — Sistema de cadastro

Agora combine os conhecimentos.

Crie um pequeno sistema que possua funções para:

  • mostrar o menu;
  • cadastrar usuário;
  • listar usuários;
  • procurar usuário;
  • validar entrada;
  • encerrar o programa.

Uma possível estrutura:

usuarios = []


def mostrar_menu():
    print("\n=== SISTEMA ===")
    print("1 - Cadastrar")
    print("2 - Listar")
    print("3 - Procurar")
    print("4 - Sair")


def cadastrar():
    nome = input("Nome: ")

    usuarios.append({
        "nome": nome
    })


def listar():
    for usuario in usuarios:
        print(usuario["nome"])


def procurar():
    nome = input("Nome para procurar: ")

    for usuario in usuarios:
        if usuario["nome"] == nome:
            print("Usuário encontrado.")
            return

    print("Usuário não encontrado.")


def executar():
    while True:
        mostrar_menu()

        opcao = input("Escolha: ")

        if opcao == "1":
            cadastrar()

        elif opcao == "2":
            listar()

        elif opcao == "3":
            procurar()

        elif opcao == "4":
            break

        else:
            print("Opção inválida.")


executar()

O objetivo desse desafio não é apenas fazer o programa funcionar.

Observe como o código foi dividido em responsabilidades:

executar()
    ↓
mostrar_menu()
    ↓
escolher operação
    ↓
cadastrar()
listar()
procurar()

Essa decomposição é justamente uma das razões pelas quais funções são tão importantes em programação.


Como pensar como um bom desenvolvedor ao criar funções

Ao criar uma função, não pense apenas:

"Onde posso colocar este código?"

Pergunte:

  1. Qual é a responsabilidade desta função?
  2. Quais dados ela precisa receber?
  3. O que ela deve retornar?
  4. Ela possui efeitos colaterais?
  5. Pode ser reutilizada?
  6. O nome deixa sua intenção clara?
  7. É fácil testar essa função?
  8. Ela depende de variáveis globais?
  9. Está fazendo mais de uma tarefa?
  10. Os parâmetros estão claros?

Esse processo transforma funções de simples blocos de código em componentes reutilizáveis do programa.


Funções em Python: do básico ao avançado

Dominar funções significa muito mais do que saber escrever:

def minha_funcao():
    ...

É preciso entender como elas interagem com o restante da linguagem.

Começamos com:

def
↓
parâmetros
↓
argumentos
↓
return

Depois avançamos para:

valores padrão
↓
argumentos nomeados
↓
/ e *
↓
*args e **kwargs

E então chegamos aos conceitos mais avançados:

funções como objetos
↓
funções de ordem superior
↓
lambda
↓
closures
↓
decorators
↓
generators
↓
type hints

Essa evolução permite escrever programas cada vez mais organizados, reutilizáveis e expressivos.


E-book gratuito de Python

📚 Quer estudar Python de forma mais estruturada?

Baixe gratuitamente o e-book Python: Variáveis e Funções e continue praticando os fundamentos apresentados neste artigo.

Conhecer os e-books de Python →


Conclusão

As funções são uma das bases da programação em Python.

No começo, podemos utilizá-las simplesmente para evitar repetição:

def mostrar_mensagem():
    print("Olá!")

Mas, conforme avançamos, percebemos que elas permitem construir abstrações muito mais poderosas.

Aprendemos a trabalhar com:

  • parâmetros;
  • argumentos posicionais;
  • argumentos nomeados;
  • valores padrão;
  • parâmetros somente posicionais;
  • parâmetros somente nomeados;
  • *args;
  • **kwargs;
  • escopo;
  • global;
  • nonlocal;
  • funções de primeira classe;
  • funções de ordem superior;
  • lambda;
  • map();
  • filter();
  • reduce();
  • closures;
  • decorators;
  • type hints;
  • docstrings;
  • recursividade;
  • generators;
  • yield.

O mais importante, entretanto, não é decorar cada recurso.

É aprender a identificar qual ferramenta resolve melhor determinado problema.

Uma função pequena e clara pode ser mais valiosa do que uma solução cheia de recursos avançados. Da mesma forma, lambda, decorators, closures ou generators devem ser utilizados quando realmente tornam o código mais expressivo ou eficiente.

Se você está aprendendo Python para entrar na programação, domine primeiro os fundamentos e pratique bastante. Depois, avance gradualmente para os recursos mais sofisticados.

A melhor forma de aprender funções é criar suas próprias funções, testar diferentes abordagens, cometer erros e analisar por que determinado código funciona ou não.

Próximo passo

Se você quer continuar sua jornada de aprendizado em Python, explore os outros conteúdos da categoria Python do Entre Bugs e Soluções e pratique os conceitos apresentados neste guia.

E, para continuar estudando de forma estruturada, baixe gratuitamente o e-book sobre Python disponibilizado pelo blog.