Introdução à funções#

Dentre todos os meus anos como instrutor, notei que funções é o primeiro tema que as pessoas começam a ter uma dificuldade maior. Por isso vou utilizar alguns recursos extras para explicar esse tema ao longo deste capítulo. Nós já usamos várias funções ao longo do livro, e algumas delas não serão novidades para você. Vamos aprender mais sobre as funções que já vimos, outras funções nativas do Python e como criar nossas próprias funções.

Conceito e definição#

Uma função é como uma receita que descreve um conjunto de instruções para realizar uma tarefa específica. Imagine que você quer ensinar alguém a fazer um bolo. Em vez de repetir todos os passos toda vez que alguém pedir a receita, você escreve a receita uma vez e a entrega para quem precisar. Uma função em Python funciona de maneira similar: você define o que precisa ser feito (escreve a receita), e depois pode usar essa função sempre que precisar executar aquela tarefa, sem precisar reescrever todo o código. É mais fácil exemplificar com algumas funções que já vimos.

Por exemplo:

  • a função print() é usada para imprimir algo na tela

  • a função len() é usada para retornar o tamanho de uma sequencia

  • a função range(), que vimos nesta seção do capítulo de laço for, é usada para criar uma sequência de números.

  • a função type() é usada para retornar o tipo de um objeto. Esta vem nos acompanhando desde o início do livro, na seção sobre a função type no capítulo de tipos numéricos.

Você se lembra de outras funções que vimos ao longo do livro?

Todas estas funções acima fazem parte de um conjunto maior de funções que são chamadas de funções nativas do Python, cuja lista completa pode ser encontrada neste link. Estas funções nativas estão prontas pra uso, bastando chamá-las no código.

As funções podem ser divididas, a princípio, em dois grupos:

  • Funções nativas: são as funções que já vêm prontas no Python, como as que vimos acima.

  • Funções customizadas: são funções que você mesmo cria para realizar uma tarefa específica.

Vamos detalhar cada um destes grupos na sequencia.

Funções nativas#

As funções nativas, também chamadas de built-in, são funções que já vêm prontas no Python e podem ser usadas diretamente. São relativamente poucas funções, porém não vou passar por cada uma delas. Lembrando que o objetivo é falar de forma genérica sobre funções, e cabe a você consultar a documentação do Python para saber mais sobre cada uma delas. Vou passar um pequeno resumo de algumas funções que considero mais importantes:

Nota (muita calma nesta hora!)

Talvez você possa se assustar com a lista enorme (que é pequena, vá por mim!) de funções novas que você vai ver, mas não se preocupe! A ideia é que você conheça a existência delas, e não que você saiba todas de cor. Afinal, você pode consultar a documentação sempre que precisar. Eu pretendo te guiar pra consultar a documentação, então não se preocupe em decorar cada uma das funções.

  1. Funções de entrada e saída

Essas funções são usadas para interagir com o usuário ou com arquivos.

  • print(): Exibe informações na tela.

  • input(): Recebe dados de entrada do usuário como uma string a partir do terminal.

  • open(): Abre um arquivo e retorna um objeto de arquivo para leitura e/ou escrita.

  1. Funções de conversão de tipos

Essas funções convertem valores de um tipo de dados para outro.

  • int(): Converte um valor para um número inteiro.

  • float(): Converte um valor para um float.

  • str(): Converte um valor para uma string.

  • list(): Converte uma sequência para uma lista. Também criar uma lista vazia.

  • tuple(): Converte uma sequência para uma tupla. Também criar uma tupla vazia.

  • set(): Converte uma sequência para um conjunto. Também criar um conjunto vazio.

  • dict(): Converte uma coleção de pares chave-valor para um dicionário. Também criar um dicionário vazio.

  1. Funções de manipulação de sequências

Essas funções são usadas para processar e manipular sequências como listas, tuplas e sets.

  • len(): Retorna o comprimento (número de itens) de uma sequência.

  • max(): Retorna o maior item de uma sequência.

  • min(): Retorna o menor item de uma sequência.

  • sum(): Retorna a soma de todos os itens de uma sequência.

  • sorted(): Retorna uma nova lista ordenada a partir de uma sequência.

  • enumerate(): Adiciona um contador a uma sequência para capturar também os índices.

  • zip(): Combina elementos de múltiplas sequencias em tuplas.

  • map(): Aplica uma outra função a todos os itens de uma sequência.

  • filter(): Filtra itens em uma sequência de acordo com uma outra função passada.

  1. Funções matemáticas

Essas funções realizam operações matemáticas.

  • abs(): Retorna o valor absoluto (sem sinal) de um número.

  • round(): Arredonda um float para um número especificado de dígitos nas casas decimais.

  • divmod(): Retorna o quociente e o resto da divisão de dois números.

  • sum(): Retorna a soma de todos os itens de uma sequência.

  1. Funções de controle de fluxo e comparação

Essas funções são usadas para controle de fluxo e comparações lógicas.

  • any(): Retorna True se qualquer elemento em uma sequência for verdadeiro.

  • all(): Retorna True se todos os elementos em uma sequência forem verdadeiros.

  1. Funções de inspeção de objetos

Essas funções retornam informações sobre objetos, como seu tipo ou identificador.

  • type(): Retorna o tipo de um objeto.

  • id(): Retorna o identificador único na memória do computador de um objeto.

  • isinstance(): Verifica se um objeto é uma instância de uma classe ou tipo.

Existem outras funções, mas como disse, a ideia não é ensinar cada uma delas no detalhe, e sim uma visão mais geral. Você pode consultar a documentação para saber mais sobre cada uma delas.

Funções customizadas#

Nesta seção vamos aprender a criar nossas próprias funções.

Antes de mais nada, entenda que não é necessário recriar funções que já existem. À exemplo da função sum(), que soma os elementos de uma sequência, não faria sentido criar uma função soma() que faz a mesma coisa.

O ideal é que você crie funções para tarefas específicas. Um exemplo típico é a função media() que calcula a média de uma sequência de números. Percebam que não existe nenhuma outra função nativa que faça isso (que poderia ser chamada de mean() ou avg()), então é uma boa ideia criar uma função para este cálculo.

Vamos entender a sintaxe de criação de funções com uma função mais simples, e em seguida trabalharemos com a construção da função media().

A sintaxe para criar uma função é a seguinte:

def nome_da_funcao(parametro1, parametro2, ...):
    # corpo da função com o código
    # é possível ter várias linhas
    # de instruções
    return valor_de_retorno

Vamos entender cada parte na criação da função:

  1. Nome da função:

No exemplo def nome_da_funcao(parametro1, parametro2):, a palavra def é usada para indicar ao Python que estamos criando uma função. É como dizer ao computador: aqui está a receita que você vai seguir. E nome_da_funcao é o nome que você dá para essa função. Pode ser qualquer nome, mas é bom escolher algo que descreva o que a função faz e vale a mesma regra de nomenclatura de variáveis que vimos anteriormente.

  1. Parâmetros:

Os parametros são como os ingredientes de uma receita. Eles são as informações que você passa para a função, para que ela possa fazer seu trabalho. Se você está fazendo um bolo, seus parâmetros podem ser os ingredientes como farinha, açúcar, e ovos. No exemplo, parametro1 e parametro2 são os nomes que você vai usar dentro da função. Quando você chamar essa função, você vai dizer quais valores quer usar como parâmetros. Por exemplo, nome_da_funcao(10, 20), onde parametro1 vai ser 10 e parametro2 vai ser 20.

  1. Corpo da função:

O corpo da função é o conjunto de instruções que dizem ao computador o que fazer com os parâmetros. No exemplo, o corpo da função está representado por linhas de código que começam logo abaixo de def nome_da_funcao(parametro1, parametro2):. Aqui, você pode ter várias linhas que instruem o computador sobre o que fazer. Pode ser calcular algo, modificar um valor, ou até mesmo apenas mostrar uma mensagem na tela.

  1. Retorno:

Finalmente, temos a linha return valor_de_retorno. O return é a palavra reservada que indica o valor a ser retornado como resultado da função, como o resultado final da receita, o bolo pronto. É o que a função vai devolver depois de fazer tudo o que tinha que fazer. E valor_de_retorno é o resultado que a função calcula ou produz, que pode ser um número, uma string, ou qualquer outro tipo de dado.

Algumas observações importantes:

  • A função pode ou não receber parâmetros. Se não receber, os parênteses ficariam vazios conforme trecho abaixo. Se receber, os parâmetros são separados por vírgula, conforme o exemplo acima:

def funcao_sem_parametros():
    ...
  • Os : no final da definição da função são obrigatórios. Eles indicam ao Python que o bloco de código que vem a seguir é o corpo da função.

  • O corpo da função é identado, ou seja, todas as linhas de código que fazem parte da função devem estar alinhadas com a palavra def indicando que todo código identado pertence à função.

  • A palavra return é opcional. Se você não usar, a função vai ser executada, mas não vai retornar nada para fora dela. Vamos ver mais detalhadamente a questão de retorno vs não retorno mais adiante.

E o mais importante de todos: ao definirmos uma função, ela é simplesmente criada na memória do computador, mas isso não quer dizer que ela é executada no momento da definição. Para executar a função, você precisa chamá-la, como fizemos com as funções nativas. Exemplo:

# Aqui estamos apenas definindo a função, nada é executado neste momento...
def funcao_sem_parametros():
    ...
    ...

funcao_sem_parametros() # Aqui é onde estamos chamando a função, e o bloco de código dela será executado

Vamos começar com um exemplo de uma função customizada que não existe na lista de funções nativas do Python. Vamos criar uma função que calcula a média de uma sequência de números. Conceitualmente, a média aritmética simples é a soma de todos os elementos dividida pelo número de elementos. Já existe a função sum() que calcula a soma de uma dada sequencia e len() que calcula o tamanho da sequência, então podemos usar essas duas funções para calcular a média. Vamos ver como fazer isso.

def calcula_media(valores):
    return sum(valores) / len(valores)


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

media = calcula_media(valores_exemplo)
print(media)
3.0

Aqui definimos a função e a usamos em seguida para calcular a média da lista valores_exemplo. O resultado da função calcula_media é armazenado na variável media e depois é impresso na tela.

É possível reaproveitar a mesma função para calcular a média de outras sequências de números, bastando passar as diferentes sequências como argumento.

def calcula_media(valores):
    return sum(valores) / len(valores)


lista1 = [1, 2, 3, 4, 5]
lista2 = [45, 74, 50, 99, 51]
lista3 = [548, 781, 961, 473, 552]

media1 = calcula_media(lista1)
media2 = calcula_media(lista2)
media3 = calcula_media(lista3)

print(f"As médias são, respectivamente, {media1}, {media2} e {media3}")
As médias são, respectivamente, 3.0, 63.8 e 663.0

Escopo de variáveis#

É possível criar variável dentro de funções, porém uma variável só estará disponível dentro do escopo que ela foi criada. Elas só vão existir enquanto a função for executada. É o que chamamos de escopo local. Se você tentar acessar uma variável criada dentro de uma função fora dela, você terá um erro. Vamos ver um exemplo:

def exemplo_escopo_local():
    x = 10
    print(f"A variável x dentro da função é {x}")


print(x)
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
Cell In[3], line 6
      2     x = 10
      3     print(f"A variável x dentro da função é {x}")
----> 6 print(x)

NameError: name 'x' is not defined

O erro NameError: name 'x' is not defined ocorre porque a variável x foi criada dentro da função exemplo_escopo_local. Isso significa que x só existe enquanto o código está sendo executado dentro dessa função. Se você tentar acessar x fora da função, o Python não conseguirá encontrá-la, resultando nesse erro.

O mesmo vale para os parâmetros de uma função: eles só existem no escopo interno da função e não podem ser acessados de fora.

Todas as variáveis definidas no escopo principal (fora de qualquer função) são chamadas de variáveis globais. Elas podem ser acessadas de qualquer lugar do código, inclusive de dentro de funções. E sinceramente, o Python é bem confuso e neste ponto, pois ele dá abertura para algumas más práticas de programação que tornanm o código dificil de entender, apesar do código funcionar. Vejamos um exemplo:

x = 20  # variável definida no escopo global


def exemplo_escopo_global():
    x = 10  # variável definida no escopo local da função
    print(f"A variável x dentro da função é {x}")


exemplo_escopo_global()
print(f"A variável x fora da função é {x}")
A variável x dentro da função é 10
A variável x fora da função é 20

Percebam como fica confuso! Temos um x global e um x local, e o Python não reclama disso. Isso é um problema, pois se você não tomar cuidado, você pode acabar entendendo errado e fazendo confusão com os valores.

Dica profissional (variáveis globais e locais)

Não use variáveis globais e locais com o mesmo nome pois dá margem para confusão e erros! Busque sempre usar nomes diferentes para variáveis globais e locais.

Existe ainda uma palavra reservada chamada global para permitir o uso de variáveis globais dentro de funções, e o entendimento do código pode ficar pior ainda! Por isso nem gosto de ensinar esta abordagem por ser considerada uma má prática.

Perigo (variáveis globais)

Evite ao máximo usar variáveis globais dentro de funções! Se você precisar de um valor de fora da função, passe esse valor como parâmetro. Isso torna o código mais legível e menos propenso a erros.

Dicas de tipo (type hints)#

Notem que, na função acima calcula_media que criamos anteriormente, é difícil dizer quais são os tipos de dados que a função espera como argumento e qual é o tipo de dado que ela retorna. Isso pode ser um problema se você estiver trabalhando em um projeto grande e complexo, ou se você estiver trabalhando em equipe. Para resolver isso, o Python permite que você adicione dicas de tipo, chamadas de type hints, para indicar quais são os tipos de dados esperados e retornados pela função.

Infelizmente type hints são apenas dicas mesmo, e o Python não vai reclamar se você passar um tipo de dado diferente do esperado. Mas, ainda sim, é uma boa prática adicionar essas dicas para ajudar a entender o código. Vamos ver como fazer isso:

from typing import Union


def calcula_media(valores: list[Union[int, float]]) -> float:
    return sum(valores) / len(valores)

Nota (importações)

No código acima você verá algo novo: from typing import Union. Isso é uma importação de um módulo chamado typing que contém várias funcionalidades extras para trabalhar com anotação de tipos de dados. Neste caso, estamos importando Union que é usada para indicar que um parâmetro pode ser de um tipo ou de outro. Vamos ver mais sobre isso importações de módulos em uma seção futura. Por enquanto, entenda que estamos trazendo algo de fora para usar em nosso código.

Nota (notação de tipo Python 3.10)

A partir da versão 3.10 do Python, Union pode ser substituído por |, simplificando a notação de tipos. No exemplo acima, ficaria list[int | float] ao invés de list[Union[int, float]]. A importação from typing import Union não é necessária ao usar |.

A função acima indica o tipo do parâmetro valores como uma lista de números inteiros ou floats através da notação : list[Union[int, float]] logo na sequencia do nome do parâmetro. Já a notação -> float indica que a função retorna um número float (o que faz sentido pois a média pode ser um número quebrado).

Porém, como disse anteriormente, o Python não vai reclamar se você passar um tipo de dado diferente do esperado. Essa função fica um pouco mais claro em termos de tipos de dados (desde que informados corretamente) e pode ajudar a entender melhor o código.

Podemos anotar qualquer parâmetro da função, bem como seu(s) retorno(s). As notações a princípio seguem os tipos básicos que vimos até agora:

  • int: número inteiro

  • float: número de ponto flutuante

  • str: string

  • list: lista

  • tuple: tupla

  • dict: dicionário

  • set: conjuntos

Para as sequências como list, tuple e set, é possível indicar o tipo de dado que a sequência contém, como fizemos no exemplo acima, usando list[Union[int,float] (ou list[int |float] a partir do Python 3.10) para indicar que a lista contém números inteiros ou floats.

No caso de dicionários, é possível indicar o tipo de dado da chave e do valor, como dict[str, int] para indicar um dicionário onde a chave é uma string e o valor é um inteiro.

Parâmetros opcionais#

Todos os parâmetros de uma função são obrigatórios, ou seja, se você definir um parâmetro na função, você precisa passar um valor para ele quando chamar a função. Mas é possível definir parâmetros opcionais, que são parâmetros que têm um valor padrão e podem ser omitidos quando você chama a função. A melhor forma é apresentando um exemplo real, criando uma função que calcula o salário de um colaborador, onde o valor base do salário é obrigatório, mas há benefícios opcionais, como bônus, vale-refeição, e plano de saúde. Esses benefícios podem ser incluídos ou não, dependendo das condições de cada funcionário.

def calcular_salario(
    base: int, bonus: int = 0, vale_refeicao: int = 0, plano_saude: int = 0
) -> int:
    return base + bonus + vale_refeicao + plano_saude


# Exemplo de uso
salario_maria = calcular_salario(4000)  # Sem benefícios adicionais
salario_joao = calcular_salario(3000, 500, 200)

print(salario_maria)
print(salario_joao)
4000
3700

O código define uma função chamada calcular_salario que recebe quatro parâmetros: base, bonus, vale_refeicao, e plano_saude. O parâmetro base é obrigatório, enquanto os demais são opcionais e possuem valores padrão 0, configurados na própria definição da função. Isso significa que, se ao chamar a função, não forem fornecidos valores para bonus, vale_refeicao ou plano_saude, o Python usará automaticamente o valor 0 para esses parâmetros.

A função retorna a soma dos valores dos quatro parâmetros, representando o salário total do funcionário, que inclui a base salarial e os benefícios opcionais.

Na sequência, duas chamadas à função são feitas:

  • A primeira, salario_maria = calcular_salario(4000), passa apenas o valor de base como 4000, sem fornecer valores para os outros parâmetros. Como bonus, vale_refeicao e plano_saude possuem valores padrão de 0, o salário final de Maria será apenas 4000.

  • A segunda, salario_joao = calcular_salario(3000, 500, 200), passa 3000 como o valor de base, 500 como bonus e 200 como vale_refeicao. Percebam que os valores são captados na mesma ordem dos parâmetros. O parâmetro plano_saude não é informado, então o valor padrão de 0 será utilizado. Assim, o salário de João será 3000 + 500 + 200, totalizando 3700.

Esse código ilustra o uso de parâmetros opcionais em funções, permitindo flexibilizar o cálculo de salários com ou sem benefícios adicionais.

Em resumo:

  • Parâmetros obrigatórios: são parâmetros que precisam ser passados ao chamar a função e não possuem um valor padrão. Se eles não forem passados, o Python vai gerar um erro.

  • Parâmetros opcionais: são parâmetros que têm um valor padrão na definição da função e podem ser omitidos ao chamar a função.

É importante ressaltar que todos os parâmetros obrigatórios devem vir antes (à esquerda) dos parâmetros opcionais na definição da função. Ou seja, você não pode ter um parâmetro opcional antes (à esquerda) de um parâmetro obrigatório. Caso isso aconteça, o Python vai gerar o erro SyntaxError: parameter without a default follows parameter with a default, conforme o exemplo abaixo:

# O parâmetro `base`` é obrigatório, os demais são opcionais
# Parâmetro opcional `bonus` está à esquerda do parâmetro obrigatório base
# Por isso o erro SyntaxError: parameter without a default follows parameter with a default
def calcular_salario(
    bonus: int = 0, base: int, vale_refeicao: int = 0, plano_saude: int = 0
) -> int:
    return base + bonus + vale_refeicao + plano_saude
  Cell In[7], line 5
    bonus: int = 0, base: int, vale_refeicao: int = 0, plano_saude: int = 0
                               ^
SyntaxError: non-default argument follows default argument

Parâmetros posicionais e nominais#

Partindo do exemplo abaixo, eu farei uma pergunta pra explicar este tópico.

def calcular_salario(
    base: int, bonus: int = 0, vale_refeicao: int = 0, plano_saude: int = 0
) -> int:
    return base + bonus + vale_refeicao + plano_saude


salario_maria = calcular_salario(4000)
salario_joao = calcular_salario(3000, 500, 200)

Como o Python sabe que ao chamar a função em salario_maria = calcular_salario(4000) o valor 4000 é o valor de base e não de bonus ou vale_refeicao? E em salario_joao = calcular_salario(3000, 500, 200) como o Python sabe que 3000 é o valor de base, 500 é o valor de bonus e 200 é o valor de vale_refeicao?

Basicamente quando informamos os argumentos da função na chamada simplesmente passando o valor, o Python entende que o primeiro valor é o primeiro parâmetro, o segundo valor é o segundo parâmetro, e assim por diante. Isso é chamado de passagem de parâmetros posicionais, pois a ordem dos valores passados é importante.

Existe uma outra forma de passar os argumentos para a função, que é chamada de passagem de parâmetros nomeados. Neste caso, ao chamar a função, você informa o nome do parâmetro seguido de = e o valor que você quer passar. Isso permite que você passe os argumentos em qualquer ordem, e o Python vai associar o valor ao parâmetro correto. Vamos ver o mesmo exemplo, porém usando parâmetros nominais:

def calcular_salario(
    base: int, bonus: int = 0, vale_refeicao: int = 0, plano_saude: int = 0
) -> int:
    return base + bonus + vale_refeicao + plano_saude


salario_maria = calcular_salario(base=4000)
salario_joao = calcular_salario(base=3000, bonus=500, vale_refeicao=200)

Percebam que fica mais claro qual parâmetro recebe qual valor? Isso é uma das vantagens de usar parâmetros nomeados. Usando parâmetros nomeados, você pode passar os argumentos em qualquer ordem, o que pode ser útil em funções com muitos parâmetros, ou quando você quer deixar o código mais claro e legível. Vejamos um exemplo de chamada da função passando parâmetros nomeados em ordem diferente:

salario_joao = calcular_salario(vale_refeicao=200, base=3000, bonus=500)

Podemos inclusive misturar os dois tipos de passagem de parâmetros, mas é importante lembrar que os parâmetros posicionais devem vir antes dos parâmetros nomeados obrigatoriamente. No exemplo abaixo, temos que base=3000 (posicional), bonus=500 (nomeado) e vale_refeicao=200 (nomeado).

salario_joao = calcular_salario(3000, vale_refeicao=200, bonus=500)
print(salario_joao)
3700

Caso algum parâmetro nomeado seja passado antes de um parâmetro posicional, o Python vai gerar o erro SyntaxError: positional argument follows keyword argument dizendo exatamente que parâmetro posicionais não podem vir depois de parâmetros nomeados.

salario_joao = calcular_salario(vale_refeicao=200, bonus=500, 300)
  Cell In[12], line 1
    salario_joao = calcular_salario(vale_refeicao=200, bonus=500, 300)
                                                                     ^
SyntaxError: positional argument follows keyword argument

Um outro erro muito comum também ao misturar parâmetros nomeados e posicionais é informar valores duplicados para um parâmetro, um como posicional e outro como nomeado. Isso também gera um erro, o TypeError: calcular_salario() got multiple values for argument 'base'. Ou seja, algum parâmetro, neste caso base, está recebendo valor como posicional e também como nomeado, conforme mostra o exemplo abaixo.

# O parâmetro `base` recebe 4000 como posicional e 8000 como nomeado, gerando um erro!
salario_maria = calcular_salario(4000, base=8000)
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
Cell In[13], line 2
      1 # O parâmetro `base` recebe 4000 como posicional e 8000 como nomeado, gerando um erro!
----> 2 salario_maria = calcular_salario(4000, base=8000)

TypeError: calcular_salario() got multiple values for argument 'base'

Eu, particularmente, prefiro usar majoritariamente por questões de clareza, parâmetros nomeados (desde que os nomes dos parâmetros sejam bem escolhidos), mas isso é uma questão de estilo de programação e você pode escolher o que achar melhor. A esta altura do capítulo, você provavelmente não vai lembrar mais a ordem dos parâmetros que foi definida na função. Na chamada da função abaixo…

salario_joao = calcular_salario(3000, 500, 200)

… qual posição era mesmo a posição do parâmetro vale_refeicao? Era segundo, terceiro? E plano_saude? bonus? Não dá para saber sem consultar a definição função pra lembrar da ordem!

Já se você usar parâmetros nominais, você não precisa se preocupar com a ordem dos parâmetros, e o código fica mais claro e legível, mesmo que eles forem chamados em ordem diferente da definição da função. Lembrando que o parâmetro bonus é opcional, então não é necessário informá-lo…

salario_joao = calcular_salario(base=3000, vale_refeicao=500, plano_saude=200)

Funções anônimas#

Agora que já aprendemos o básico sobre funções, vamos falar sobre um tipo especial de função chamado de função anônima.

Quando criamos uma função conforme mostrei acima, usando def, estamos criando uma função nomeada na memória do computador. Vou apresentar um exemplo mais simples somente para fins didáticos:

def dobro(numero: int) -> int:
    return numero * 2

Quando executamos o código acima, ele literalmente cria algo na memória do computador que fica disponível para quando quisermos usar. Prova disso é que podemos imprimir a função (somente o objeto da função, sem chamá-la) e ver o que ela é:

def dobro(numero: int) -> int:
    return numero * 2


print(dobro)
<function dobro at 0x7fe0dc90bf70>

Nota (objetos de função vs execução da função)

Reparem que usando apenas dobro, eu estou imprimindo o objeto da função, e não chamando a função com (). É diferente de dobro(10) que é a execução da função. A execução em si requer a passagem dos parâmetros obrigatórios.

O código acima mostra algo do tipo <function dobro at 0x00000235A026ECA0> que representa o endereço de memória onde a função dobro foi armazenada. Isso é o que chamamos de função nomeada.

Existe, porém, um outro tipo de função que é chamada de função anônima, ou lambda. Ela é uma função que não é armazenada na memória do computador, existindo só durante a execução do código. Quando as funções são mais simples, com apenas uma linha. Vamos construir o mesmo exemplo da função dobro acima, mas agora usando uma função anônima:

lambda x: x * 2

Percebam que a função anônima é bem mais simples e direta, sem a necessidade de usar def e return. A função anônima é criada usando a palavra reservada lambda, seguida dos parâmetros e do que a função faz. Os valores à esquerda dos : representam os parâmetros de entrada separados por ,, e à direita, o valor retornado pela função. No caso acima, a função anônima recebe um parâmetro x e retorna o dobro dele, x * 2.

É possível criar uma função lambda que recebe mais de um parâmetro, como no exemplo abaixo:

lambda x, y: x + y

A função anônima é muito útil quando você precisa de uma função simples e rápida, e não quer criar uma função nomeada.

Vamos aplicar ambas as funções para dobrar os valores de uma dada lista de números. Primeiro, vamos usar a função nomeada dobro:

lista_numeros = [1, 2, 3, 4, 5, 6, 7]  # 1


def dobro(numero: int) -> int:  # 2
    return numero * 2


valores_dobrados = list(map(dobro, lista_numeros))  # 3

print(valores_dobrados)  # 4
[2, 4, 6, 8, 10, 12, 14]

No exemplo acima, nós:

  1. Criamos uma lista de números

  2. Definimos a função dobro como uma função nomeada que recebe um parâmetro numero e retorna o dobro dele.

  3. Aplicamos a função dobro a cada elemento da lista usando a função map(), que aplica uma função a todos os elementos de uma sequência. Aqui a função dobro é passada como parâmetro para map(). Transformamos o resultado do map() em uma lista para visualização.

  4. Imprimimos a lista com os valores dobrados.

Agora vamos ver o mesmo exemplo, mas usando a função anônima:

lista_numeros = [1, 2, 3, 4, 5, 6, 7]

valores_dobrados = list(map(lambda x: x * 2, lista_numeros))

print(valores_dobrados)
[2, 4, 6, 8, 10, 12, 14]

Basicamente, ao invés de definir a função e usá-la, nós usamos a função anônima diretamente dentro do map(), sem precisar criar uma função nomeada. A função anônima é mais simples e direta, e é muito usada em situações onde você precisa de uma função simples (funções de uma única linha).