Python & AI Tutorials Logo
Programação Python

23. Funções de Primeira Classe e Técnicas Funcionais

Em capítulos anteriores, aprendemos a definir e chamar funções, trabalhar com parâmetros e argumentos e entender o escopo de variáveis. Agora vamos explorar um recurso poderoso que diferencia o Python: funções são objetos de primeira classe. Isso significa que funções podem ser tratadas como qualquer outro valor — armazenadas em variáveis, passadas como argumentos para outras funções e retornadas por funções.

Essa capacidade abre técnicas elegantes de programação que tornam o código mais flexível, reutilizável e expressivo. Vamos explorar como aproveitar funções de primeira classe por meio de exemplos práticos, entender closures (funções que “lembram” seu ambiente), usar expressões lambda para definições concisas de funções e aplicar funções embutidas como map(), filter(), any() e all() para trabalhar com coleções de forma eficiente.

23.1) Funções como Objetos de Primeira Classe

23.1.1) O Que “Primeira Classe” Significa

Em Python, funções são objetos de primeira classe, o que significa que elas podem ser:

  • Atribuídas a variáveis
  • Armazenadas em estruturas de dados (listas, dicionários etc.)
  • Passadas como argumentos para outras funções
  • Retornadas como valores de outras funções

Isso é diferente de algumas linguagens de programação em que funções têm um status especial e não podem ser manipuladas como valores comuns. Em Python, uma função é apenas mais um tipo de objeto, semelhante a inteiros, strings ou listas.

Vamos ver isso na prática:

python
# Define uma função simples
def greet(name):
    return f"Hello, {name}!"
 
# Atribui a função a uma variável
say_hello = greet
 
# Chama a função através da nova variável
message = say_hello("Alice")
print(message)  # Output: Hello, Alice!
 
# Verifica que ambos os nomes se referem à mesma função
print(greet)      # Output: <function greet at 0x...>
print(say_hello)  # Output: <function greet at 0x...>
print(greet is say_hello)  # Output: True

Repare que quando escrevemos say_hello = greet, não estamos chamando a função (sem parênteses). Estamos criando um novo nome que se refere ao mesmo objeto função. Tanto greet quanto say_hello agora apontam para a mesma função, o que podemos verificar usando o operador is.

23.1.2) Armazenando Funções em Estruturas de Dados

Como funções são objetos, podemos armazená-las em listas, dicionários ou qualquer outra coleção:

python
# Calculadora com operações armazenadas em um dicionário
def add(x, y):
    return x + y
 
def subtract(x, y):
    return x - y
 
def multiply(x, y):
    return x * y
 
def divide(x, y):
    return x / y
 
# Armazena funções em um dicionário
operations = {
    '+': add,
    '-': subtract,
    '*': multiply,
    '/': divide
}
 
# Usa o dicionário para fazer cálculos
num1 = 10
num2 = 5
operator = '*'
 
result = operations[operator](num1, num2)
print(f"{num1} {operator} {num2} = {result}")  # Output: 10 * 5 = 50

Esse padrão é extremamente útil para construir sistemas flexíveis. Em vez de escrever longas cadeias de instruções if-elif para escolher qual função chamar, podemos buscar a função apropriada em um dicionário e chamá-la diretamente.

23.2) Passando Funções como Argumentos

23.2.1) O Conceito Básico

Um dos usos mais poderosos de funções de primeira classe é passá-las como argumentos para outras funções. Isso permite escrever código flexível e reutilizável que pode funcionar com comportamentos diferentes.

Aqui vai um exemplo simples:

python
# Função que aplica outra função a um valor
def apply_operation(value, operation):
    """Aplica a função operation recebida como parâmetro ao valor."""
    return operation(value)
 
# Operações diferentes
def double(x):
    return x * 2
 
def square(x):
    return x * x
 
def negate(x):
    return -x
 
# Usa a mesma função apply_operation com diferentes operações
number = 5
print(apply_operation(number, double))   # Output: 10
print(apply_operation(number, square))   # Output: 25
print(apply_operation(number, negate))   # Output: -5

A função apply_operation não sabe nem se importa com qual operação específica está realizando. Ela simplesmente chama qualquer função que seja passada para ela. Essa separação de responsabilidades torna o código mais modular e fácil de estender.

23.2.2) Processando Coleções com Funções Personalizadas

Um padrão comum é processar cada item de uma coleção usando uma função passada como argumento:

python
# Processa cada item em uma lista usando uma função fornecida
def process_list(items, processor):
    """Aplica a função processor a cada item da lista."""
    results = []
    for item in items:
        results.append(processor(item))
    return results
 
# Funções diferentes de processamento
def uppercase(text):
    return text.upper()
 
def add_exclamation(text):
    return text + "!"
 
def get_length(text):
    return len(text)
 
# Processa a mesma lista de diferentes formas
words = ["hello", "world", "python"]
 
print(process_list(words, uppercase))        # Output: ['HELLO', 'WORLD', 'PYTHON']
print(process_list(words, add_exclamation))  # Output: ['hello!', 'world!', 'python!']
print(process_list(words, get_length))       # Output: [5, 5, 6]

Esse padrão é tão útil que o Python fornece funções embutidas como map() e filter() que funcionam dessa forma (vamos explorar isso na Seção 23.6).

23.2.3) Ordenando ao Fornecer uma Função de Chave (Introdução Breve)

A função sorted() do Python aceita um parâmetro key — uma função que determina como comparar itens:

python
# Ordena alunos por critérios diferentes
students = [
    {"name": "Alice", "grade": 85, "age": 20},
    {"name": "Bob", "grade": 92, "age": 19},
    {"name": "Charlie", "grade": 78, "age": 21},
    {"name": "Diana", "grade": 95, "age": 20}
]
 
# Função para extrair a nota
def get_grade(student):
    return student["grade"]
 
# Função para extrair o nome
def get_name(student):
    return student["name"]
 
# Ordena por nota (crescente)
by_grade = sorted(students, key=get_grade)
print("Sorted by grade:")
for student in by_grade:
    print(f"  {student['name']}: {student['grade']}")
# Output:
#   Charlie: 78
#   Alice: 85
#   Bob: 92
#   Diana: 95
 
# Ordena por nome (alfabeticamente)
by_name = sorted(students, key=get_name)
print("\nSorted by name:")
for student in by_name:
    print(f"  {student['name']}: {student['grade']}")
# Output:
#   Alice: 85
#   Bob: 92
#   Charlie: 78
#   Diana: 95

A função key é chamada uma vez para cada item, e seu valor de retorno é usado para comparação. Isso é muito mais flexível do que ter que escrever uma lógica de ordenação personalizada.

Esse padrão de passar funções para personalizar o comportamento é extremamente comum em Python. Vamos explorar técnicas de ordenação mais avançadas no Capítulo 38.

23.3) Retornando Funções a Partir de Funções

23.3.1) Funções Que Criam Funções

Assim como podemos passar funções como argumentos, também podemos retornar funções a partir de outras funções. Isso nos permite criar funções especializadas dinamicamente:

python
# Função que cria e retorna uma nova função
def create_multiplier(factor):
    """Cria uma função que multiplica pelo fator fornecido."""
    def multiplier(x):
        return x * factor
    return multiplier
 
# Cria funções multiplicadoras especializadas
double = create_multiplier(2)
triple = create_multiplier(3)
times_ten = create_multiplier(10)
 
# Usa as funções criadas
print(double(5))      # Output: 10
print(triple(5))      # Output: 15
print(times_ten(5))   # Output: 50

O que está acontecendo aqui? A função create_multiplier define uma função interna chamada multiplier e a retorna. Cada vez que chamamos create_multiplier com um fator diferente, recebemos de volta uma nova função que “lembra” aquele fator específico. Esse é o nosso primeiro vislumbre de closures, que vamos explorar a fundo na próxima seção.

23.3.2) Criando Validadores Personalizados

Retornar funções é particularmente útil para criar funções personalizadas de validação ou processamento:

python
# Cria validadores de faixa dinamicamente
def create_range_validator(min_value, max_value):
    """Cria uma função que valida se um número está na faixa."""
    def validator(number):
        return min_value <= number <= max_value
    return validator
 
# Cria validadores específicos
is_valid_age = create_range_validator(0, 120)
is_valid_percentage = create_range_validator(0, 100)
is_room_temperature = create_range_validator(15, 30)
 
# Usa os validadores
age = 25
print(f"Is {age} a valid age? {is_valid_age(age)}")  # Output: True
 
temp = 22
print(f"Is {temp}°C room temperature? {is_room_temperature(temp)}")  # Output: True
 
score = 150
print(f"Is {score} a valid percentage? {is_valid_percentage(score)}")  # Output: False

23.4) Entendendo Closures: Funções Que Lembram

23.4.1) O Que É uma Closure?

Uma closure é uma função que “lembra” variáveis do escopo onde foi criada, mesmo depois que esse escopo terminou de executar. Nos exemplos da Seção 23.3, já estávamos usando closures sem nomeá-las explicitamente.

Vamos examinar como closures funcionam:

python
def create_counter(start=0):
    """Cria uma função contador que lembra sua contagem."""
    count = start  # Essa variável é "capturada" pela closure
    
    def counter():
        nonlocal count  # Acessa a variável capturada
        count += 1
        return count
    
    return counter
 
# Cria dois contadores independentes
counter1 = create_counter(0)
counter2 = create_counter(100)
 
# Cada contador mantém sua própria contagem
print(counter1())  # Output: 1
print(counter1())  # Output: 2
print(counter1())  # Output: 3
 
print(counter2())  # Output: 101
print(counter2())  # Output: 102
 
print(counter1())  # Output: 4 (counter1 is independent of counter2)

A função interna counter forma uma closure sobre a variável count. Mesmo que create_counter já tenha terminado a execução, a função counter retornada ainda tem acesso a count. Cada chamada a create_counter cria uma nova closure, independente, com sua própria variável count.

23.4.2) Como Closures Capturam Variáveis

Quando uma função é definida dentro de outra função, ela pode acessar variáveis do escopo da função externa. Essas variáveis são “capturadas” e permanecem acessíveis mesmo depois que a função externa retorna:

Quando o Python cria a função interna, ele não salva apenas o código da função — ele também salva referências a quaisquer variáveis da função externa que a função interna usa. Esse processo é chamado de “captura” de variáveis.

python
def create_greeter(greeting):
    """Cria uma função de saudação com uma saudação personalizada."""
    def greet(name):
        return f"{greeting}, {name}!"
    return greet
 
# Cria diferentes saudadores
say_hello = create_greeter("Hello")
say_hi = create_greeter("Hi")
say_bonjour = create_greeter("Bonjour")
 
# Cada saudador lembra sua saudação específica
print(say_hello("Alice"))    # Output: Hello, Alice!
print(say_hi("Bob"))         # Output: Hi, Bob!
print(say_bonjour("Claire")) # Output: Bonjour, Claire!

O parâmetro greeting é capturado pela closure. Cada função saudadora tem seu próprio valor capturado de greeting que ela usa sempre que é chamada.

23.4.3) Uso Prático: Funções de Configuração

Closures são excelentes para criar funções com comportamento pré-configurado:

python
# Cria calculadoras de preço com diferentes taxas de imposto
def create_price_calculator(tax_rate):
    """Cria uma calculadora que aplica uma taxa de imposto específica."""
    def calculate_total(price):
        tax = price * tax_rate
        return price + tax
    return calculate_total
 
# Cria calculadoras para diferentes regiões
us_calculator = create_price_calculator(0.07)    # imposto de 7%
uk_calculator = create_price_calculator(0.20)    # VAT de 20%
japan_calculator = create_price_calculator(0.10) # imposto de consumo de 10%
 
# Calcula preços em diferentes regiões
item_price = 100
 
print(f"US total: ${us_calculator(item_price):.2f}")      # Output: US total: $107.00
print(f"UK total: £{uk_calculator(item_price):.2f}")      # Output: UK total: £120.00
print(f"Japan total: ¥{japan_calculator(item_price):.2f}") # Output: Japan total: ¥110.00

23.4.4) Quando Usar Closures

Closures são particularmente úteis quando você precisa:

  • Criar funções com comportamento pré-configurado
  • Manter estado entre chamadas de função sem usar classes
  • Implementar funções de callback que precisam lembrar o contexto
  • Criar fábricas de funções que produzem funções especializadas

23.5) Usando lambda para Funções Anônimas Curtas

23.5.1) O Que São Expressões Lambda?

Uma expressão lambda cria uma função pequena e anônima — uma função sem nome. Expressões lambda são úteis quando você precisa de uma função simples por um curto período e não quer defini-la formalmente com def.

A sintaxe é:

python
lambda parameters: expression

A lambda recebe parâmetros (como uma função regular) e retorna o resultado de avaliar a expressão. Aqui vai um exemplo simples:

python
# Função regular
def add(x, y):
    return x + y
 
# Expressão lambda equivalente
add_lambda = lambda x, y: x + y
 
# Ambas funcionam do mesmo jeito
print(add(3, 5))        # Output: 8
print(add_lambda(3, 5)) # Output: 8

Expressões lambda são limitadas a uma única expressão — elas não podem conter instruções como if, for ou múltiplas linhas de código. Essa limitação as mantém simples e focadas.

23.5.2) Expressões Lambda como Argumentos

Expressões lambda brilham quando você precisa passar uma função simples como argumento e não quer definir uma função nomeada separada:

python
# Ordena alunos por nota usando lambda
students = [
    {"name": "Alice", "grade": 85},
    {"name": "Bob", "grade": 92},
    {"name": "Charlie", "grade": 78},
    {"name": "Diana", "grade": 95}
]
 
# Em vez de definir uma função separada:
# def get_grade(student):
#     return student["grade"]
# sorted_students = sorted(students, key=get_grade)
 
# Podemos usar uma lambda diretamente:
sorted_students = sorted(students, key=lambda student: student["grade"])
 
print("Students sorted by grade:")
for student in sorted_students:
    print(f"  {student['name']}: {student['grade']}")
# Output:
#   Charlie: 78
#   Alice: 85
#   Bob: 92
#   Diana: 95

Isso é mais conciso quando a função é simples e usada apenas uma vez. A lambda lambda student: student["grade"] é equivalente a uma função que recebe um aluno e retorna sua nota.

23.5.3) Lambda com Múltiplos Parâmetros

Expressões lambda podem receber múltiplos parâmetros, assim como funções regulares:

python
# Operações de calculadora usando lambda
operations = {
    'add': lambda x, y: x + y,
    'subtract': lambda x, y: x - y,
    'multiply': lambda x, y: x * y,
    'divide': lambda x, y: x / y if y != 0 else "Error"
}
 
# Usa as expressões lambda
print(operations['add'](10, 5))       # Output: 15
print(operations['multiply'](10, 5))  # Output: 50
print(operations['divide'](10, 0))    # Output: Error

Repare como podemos usar uma expressão condicional (x / y if y != 0 else "Error") dentro de uma lambda, mas não podemos usar uma instrução if (o que exigiria múltiplas linhas).

23.5.4) Quando Usar Lambda vs Funções Nomeadas

Use expressões lambda quando:

  • A função é muito simples (uma expressão)
  • A função é usada apenas uma vez ou em um contexto bem localizado
  • Definir uma função nomeada adicionaria verbosidade desnecessária

Use uma função nomeada quando:

  • A função é complexa ou exige múltiplas instruções
  • A função será reutilizada em vários lugares
  • A função precisa de um nome descritivo para clareza
  • A função precisa de uma docstring

23.5.5) Limitações de Lambda e Alternativas

Expressões lambda têm limitações importantes:

python
# ❌ Isso não vai funcionar - lambda não pode conter instruções
# bad_lambda = lambda x: 
#     if x > 0:
#         return x
#     else:
#         return -x
 
# ✅ Use uma expressão condicional em vez disso
absolute_value = lambda x: x if x > 0 else -x
print(absolute_value(-5))  # Output: 5
print(absolute_value(3))   # Output: 3
 
# ✅ Para múltiplas operações, use uma função regular
def process_and_double(x):
    print(f"Processing: {x}")
    return x * 2
 
result = process_and_double(5)  # Output: Processing: 5
print(result)                    # Output: 10

Expressões lambda são ferramentas para situações específicas. Quando elas tornam o código mais claro e conciso, use-as. Quando elas tornam o código mais difícil de entender, use uma função nomeada regular em vez disso.

23.6) Usando map() e filter() com Funções Simples

23.6.1) A Função map()

A função map() aplica uma function fornecida a cada item de um iterável (como uma lista, tupla ou string) e retorna um iterador contendo os resultados. É uma forma de transformar cada elemento de uma coleção sem escrever um loop explícito.

python
map(function, iterable, *iterables)

Parâmetros:

  • function (obrigatório): Uma função que recebe um ou mais argumentos, os processa e retorna um valor. A função é chamada uma vez para cada elemento no(s) iterable(s).
  • iterable (obrigatório): Uma sequência (lista, tupla, string etc.) cujos elementos serão passados para a function.
  • *iterables (opcional): Iteráveis adicionais para uma function com múltiplos argumentos.

Se vários iteráveis forem fornecidos, function deve aceitar essa quantidade de argumentos
map() vai parar quando o iterável mais curto se esgotar

Retorna:

Um objeto map (iterador) contendo os resultados retornados pela function para cada elemento de entrada.

Importante: o objeto map é um iterador, não uma sequência como uma list.

python
# Dobra cada número em uma lista
numbers = [1, 2, 3, 4, 5]
 
def double(x):
    return x * 2
 
# Aplica double a cada número
doubled = map(double, numbers)
result = list(doubled)  # Converte o objeto map (iterador) para list
print(result)  # Output: [2, 4, 6, 8, 10]

23.6.2) Usando map() com Lambda

Expressões lambda funcionam perfeitamente com map() para transformações simples:

python
# Converte temperaturas de Celsius para Fahrenheit
celsius_temps = [0, 10, 20, 30, 40]
 
fahrenheit_temps = list(map(lambda c: (c * 9/5) + 32, celsius_temps))
print(fahrenheit_temps)  # Output: [32.0, 50.0, 68.0, 86.0, 104.0]

23.6.3) A Função filter()

A função filter() aplica uma function fornecida a cada item de um iterable e retorna um iterador contendo apenas os itens para os quais a função retorna True. É uma forma de selecionar elementos de uma coleção sem escrever um loop explícito.

python
filter(function, iterable)

Parâmetros:

  • function: Uma função que recebe um argumento, o avalia e retorna True ou False. A função é chamada uma vez para cada elemento no iterable.
  • iterable: Uma sequência (lista, tupla, string etc.) cujos elementos serão testados pela function.

Retorna:

Um objeto filter (iterador) contendo apenas os elementos para os quais a function retornou True.

Importante: o objeto filter é um iterador, não uma sequência como uma lista.

Exemplo:

python
# Mantém apenas números pares
numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
 
def is_even(x):
    return x % 2 == 0
 
# Aplica is_even a cada número, mantém apenas os que retornam True
even_numbers = filter(is_even, numbers)
result = list(even_numbers)  # Converte o objeto filter para list
print(result)  # Output: [2, 4, 6, 8, 10]

23.6.4) Usando filter() com Lambda

Expressões lambda são comumente usadas com filter() para filtragem concisa:

python
# Filtra alunos que passaram (grade >= 60)
students = [
    {"name": "Alice", "grade": 85},
    {"name": "Bob", "grade": 55},
    {"name": "Charlie", "grade": 92},
    {"name": "Diana", "grade": 48},
    {"name": "Eve", "grade": 73}
]
 
passed = list(filter(lambda s: s["grade"] >= 60, students))
print("Students who passed:")
for student in passed:
    print(f"  {student['name']}: {student['grade']}")
# Output:
#   Alice: 85
#   Charlie: 92
#   Eve: 73

23.6.5) Combinando map() e filter()

Você pode encadear operações de map() e filter() para realizar transformações complexas:

python
# Obtém quadrados de números pares
numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
 
# Primeiro filtra números pares e depois eleva ao quadrado
even_numbers = filter(lambda x: x % 2 == 0, numbers)
squared = map(lambda x: x ** 2, even_numbers)
result = list(squared)
print(result)  # Output: [4, 16, 36, 64, 100]

Comparação Visual: map() vs filter()

filter() - Mantém ALGUNS itens

Entrada: [1, 2, 3, 4, 5]

Teste: is_even(x)

Saída: [2, 4](igual ou menor)

map() - Transforma TODOS os itens

Entrada: [1, 2, 3, 4, 5]

Aplica: double(x) = x * 2

Saída: [2, 4, 6, 8, 10](mesmo tamanho)

Principais diferenças:

  • map(): Aplica uma função para transformar cada item → a saída tem o mesmo tamanho
  • filter(): Testa cada item e mantém apenas os que passam → a saída tem tamanho igual ou menor

Neste capítulo, exploramos os recursos poderosos de programação funcional do Python. Aprendemos que funções são objetos de primeira classe que podem ser passadas como qualquer outro valor, permitindo padrões de código flexíveis e reutilizáveis. Vimos como funções podem retornar outras funções, criando closures que lembram seu ambiente. Exploramos expressões lambda para definições concisas de funções e usamos map() e filter() para processar coleções de forma elegante.

Esses conceitos formam a base para técnicas avançadas de programação em Python. No Capítulo 38, vamos desenvolver esse conhecimento para dominar decorators, um dos recursos mais elegantes do Python.

© 2025. Primesoft Co., Ltd.
support@primesoft.ai