Curso
Uma boa estrutura é o sinal de um código bem escrito. A natureza dessa estrutura é determinada pelo paradigma de programação utilizado. Um paradigma de programação é um conjunto coerente de princípios e técnicas que definem a abordagem adotada ao projetar e codificar programas de computador.
Existem vários paradigmas de programação, como o procedural, imperativo, declarativo, orientado a objetos e funcional. Algumas linguagens se restringem a poucos paradigmas. Por exemplo, SQL é uma linguagem declarativa. Já muitas linguagens modernas, incluindo Python, suportam vários paradigmas, oferecendo mais flexibilidade.
Pratique escrever funções puras com um exercício prático do nosso curso Introduction to Programming Paradigms.
Muitos programadores acabam adotando um paradigma preferido, geralmente o primeiro que aprenderam. Profissionais mais avançados entendem as vantagens e limitações de cada paradigma e sabem aproveitar os pontos fortes de cada um em cenários diferentes. Vamos revisar dois dos paradigmas mais usados em data science: programação funcional e programação orientada a objetos.

O que é programação funcional (FP)?
Na programação funcional, o código é construído definindo e usando funções que ditam como o programa deve operar. Ela é um subtipo do paradigma de programação declarativa.
Funções são trechos de código que recebem argumentos e retornam saídas. Uma função simples pode receber uma lista de números como argumento e retornar apenas os valores pares, por exemplo.
def get_even_numbers(list):
return [num for num in list if num % 2 == 0]
Confira estes cursos para aprender as melhores práticas ao escrever funções em Python e R.
A programação funcional se destaca pela rastreabilidade e previsibilidade. Isso acontece porque as funções usadas são imutáveis, ou seja, não podem ser alteradas. As funções são definidas, muitas vezes em uma seção separada do código (ou até em outro arquivo), e então usadas ao longo do código conforme necessário. Essa característica facilita entender o que está acontecendo em um trecho de código, já que a função se comporta e é chamada da mesma forma em todos os lugares.
O conceito de imutabilidade também se estende às estruturas de dados na programação funcional. Em FP, é preferível criar uma cópia dos dados ao mudar seu estado. Isso cria um rastro virtual de todas as transformações aplicadas aos dados, aumentando a rastreabilidade e facilitando a identificação de problemas. Porém, é importante notar que essa abordagem costuma aumentar o consumo de memória, já que cada mudança de estado envolve copiar todo o conjunto de dados.
Um subconjunto de funções usadas em programação funcional são as funções de ordem superior. São funções que recebem outra função como argumento ou retornam uma função como resultado. Por exemplo, você pode ter uma função de ordem superior que executa uma função várias vezes, como a abaixo, ou uma que escolhe a função correta para usar em um cenário específico.
def run_function_multiple_times(function, num_times):
for i in range(num_times):
function()
Vantagens da programação funcional na análise de dados
A programação funcional oferece uma estrutura para reduzir duplicação no seu código. A ideia é facilitar a expressão de ações repetidas usando verbos de alto nível.
Por exemplo, suponha que você precise modificar strings para se adequar a um formato específico em vários pontos do seu código. Esse formato exige remover espaços, pontuação e números, além de deixar tudo em minúsculas. Você poderia copiar e colar várias linhas de código em diferentes partes do programa. Mas essa duplicação deixaria o código inchado. Em vez disso, você pode simplesmente definir uma função para limpar suas strings e chamá-la nos lugares certos. Isso enxuga o código, o deixa mais legível e mais rápido de escrever.
def clean_string(string):
exclude = set(string.punctuation + string.digits + " ")
return ''.join(ch.lower() for ch in string if ch not in exclude)
my_string = "Hello, World! 123"
new_string = clean_string(my_string)
print(new_string)
Compor funções para construir pipelines de dados complexos
A programação funcional é útil ao trabalhar com grandes volumes de dados ou pipelines porque permite dividir tarefas complexas de processamento em etapas menores e mais gerenciáveis. Geralmente, ao menos uma função é criada para cada etapa do pipeline. Se você anotou um fluxograma das etapas necessárias, já terá uma boa ideia de quais funções vai precisar. Em linhas gerais, você terá pelo menos uma função por etapa.

A maioria das linguagens também tem várias funções nativas e bibliotecas extras que podem ser instaladas. O pacote purrr em R é um ótimo exemplo. Isso facilita montar um pipeline rapidamente, reduzindo o número de funções personalizadas que você precisa criar.
Criar código expressivo e legível usando princípios de FP
A programação funcional busca produzir um código muito legível. Aqui vão algumas diretrizes para construir código expressivo e fácil de ler usando FP:
- Mantenha as funções pequenas e focadas: cada função deve ter uma responsabilidade única e clara, facilitando o entendimento e a manutenção. Essas são as chamadas funções puras.
- Evite estado mutável (em mudança): evite mudar o estado dos seus dados para eliminar efeitos colaterais e tornar o fluxo mais previsível. Estruturas imutáveis contribuem para a clareza do código e do raciocínio. Em vez de modificar uma estrutura, crie uma nova com as alterações desejadas.
- Aproveite funções de ordem superior: funções que recebem outras funções como argumentos ou retornam funções são chamadas de ordem superior. Elas permitem escrever código mais expressivo e conciso.
- Use composição de funções: composição é combinar duas ou mais funções para produzir uma nova função. Isso ajuda a quebrar problemas complexos em partes menores e mais fáceis de gerenciar.
O que é programação orientada a objetos (OOP)?
A programação orientada a objetos é um paradigma que foca em objetos. Objetos são entidades autocontidas que reúnem dados e métodos que operam sobre esses dados. É uma forma de manter os dados e suas operações próximos, diferente da programação funcional, em que eles costumam ficar separados. Isso pode facilitar o acompanhamento do que acontece com seus dados.
Na programação orientada a objetos, dados e métodos são organizados em objetos definidos por sua classe. As classes determinam como cada objeto deve se comportar, e então os objetos são criados dentro dessa classe. Confira o tutorial da DataCamp para mais detalhes sobre como as classes funcionam em Python.
Exemplo de OOP
Por exemplo, você pode ter uma classe chamada BankAccount que descreve como cada conta deve se comportar. Essa classe pode ter atributos como saldo, número de ID e nome do cliente. Também pode ter métodos que descrevem como as informações dentro da classe podem ser afetadas, como saque, depósito e consulta de saldo.
Depois que essa classe for definida, você pode criar um objeto, neste caso, a conta de alguém. Com o objeto criado, você preenche os dados de cada propriedade e usa os métodos para modificar a conta. Veja o exemplo abaixo.
# Define uma classe que representa uma conta bancária
class BankAccount:
def __init__(self, account_id, customer_name, initial_balance=0.0):
# Inicializa as propriedades da conta
self.account_id = account_id
self.customer_name = customer_name
self.balance = initial_balance
# Método para depositar dinheiro na conta
def deposit(self, amount):
"""Deposita dinheiro na conta."""
if amount > 0:
# Atualiza o saldo e imprime informações do depósito
self.balance += amount
print(f"Deposited ${amount}. New balance: ${self.balance}")
else:
# Mensagem para valor de depósito inválido
print("Invalid deposit amount. Please deposit a positive amount.")
# Método para sacar dinheiro da conta
def withdraw(self, amount):
"""Saca dinheiro da conta."""
if 0 < amount <= self.balance:
# Atualiza o saldo e imprime informações do saque
self.balance -= amount
print(f"Withdrew ${amount}. New balance: ${self.balance}")
elif amount > self.balance:
# Mensagem para fundos insuficientes
print("Insufficient funds. Withdrawal canceled.")
else:
# Mensagem para valor de saque inválido
print("Invalid withdrawal amount. Please withdraw a positive amount.")
# Método para verificar o saldo atual da conta
def check_balance(self):
"""Verifica o saldo atual da conta."""
print(f"Current balance for {self.customer_name}'s account (ID: {self.account_id}): ${self.balance}")
Encapsulamento
Esse processo de agrupar seus dados e comportamentos em um objeto definido por uma classe é chamado de encapsulamento. Ele permite criar uma estrutura bem definida que facilita a interação do restante do código com o objeto. As variáveis armazenadas dentro do objeto são chamadas de atributos, e as funções que determinam o comportamento do objeto são chamadas de métodos.
Herança
Uma forma de reduzir duplicação de código é usar herança ao criar novas classes. É um jeito de criar classes que mantêm funcionalidades das classes pai. No nosso exemplo de conta bancária, você pode ter uma classe pai generalista para todas as contas e classes filhas com especificidades para contas poupança e contas-correntes.
Polimorfismo
Polimorfismo é um conceito que permite usar o mesmo nome para métodos com comportamentos diferentes dependendo da entrada. É comum ser usado junto com herança.
Por exemplo, suponha que temos uma classe pai chamada Shape, com um método para calcular a área da forma. Você pode ter duas classes filhas, Circle e Square. Embora ambas tenham um método chamado Area, a definição desse método será diferente para cada forma. Compare os métodos de área para as diferentes formas no bloco de código abaixo.
# Define a classe pai Shape
class Shape:
# Inicializa os atributos da forma
def __init__(self, name):
self.name = name
# Método genérico para calcular a área
def area(self):
print(f"The area of {self.name} is unknown.")
# Define a classe filha Circle que herda de Shape
class Circle(Shape):
# Inicializa os atributos do círculo
def __init__(self, name, radius):
# Chama o construtor da classe pai
super().__init__(name)
self.radius = radius
# Sobrescreve o método area para o círculo
def area(self):
# Usa a fórmula pi * r^2
area = 3.14 * self.radius ** 2
print(f"The area of {self.name} is {area}.")
# Define a classe filha Square que herda de Shape
class Square(Shape):
# Inicializa os atributos do quadrado
def __init__(self, name, side):
# Chama o construtor da classe pai
super().__init__(name)
self.side = side
# Sobrescreve o método area para o quadrado
def area(self):
# Usa a fórmula s^2
area = self.side ** 2
print(f"The area of {self.name} is {area}.")
Há vários recursos para aprender a usar programação orientada a objetos, incluindo o curso de OOP em Python da DataCamp, o curso de OOP em R e o tutorial de OOP em Python.
Vantagens da programação orientada a objetos na análise de dados
A programação orientada a objetos pode oferecer várias vantagens para a análise de dados:
- Encapsulamento de dados: o encapsulamento ajuda a ocultar detalhes internos de algoritmos e estruturas de análise de dados, expondo apenas as interfaces necessárias. Isso promove uma separação limpa de componentes e melhora a manutenção do código.
- Reutilização de código: a programação orientada a objetos incentiva a criação de classes genéricas que podem ser herdadas por outras classes, evitando duplicação de código e reduzindo o esforço de manutenção. Isso pode melhorar a produtividade e a eficiência da análise de dados.
- Benefícios de design: modelar problemas do mundo real com classes e objetos que representam dados e comportamentos aumenta a legibilidade, a clareza e a modularidade na análise de dados.
OOP vs. FP: escolhendo o paradigma certo
Há muitos fatores a considerar ao escolher entre OOP e FP para seu projeto de dados. Alguns times de dados podem preferir fortemente um paradigma em vez do outro; nesse caso, alinhar seu paradigma ao do time é o ideal. Em outros, a natureza dos seus dados ou aplicações pode pesar a balança para um lado.
Quando usar OOP em tarefas de dados
A programação orientada a objetos é excelente para aplicações em que você precisa modelar entidades. Ótimos exemplos incluem projetar um sistema de gestão de biblioteca ou criar um sistema para pontuar cada empresa em uma carteira de investimentos. O processo de criar objetos encapsulados facilita acompanhar essas entidades. OOP também é preferida quando você tem memória limitada, já que FP tende a exigir bem mais memória do que OOP.
Quando usar FP em tarefas de dados
A programação funcional se destaca em tarefas de análise de dados que exigem processamento paralelo. O foco em imutabilidade e em evitar efeitos colaterais facilita paralelizar operações em um conjunto de dados. Funções podem ser aplicadas simultaneamente em partes diferentes dos dados sem interferência.
O foco da FP em imutabilidade também a torna ideal para pipelines de transformação de dados. As funções podem ser compostas para criar uma série de transformações sem mutar estado, resultando em um código mais legível e fácil de manter.
FP também costuma ser a preferida de cientistas e acadêmicos, por manter um foco mais próximo da matemática pura.
Combinando técnicas de OOP e FP
Em muitas aplicações práticas, OOP e FP podem ser combinadas em um mesmo programa. Isso permite que desenvolvedores aproveitem os pontos fortes de cada paradigma. O encapsulamento de OOP e a expressividade de FP podem ser usados juntos para construir um pipeline de machine learning ou uma aplicação web.
No entanto, combinar paradigmas pode trazer desafios e deve ser feito com cuidado. Pode ser mais difícil entender um código que alterna entre paradigmas. Também pode ser desafiador garantir a integração suave dos componentes.
Considere o exemplo abaixo. Este código em Python combina OOP e FP para realizar operações matemáticas em uma lista de números. A classe MathOperation funciona como um contêiner para uma operação matemática específica criada usando FP. As funções filter_odd_numbers e square_operation ajudam a filtrar números ímpares e calcular o quadrado de um número, respectivamente. Usando esses componentes em conjunto, o código filtra de forma eficiente os números ímpares de uma lista e depois eleva cada um ao quadrado, mostrando como OOP e FP podem trabalhar juntas para simplificar e organizar tarefas de análise de dados.
# Programação Orientada a Objetos (OOP) - Definição da classe
class MathOperation:
def __init__(self, operation):
self.operation = operation
def apply_operation(self, number):
return self.operation(number)
# Programação Funcional (FP) - Funções de ordem superior
def filter_odd_numbers(numbers):
return list(filter(lambda x: x % 2 != 0, numbers))
def square_operation(number):
return number ** 2
def process_numbers(numbers, operation_function):
return list(map(operation_function, numbers))
# Exemplo de uso:
# Define uma classe OOP que representa uma operação matemática (quadrado, neste caso)
square_operation_instance = MathOperation(square_operation)
# Lista de números de exemplo
numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
# Usa FP para filtrar números ímpares
odd_numbers = filter_odd_numbers(numbers)
# Usa OOP para aplicar a operação de quadrado aos números ímpares
result = process_numbers(odd_numbers, square_operation_instance.apply_operation)
# Exibe o resultado
print(result)
Para outro exemplo de uso combinado de OOP e FP, confira este repositório no GitHub.
Conclusão
Não existe uma resposta definitiva sobre qual paradigma de programação é a melhor escolha: isso depende do contexto, do objetivo, da linguagem e da preferência de quem programa. Ainda assim, há diretrizes gerais que você pode seguir para decidir qual paradigma funcionará melhor na sua situação.
Use OOP quando precisar modelar sistemas complexos com múltiplas entidades e interações e quando for necessário encapsular dados e comportamentos em componentes reutilizáveis. Use FP quando precisar realizar cálculos puros com entradas e saídas simples e quando for importante evitar efeitos colaterais ou mudanças de estado. Use uma abordagem mista quando quiser aproveitar os pontos fortes de ambos os paradigmas e quando precisar se adaptar aos recursos e limitações da linguagem.
Os melhores programadores não ficam presos a um único paradigma; eles transitam entre paradigmas conforme necessário para alcançar seus objetivos. Explore diferentes paradigmas em profundidade com o curso Introduction to Programming Paradigms. Experimente trabalhar em um projeto de dados e use tanto OOP quanto FP para resolvê-lo. Praticar cada paradigma vai te dar uma boa intuição sobre qual funciona melhor para o seu projeto.
Sou PhD e tenho 13 anos de experiência trabalhando com dados em um ambiente de pesquisa biológica. Crio software em várias linguagens de programação, incluindo Python, MATLAB e R. Sou apaixonado por compartilhar meu amor pelo aprendizado com o mundo.
