Fundamentos Essenciais de Programação Python

Seu Primeiro Programa em Python

Iniciar sua jornada em Python é simples. O programa mais básico que você pode escrever para verificar se tudo está funcionando corretamente é o famoso "Olá, Mundo!".

print("Olá, Mundo!")

Métodos para Executar um Programa Python

Existem diversas maneiras de executar código Python, cada uma com suas particularidades. As duas abordagens mais comuns são o modo interativo e a execução de um script.

1. Modo Interativo

O interpretador Python pode ser acessado diretamente a partir da linha de comando, permitindo a execução de código linha a linha. Isso é particularmente útil para testar pequenos trechos de código ou explorar funcionalidades.

  • Para iniciar, abra seu terminal (ou Prompt de Comando no Windows) e digite python (certifique-se de que Python está no seu PATH).
  • **Vantagem:** Feedback imediato. Cada linha de código digitada é executada e seu resultado exibido na hora.
  • **Desvantagem:** O código não é salvo permanentemente. Ao sair do interpretador, todo o trabalho é perdido.

2. Execução de um Arquivo Python (Script)

Para projetos maiores ou códigos que precisam ser persistentes, você escreve seu programa em um arquivo com extensão .py e o executa. Esta é a forma padrão de desenvolver aplicações em Python.

  • **Via linha de comando:**
    • Especifique o caminho absoluto: python /caminho/completo/do/seu_script.py
    • Especifique o caminho relativo (funciona se você estiver no diretório do script): python seu_script.py
  • **Usando um Ambiente de Desenvolvimento Integrado (IDE) como o PyCharm:**
    • No PyCharm, você pode clicar com o botão direito em qualquer parte do código e selecionar "Run".
    • **Vantagem:** IDEs oferecem funcionalidades como salvamento automático, realce de sintaxe e verificação de erros em tempo real.

Variáveis em Python

Variáveis são como "caixas" nomeadas na memória do computador que armazenam valores. O "nome" da caixa permite que você acesse e manipule o valor armazenado.

O que são Variáveis?

No contexto de programação, uma variável é um símbolo ou nome que se refere a um local de armazenamento na memória do computador. Esse local contém dados que podem ser modificados durante a execução do programa.

Por que Usar Variáveis?

Variáveis permitem que o computador "lembre" e utilize informações. Sem elas, seria impossível manipular dados dinamicamente, tornando a maioria dos programas inúteis.

Como Usar Variáveis

A sintaxe para definir uma variável em Python é simples: nome_da_variavel = valor.

# Definindo uma variável
nome_usuario = 'Alice'
idade_usuario = 30

# Usando variáveis
print(nome_usuario)
print(idade_usuario)

# O valor de uma variável pode ser alterado
idade_usuario = 31
print(f"Nova idade: {idade_usuario}")

Regras de Nomenclatura de Variáveis:

  1. **Clareza (legibilidade):** Escolha nomes que indiquem o propósito da variável (ex: valor\_total em vez de vt).
  2. **Não inicie com número:** Nomes de variáveis não podem começar com dígitos (ex: 1nome é inválido, nome1 é válido).
  3. **Caracteres permitidos:** Podem conter letras (maiúsculas e minúsculas), números e sublinhados (\_). Ex: nome\_completo, valor\_item\_2. Python diferencia maiúsculas de minúsculas (Nome é diferente de nome).
  4. **Keywords:** Não use palavras-chave reservadas do Python (ex: if, for, class).

Exemplos de Nomes de Variáveis:

# Usando sublinhados (snake_case - recomendado para variáveis em Python)
idade_do_aluno = 22

# Usando CamelCase (comum em outras linguagens, mas menos em Python para variáveis)
NomeDoProfessor = "Professor Silva"

# Constantes (convenção: todas maiúsculas)
VALOR_MAXIMO = 1000
PI_VALOR = 3.14159

Em Python, por convenção, usamos letras maiúsculas para indicar que uma variável é uma "constante", ou seja, seu valor não deve ser alterado, embora a linguagem não impeça a modificação.

Propriedades das Variáveis

  • **ID (Identificador):** Cada objeto em Python tem um ID único que representa seu endereço na memória. Use id() para verificar.
  • **Tipo:** Cada variável tem um tipo de dado (inteiro, string, etc.). Use type() para verificar.
meu_nome = "João"
outro_nome = "Maria"

print(f"ID de meu_nome: {id(meu_nome)}")
print(f"Tipo de meu_nome: {type(meu_nome)}")

PI_VALOR = 3.14
print(f"ID de PI_VALOR: {id(PI_VALOR)}")
print(f"Tipo de PI_VALOR: {type(PI_VALOR)}")

Tipos de Dados em Python

Tipo String (str)

Strings são sequências de caracteres usadas para representar textos, como nomes, endereços, descrições. São definidas usando aspas simples ('...'), duplas ("...") ou triplas ("""...""" ou '''...''').

nome_aluno = 'José'
sobrenome_aluno = "Silva"
citacao_multi_linha = '''
"A vida é o que acontece
enquanto você está ocupado
fazendo outros planos."
- John Lennon
'''
print(nome_aluno, sobrenome_aluno)
print(citacao_multi_linha)

# Strings podem conter aspas internas
print('Meu nome é "José"')
print("Meu nome é 'Silva'")

Operações com Strings:

  • **Concatenação (+):** Combina duas ou mais strings.
  • **Repetição (\*):** Repete uma string um número especificado de vezes.
saudacao = 'Olá' + ' Mundo!'
print(saudacao)

repetido = 'Python ' * 3
print(repetido)

Indexação e Fatiamento (Slicing):

Strings são sequências, o que significa que seus caracteres podem ser acessados por índice (posição). O primeiro caractere tem índice 0. Índices negativos contam a partir do final (-1 é o último caractere).

palavra = 'Exemplo'
print(f"Primeiro caractere: {palavra[0]}")
print(f"Terceiro caractere: {palavra[2]}")
print(f"Último caractere: {palavra[-1]}")

# Fatiamento: [inicio:fim:passo] (o fim é exclusivo)
print(f"Fatia de 0 a 3: {palavra[0:4]}")
print(f"Fatia do início até o 4º: {palavra[:5]}")
print(f"Invertendo a string: {palavra[::-1]}")

Strings são Imutáveis: Uma vez criada, o conteúdo de uma string não pode ser alterado. Qualquer operação que "mude" uma string, na verdade, cria uma nova string.

texto_original = "abc"
texto_modificado = texto_original.upper() # Cria uma nova string
print(f"Original: {texto_original}, ID: {id(texto_original)}")
print(f"Modificado: {texto_modificado}, ID: {id(texto_modificado)}")

Interação com o Usuário

Programas frequentemente precisam receber dados do usuário e apresentar resultados. Python oferece funções para isso.

  • input(): Lê uma linha de texto do usuário. Sempre retorna uma string.
  • print(): Exibe informações na saída padrão (geralmente o console).
nome_digitado = input("Por favor, digite seu nome: ")
print(f"Olá, {nome_digitado}!")

# input() sempre retorna string; para números, converta:
idade_str = input("Digite sua idade: ")
idade_num = int(idade_str)
print(f"Você tem {idade_num} anos.")
print(f"Tipo da idade: {type(idade_num)}")

Formatação de Saída de Strings

Para criar mensagens dinâmicas com valores de variáveis, usamos formatação de strings. F-strings (strings literais formatadas) são a forma mais moderna e recomendada em Python.

nome_produto = "Notebook"
preco_produto = 3500.50

# Usando f-strings (Python 3.6+)
print(f"O produto {nome_produto} custa R$ {preco_produto:.2f}.")

# Exemplo com entrada do usuário
usuario_nome = input("Qual é o seu nome? ")
usuario_idade = int(input("Qual é a sua idade? "))
print(f"Meu nome é {usuario_nome} e tenho {usuario_idade} anos.")

Tipos Numéricos

Python possui tipos numéricos para lidar com diferentes representações de números.

  • **Inteiro (int):** Números inteiros (positivos, negativos ou zero). Ex: idade, quantidade.
  • **Ponto Flutuante (float):** Números reais, com casas decimais. Ex: altura, salário.
quantidade = 100
print(f"Tipo de quantidade: {type(quantidade)}")

altura = 1.75
print(f"Tipo de altura: {type(altura)}")

Operadores Numéricos

Python suporta operadores aritméticos e de comparação para números.

  • **Aritméticos:**
    • + (Adição)
    • - (Subtração)
    • \* (Multiplicação)
    • / (Divisão real)
    • // (Divisão inteira - "chão" da divisão)
    • % (Módulo - resto da divisão)
    • \*\* (Exponenciação)
  • **Comparação (retornam True ou False):**
    • == (Igual a)
    • != (Diferente de)
    • > (Maior que)
    • < (Menor que)
    • >= (Maior ou igual a)
    • <= (Menor ou igual a)
a = 10
b = 3

print(f"Adição: {a + b}")
print(f"Divisão Inteira: {a // b}")
print(f"Resto da Divisão: {a % b}")
print(f"Exponenciação: {a ** 2}")

print(f"a é igual a b? {a == b}")
print(f"a é maior que b? {a > b}")
print(f"a é menor ou igual a b? {a <= b}")

Tipo Booleano (bool)

Representa valores lógicos: True (verdadeiro) ou False (falso). Essencial para controle de fluxo e tomada de decisões.

esta_ativo = True
tem_permissao = False
print(f"Tipo de esta_ativo: {type(esta_ativo)}")

Tipo Complexo (complex)

Representa números complexos, com parte real e imaginária. Menos comum na programação diária, mas presente em áreas como engenharia e matemática.

z = 3 + 4j
print(f"Número complexo: {z}, Tipo: {type(z)}")

Tipo Lista (list)

Listas são coleções ordenadas e mutáveis de itens. Podem armazenar valores de diferentes tipos de dados. Definidas por colchetes \[\] e elementos separados por vírgulas.

minha_lista = ['uva', 123, 3.14, ['nested', 'list']]
print(f"Lista completa: {minha_lista}")

# Acessando elementos por índice
print(f"Primeiro elemento: {minha_lista[0]}")
print(f"Último elemento: {minha_lista[-1]}")
print(f"Elemento da lista aninhada: {minha_lista[3][0]}")

# Listas são mutáveis: podemos alterar seus elementos
minha_lista[0] = 'maçã'
print(f"Lista após modificação: {minha_lista}")

Tipo Dicionário (dict)

Dicionários são coleções não ordenadas e mutáveis de pares chave-valor. Cada chave deve ser única e imutável (geralmente strings ou números), enquanto os valores podem ser de qualquer tipo. Definidos por chaves {}.

informacoes_pessoa = {
    'nome': 'Carla',
    'idade': 28,
    'cidade': 'São Paulo'
}
print(f"Dicionário completo: {informacoes_pessoa}")

# Acessando valores por chave
print(f"Nome: {informacoes_pessoa['nome']}")
print(f"Idade: {informacoes_pessoa['idade']}")

# Dicionários são mutáveis
informacoes_pessoa['cidade'] = 'Rio de Janeiro'
informacoes_pessoa['profissão'] = 'Engenheira'
print(f"Dicionário após modificação: {informacoes_pessoa}")

Tipo Tupla (tuple)

Tuplas são coleções ordenadas e imutáveis de itens. Semelhantes às listas, mas não podem ser alteradas após a criação. Definidas por parênteses ().

ponto_3d = (10, 20, 30)
print(f"Tupla completa: {ponto_3d}")

# Acessando elementos (indexação e fatiamento funcionam como em listas)
print(f"Primeira coordenada: {ponto_3d[0]}")

# Tentar modificar um elemento resultará em erro
# ponto_3d[0] = 5  # Isso causaria um TypeError

Mesmo que a tupla seja imutável, se ela contiver um objeto mutável (como uma lista), esse objeto interno pode ser modificado.

tupla_com_lista = (1, 2, ['a', 'b'])
tupla_com_lista[2][0] = 'c' # Modifica a lista dentro da tupla
print(f"Tupla com lista modificada internamente: {tupla_com_lista}")

Tipo Conjunto (set)

Conjuntos são coleções não ordenadas de itens únicos. Não permitem elementos duplicados. Úteis para operações matemáticas de conjuntos (união, interseção, diferença). Definidos por chaves {}.

cidades = {'São Paulo', 'Rio de Janeiro', 'Belo Horizonte', 'São Paulo'}
print(f"Conjunto de cidades: {cidades}") # 'São Paulo' aparece apenas uma vez

# Adicionando elementos
cidades.add('Curitiba')
print(f"Conjunto após adicionar: {cidades}")

# Operações de conjunto
set_a = {1, 2, 3, 4}
set_b = {3, 4, 5, 6}
print(f"Interseção (elementos em comum): {set_a & set_b}")
print(f"União (todos os elementos únicos): {set_a | set_b}")
print(f"Diferença (elementos em A mas não em B): {set_a - set_b}")

Conjuntos são frequentemente usados para remover duplicatas de listas:

lista_duplicada = [1, 2, 2, 3, 4, 4, 5]
lista_sem_duplicatas = list(set(lista_duplicada))
print(f"Lista sem duplicatas: {lista_sem_duplicatas}")

Operações e Métodos Comuns em Tipos de Dados

Strings (str)

  • len(string): Retorna o comprimento da string.
  • substring in string: Verifica se uma substring está presente.
  • string.upper(), string.lower(): Converte para maiúsculas/minúsculas.
  • string.capitalize(): Primeira letra da string em maiúscula.
  • string.title(): Primeira letra de cada palavra em maiúscula.
  • string.split(separador): Divide a string em uma lista de substrings.
  • string.strip(): Remove espaços em branco (ou outros caracteres) do início e fim.
  • separador.join(lista\_de\_strings): Junta elementos de uma lista em uma única string.
mensagem = "  Olá mundo Python  "
print(f"Comprimento: {len(mensagem)}")
print(f"'mundo' está na mensagem? {'mundo' in mensagem}")
print(f"Maiúsculas: {mensagem.upper()}")
print(f"Split por espaço: {mensagem.split()}")
print(f"Sem espaços nas bordas: '{mensagem.strip()}'")
palavras = ['Este', 'é', 'um', 'teste']
print(f"Juntar com espaço: {' '.join(palavras)}")

Listas (list)

  • len(lista): Número de elementos na lista.
  • elemento in lista: Verifica se um elemento está presente.
  • lista.append(elemento): Adiciona um elemento ao final.
  • lista.extend(outra\_lista): Adiciona todos os elementos de outra lista ao final.
  • lista.insert(indice, elemento): Insere um elemento em uma posição específica.
  • lista.remove(elemento): Remove a primeira ocorrência do elemento.
  • lista.pop(indice): Remove e retorna o elemento em um índice. Se nenhum índice for dado, remove o último.
  • lista.clear(): Remove todos os elementos.
  • lista.reverse(): Inverte a ordem dos elementos.
  • lista.sort(): Ordena a lista (in-place). Use sorted(lista) para uma nova lista ordenada.
numeros = [5, 2, 8, 1]
print(f"Comprimento: {len(numeros)}")
numeros.append(10)
print(f"Após append: {numeros}")
numeros.sort()
print(f"Após sort: {numeros}")

Dicionários (dict)

  • len(dicionario): Número de pares chave-valor.
  • chave in dicionario: Verifica se uma chave existe.
  • dicionario.keys(): Retorna uma visualização das chaves.
  • dicionario.values(): Retorna uma visualização dos valores.
  • dicionario.items(): Retorna uma visualização dos pares chave-valor.
  • dicionario.get(chave, valor\_padrao): Retorna o valor da chave ou um valor padrão se a chave não existir (evita KeyError).
  • dicionario.update(outro\_dicionario): Atualiza o dicionário com pares de outro dicionário.
  • dicionario.setdefault(chave, valor\_padrao): Se a chave existir, retorna seu valor. Caso contrário, insere a chave com o valor padrão e a retorna.
config = {'tema': 'dark', 'idioma': 'pt-BR'}
print(f"Chaves: {list(config.keys())}")
print(f"Valor do tema: {config.get('tema')}")
config.update({'idioma': 'en-US', 'notificacoes': True})
print(f"Após update: {config}")

Tuplas (tuple)

Como são imutáveis, tuplas têm menos métodos. Principalmente usam indexação e fatiamento.

  • len(tupla): Número de elementos.
  • elemento in tupla: Verifica se um elemento está presente.
  • tupla.count(elemento): Conta ocorrências de um elemento.
  • tupla.index(elemento): Retorna o índice da primeira ocorrência do elemento.
cores = ('vermelho', 'verde', 'azul', 'verde')
print(f"Contagem de 'verde': {cores.count('verde')}")

Conjuntos (set)

  • len(conjunto): Número de elementos únicos.
  • elemento in conjunto: Verifica a presença de um elemento.
  • conjunto.add(elemento): Adiciona um elemento.
  • conjunto.remove(elemento): Remove um elemento (erro se não existir).
  • conjunto.discard(elemento): Remove um elemento (não dá erro se não existir).
  • conjunto.union(outro\_conjunto) ou |: Combina elementos de ambos.
  • conjunto.intersection(outro\_conjunto) ou &amp;: Elementos em comum.
  • conjunto.difference(outro\_conjunto) ou -: Elementos no primeiro, mas não no segundo.
linguagens_backend = {'Python', 'Java', 'Node.js'}
linguagens_frontend = {'JavaScript', 'HTML', 'CSS', 'Python'}
print(f"Linguagens em comum: {linguagens_backend.intersection(linguagens_frontend)}")

Fluxo de Controle

O fluxo de controle define a ordem em que as instruções de um programa são executadas. Estruturas condicionais (if/elif/else) e de repetição (while, for) são fundamentais.

Instrução if (Condicionais)

Permite que o programa tome decisões com base em condições booleanas.

  • Sintaxe básica:
# Exemplo 1: Simples
condicao = True
if condicao:
    print('A condição é verdadeira.')

# Exemplo 2: if-else
idade_candidato = 17
if idade_candidato >= 18:
    print('Apto para votar.')
else:
    print('Inapto para votar.')

# Exemplo 3: if-elif-else (múltiplas condições)
# Apenas a primeira condição verdadeira será executada.
nota_aluno = 85
if nota_aluno >= 90:
    print('Conceito: A')
elif nota_aluno >= 80:
    print('Conceito: B')
elif nota_aluno >= 70:
    print('Conceito: C')
else:
    print('Conceito: D')

# Aninhamento de if
genero = 'feminino'
altura_cm = 165

if genero == 'feminino':
    if altura_cm >= 160:
        print("Mulher alta.")
    else:
        print("Mulher de altura média/baixa.")
else:
    print("Homem ou outro gênero.")

Operadores Lógicos (and, or, not)

Usados para combinar ou modificar condições booleanas.

  • and (E): Retorna True se ambas as condições forem True.
  • or (Ou): Retorna True se pelo menos uma das condições for True.
  • not (Não): Inverte o valor booleano da condição.
tem_carteira = True
tem_idade_minima = False
esta_sobrio = True

# Usando 'and'
if tem_carteira and tem_idade_minima and esta_sobrio:
    print("Pode dirigir.")
else:
    print("Não pode dirigir.")

# Usando 'or'
clima_bom = True
tem_folga = False
if clima_bom or tem_folga:
    print("Vamos sair!")
else:
    print("Ficaremos em casa.")

# Usando 'not'
status_erro = False
if not status_erro:
    print("Operação bem-sucedida.")
else:
    print("Ocorreu um erro.")

# Precedência: not > and > or. Use parênteses para clareza.
condicao_complexa = not True and False or True
print(f"Resultado complexo: {condicao_complexa}") # Output: True (not True é False, False and False é False, False or True é True)

Instrução while (Laços de Repetição)

Executa um bloco de código repetidamente enquanto uma condição for verdadeira.

# Exemplo 1: Contador
contador = 0
while contador < 5:
    print(f"Contador: {contador}")
    contador += 1 # Essencial para evitar loop infinito

# Exemplo 2: Loop de login
usuario_correto = "admin"
senha_correta = "12345"
tentativas_max = 3
tentativas = 0

while tentativas < tentativas_max:
    nome_digitado = input("Usuário: ")
    senha_digitada = input("Senha: ")

    if nome_digitado == usuario_correto and senha_digitada == senha_correta:
        print("Login bem-sucedido!")
        break # Sai do loop quando o login é feito
    else:
        tentativas += 1
        print(f"Credenciais inválidas. Tentativas restantes: {tentativas_max - tentativas}")
else: # O bloco else do while é executado se o loop terminar sem um 'break'
    print("Número máximo de tentativas atingido. Conta bloqueada.")


# Usando 'continue': Pula para a próxima iteração do loop
numero = 0
while numero < 5:
    numero += 1
    if numero == 3:
        continue # Pula o print quando numero for 3
    print(f"Processando número: {numero}")

Instrução for (Laços de Repetição)

Itera sobre os itens de qualquer sequência (lista, tupla, string) ou outros objetos iteráveis.

# Iterando sobre uma lista
frutas = ['maçã', 'banana', 'cereja']
for fruta in frutas:
    print(f"Temos: {fruta}")

# Iterando sobre uma string
for letra in "Python":
    print(letra)

# Iterando sobre um dicionário (padrão: chaves)
config = {'tema': 'dark', 'idioma': 'pt-BR'}
for chave in config:
    print(f"Chave: {chave}, Valor: {config[chave]}")

# Iterando sobre chaves e valores
for chave, valor in config.items():
    print(f"{chave}: {valor}")

# Usando range(): Gera uma sequência de números
# range(fim) -> de 0 até fim-1
# range(inicio, fim) -> de inicio até fim-1
# range(inicio, fim, passo) -> de inicio até fim-1, pulando 'passo'
for i in range(5): # De 0 a 4
    print(f"Número (range): {i}")

# Tabela de multiplicação (laço aninhado)
print("\nTabela de Multiplicação:")
for i in range(1, 10):
    for j in range(1, i + 1):
        print(f"{j} * {i} = {i * j}", end="\t")
    print() # Nova linha após cada linha da tabela

# Removendo duplicatas de uma lista de dicionários mantendo a ordem
itens_com_duplicatas = [
    {'id': 1, 'nome': 'Item A'},
    {'id': 2, 'nome': 'Item B'},
    {'id': 1, 'nome': 'Item A'}, # Duplicata
    {'id': 3, 'nome': 'Item C'},
    {'id': 2, 'nome': 'Item B'}  # Duplicata
]
itens_unicos = []
ids_vistos = set() # Para verificar rapidamente se um ID já foi adicionado

for item in itens_com_duplicatas:
    if item['id'] not in ids_vistos:
        itens_unicos.append(item)
        ids_vistos.add(item['id'])

print("\nItens únicos (mantendo ordem):")
for item in itens_unicos:
    print(item)

Depuração de Código (Breakpoints)

Depuração é o processo de encontrar e corrigir erros (bugs) no código. Ferramentas de depuração permitem pausar a execução do programa em pontos específicos (breakpoints) e inspecionar o estado das variáveis, o que é crucial para entender o comportamento do código.

Em IDEs como PyCharm, você pode definir um breakpoint clicando na margem ao lado de uma linha de código. Ao iniciar o programa em modo de depuração, a execução será pausada no breakpoint, permitindo que você navegue pelo código passo a passo (step over, step into, step out).

Funções

Funções são blocos de código reutilizáveis que realizam uma tarefa específica. Elas promovem a modularidade, organização e evitam a repetição de código.

Definição e Chamada de Funções

Uma função é definida com a palavra-chave def, seguida pelo nome da função, parênteses () (para parâmetros) e dois pontos :. O corpo da função é indentado.

# Definição de uma função sem parâmetros e sem retorno
def saudar():
    print("Olá! Bem-vindo(a).")

# Chamada da função
saudar()
saudar() # Pode ser chamada múltiplas vezes

Parâmetros e Argumentos

Parâmetros são variáveis listadas entre parênteses na definição da função. Argumentos são os valores passados para esses parâmetros quando a função é chamada.

  • **Funções sem Parâmetros:** Apenas executam ações fixas.
  • **Funções com Parâmetros:** Permitem que a função seja mais flexível, agindo sobre dados externos.
# Função com parâmetros
def cumprimentar_pessoa(nome, hora_dia): # 'nome', 'hora_dia' são parâmetros (variáveis)
    print(f"Bom {hora_dia}, {nome}!")

# Chamada da função com argumentos
# 1. Argumentos Posicionais: Passados na ordem em que os parâmetros são definidos.
cumprimentar_pessoa('Ana', 'dia')
cumprimentar_pessoa('Pedro', 'tarde')

# 2. Argumentos de Palavra-Chave (Keyword Arguments): Passados com 'nome_parametro=valor'.
#    Permite passar em qualquer ordem e melhora a clareza.
cumprimentar_pessoa(hora_dia='noite', nome='Maria')

# Misturando Posicionais e Palavra-Chave (Posicionais primeiro!)
cumprimentar_pessoa('Lucas', hora_dia='manhã')
# cumprimentar_pessoa(hora_dia='manhã', 'Lucas') # Erro: Posicionais após palavra-chave

Parâmetros Padrão (Default Parameters)

Permitem que um parâmetro tenha um valor predefinido. Se nenhum argumento for fornecido para esse parâmetro na chamada, o valor padrão é usado.

def registrar_usuario(nome, email, status='ativo'):
    print(f"Usuário: {nome}, Email: {email}, Status: {status}")

registrar_usuario('Joana', 'joana@email.com') # Usa status padrão
registrar_usuario('Roberto', 'roberto@email.com', status='inativo') # Sobrescreve status

Argumentos de Comprimento Variável (\*args e \*\*kwargs)

Permitem que funções aceitem um número arbitrário de argumentos.

  • \*args: Coleta argumentos posicionais adicionais em uma tupla.
  • \*\*kwargs: Coleta argumentos de palavra-chave adicionais em um dicionário.
def imprimir_info(primeiro_item, *outros_itens, **detalhes):
    print(f"Primeiro item: {primeiro_item}")
    print(f"Outros itens (tupla): {outros_itens}")
    print(f"Detalhes adicionais (dicionário): {detalhes}")

imprimir_info('Maçã', 'Banana', 'Laranja', cor='vermelha', peso=0.2)

# Desempacotamento de Argumentos (unpacking)
# Você pode usar * e ** ao chamar funções para desempacotar listas/tuplas e dicionários.
frutas_lista = ['uva', 'pera']
extras_dict = {'origem': 'Brasil', 'vitamina_c': True}

imprimir_info('Abacaxi', *frutas_lista, **extras_dict)

Parâmetros Apenas por Palavra-Chave (Named-Only Arguments)

Parâmetros colocados após \* ou \*args devem ser passados como argumentos de palavra-chave, aumentando a clareza da chamada da função.

def configurar_notificacao(tipo, *, destinatario, prioridade='normal'):
    print(f"Tipo: {tipo}, Destinatário: {destinatario}, Prioridade: {prioridade}")

configurar_notificacao('email', destinatario='suporte@empresa.com')
# configurar_notificacao('sms', 'gerente@empresa.com', 'alta') # Erro: 'destinatario' deve ser keyword
configurar_notificacao('alerta', destinatario='admin@empresa.com', prioridade='alta')

Retorno de Valores de Funções (return)

A palavra-chave return é usada para enviar um valor de volta do escopo da função para o local onde ela foi chamada.

  • Um return encerra a execução da função.
  • Funções podem retornar qualquer tipo de dado.
  • Se nenhum return for especificado, a função retorna None implicitamente.
  • Para retornar múltiplos valores, eles são empacotados em uma tupla.
def somar(a, b):
    resultado = a + b
    return resultado

def calcular_operacoes(x, y):
    soma = x + y
    subtracao = x - y
    return soma, subtracao # Retorna uma tupla (soma, subtracao)

soma_total = somar(5, 3)
print(f"Soma: {soma_total}")

res_soma, res_sub = calcular_operacoes(10, 4) # Desempacotamento da tupla
print(f"Soma: {res_soma}, Subtração: {res_sub}")

Escopo de Variáveis (Local e Global)

  • **Variáveis Locais:** Definidas dentro de uma função, só existem enquanto a função está sendo executada.
  • **Variáveis Globais:** Definidas fora de qualquer função, acessíveis por todo o programa.
global_var = 100 # Variável global

def minha_funcao():
    local_var = 50 # Variável local
    print(f"Dentro da função: {local_var} (local), {global_var} (global)")

minha_funcao()
# print(local_var) # Erro: local_var não está definida fora da função
print(f"Fora da função: {global_var} (global)")

# Usando 'global' para modificar uma variável global dentro de uma função
contador = 0
def incrementar_global():
    global contador # Declara que 'contador' se refere à variável global
    contador += 1
    print(f"Contador global incrementado para: {contador}")

incrementar_global()
incrementar_global()

Variáveis Nonlocal (Funções Aninhadas)

A palavra-chave nonlocal é usada em funções aninhadas para se referir a uma variável no escopo de uma função externa (não global), permitindo sua modificação.

def funcao_externa():
    mensagem = "Olá do escopo externo"

    def funcao_interna():
        nonlocal mensagem # Declara que 'mensagem' é a do escopo externo
        mensagem = "Olá do escopo interno (modificado)"
        print(f"Dentro da interna: {mensagem}")

    funcao_interna()
    print(f"Dentro da externa após interna: {mensagem}")

funcao_externa()

Funções Built-in (Integradas)

Python oferece diversas funções já prontas para uso, otimizando tarefas comuns.

  • abs(numero): Valor absoluto.
  • all(iteravel): True se todos os elementos do iterável são verdadeiros (ou se o iterável é vazio).
  • any(iteravel): True se algum elemento do iterável é verdadeiro (ou se o iterável é vazio e algum elemento é verdadeiro).
  • max(iteravel), min(iteravel), sum(iteravel): Máximo, mínimo e soma de elementos numéricos.
  • ord(caractere): Retorna o valor Unicode de um caractere.
  • chr(codigo): Retorna o caractere correspondente a um código Unicode.
  • zip(\*iteraveis): Agrupa elementos de múltiplos iteráveis em tuplas.
  • exec(codigo\_string): Executa uma string como código Python.
print(f"Absoluto de -7: {abs(-7)}")
print(f"Todos True? {all([True, 1, 'abc'])}")
print(f"Algum True? {any([False, 0, 'abc'])}")

valores = [10, 20, 5, 30]
print(f"Máximo: {max(valores)}, Mínimo: {min(valores)}, Soma: {sum(valores)}")

print(f"Unicode de 'A': {ord('A')}, Caractere de 65: {chr(65)}")

nomes = ['Alice', 'Bob', 'Charlie']
idades = [25, 30, 22]
combinado = list(zip(nomes, idades))
print(f"Zip: {combinado}")

# Executa código como string
exec("for i in range(3): print(f'Executando: {i}')")

Funções Recursivas

Uma função recursiva é aquela que se chama a si mesma, direta ou indiretamente. A recursão é uma técnica poderosa para resolver problemas que podem ser divididos em subproblemas menores da mesma natureza.

**Cmoponentes essenciais:**

  • **Caso base:** Uma condição que permite que a função retorne sem fazer outra chamada recursiva, evitando um loop infinito.
  • **Passo recursivo:** A função chama a si mesma, mas com um problema de tamanho reduzido, aproximando-se do caso base.

Exemplo: Cálculo do Fatorial

O fatorial de um número n (n!) é o produto de todos os inteiros positivos menores ou iguais a n. Fatorial de 0 é 1.

def fatorial(n):
    if n == 0: # Caso base
        return 1
    else: # Passo recursivo
        return n * fatorial(n-1)

print(f"Fatorial de 5: {fatorial(5)}") # Output: 120 (5*4*3*2*1)

# Exemplo de busca em estrutura aninhada (similar ao original)
estrutura_aninhada = [1, [2, [3, {'id': 100}, [4, [5, 6, [7,]]]]]]

def buscar_elementos(lista_ou_elemento):
    if isinstance(lista_ou_elemento, list):
        for item in lista_ou_elemento:
            buscar_elementos(item) # Chamada recursiva para elementos da lista
    elif isinstance(lista_ou_elemento, dict):
        print(f"Encontrou dicionário: {lista_ou_elemento}")
    else:
        print(f"Elemento simples: {lista_ou_elemento}")

buscar_elementos(estrutura_aninhada)

Funções de Fechamento (Closures)

Um fechamento (closure) ocorre quando uma função interna lembra e acessa variáveis do escopo de sua função externa, mesmo depois que a função externa já terminou sua execução.

def criar_saudacao(saudacao_prefixo):
    """
    Função externa que define uma saudação fixa.
    Retorna uma função interna.
    """
    def saudar_nome(nome): # Função interna
        return f"{saudacao_prefixo}, {nome}!"
    return saudar_nome # Retorna a função interna, não seu resultado

# 'saudar_ola' é um fechamento. Ela "lembra" saudacao_prefixo='Olá'.
saudar_ola = criar_saudacao("Olá")
saudar_bom_dia = criar_saudacao("Bom dia")

print(saudar_ola("João"))
print(saudar_bom_dia("Maria"))

No exemplo acima, saudar\_ola e saudar\_bom\_dia são funções internas que "fecham" (capturam) o valor de saudacao\_prefixo do escopo da criar\_saudacao no momento em que foram criadas.

Decoradores

Decoradores são funções que modificam ou estendem o comportamento de outras funções ou métodos, sem alterar seu código-fonte diretamente. Eles são uma forma elegante de aplicar o princípio "aberto/fechado" de design de software (aberto para extensão, fechado para modificação).

import time

def medir_tempo_execucao(func):
    """
    Um decorador que mede o tempo de execução de uma função.
    """
    def wrapper(*args, **kwargs):
        inicio = time.perf_counter()
        print(f"Início da execução de '{func.__name__}' às {time.ctime(inicio)}.")
        resultado = func(*args, **kwargs) # Executa a função original
        fim = time.perf_counter()
        print(f"Fim da execução de '{func.__name__}' às {time.ctime(fim)}.")
        print(f"Tempo total: {fim - inicio:.4f} segundos.")
        return resultado
    return wrapper

@medir_tempo_execucao # Isso é o mesmo que: calcular_soma = medir_tempo_execucao(calcular_soma)
def calcular_soma(limite):
    """Calcula a soma de números até um limite."""
    soma = 0
    for i in range(1, limite + 1):
        soma += i
    print(f"Soma calculada: {soma}")
    return soma

@medir_tempo_execucao
def simular_operacao_lenta(duracao):
    """Simula uma operação que leva um tempo."""
    print(f"Simulando operação lenta por {duracao} segundos...")
    time.sleep(duracao)
    return "Operação concluída"

# Chamando as funções decoradas
calcular_soma(1000000)
simular_operacao_lenta(2)

O @medir\_tempo\_execucao antes da definição de calcular\_soma e simular\_operacao\_lenta é a sintaxe de decorador. Ele aplica a função medir\_tempo\_execucao à função seguinte.

Tratamento de Exceções

Exceções são erros que ocorrem durante a execução de um programa. O tratamento de exceções permite que o programa se recupere de erros de forma controlada, evitando que ele trave (crash).

Um bloco try-except-else-finally é usado para lidar com exceções:

  • try: Onde você coloca o código que pode gerar uma exceção.
  • except: Captura e lida com a exceção, caso ocorra. Pode-se especificar o tipo de exceção.
  • else: (Opcional) Executado se NENHUMA exceção ocorrer no bloco try.
  • finally: (Opcional) Sempre executado, independentemente de ter ocorrido uma exceção ou não.
def dividir_numeros(num1, num2):
    try:
        resultado = num1 / num2
    except ZeroDivisionError:
        print("Erro: Divisão por zero não é permitida.")
        return None
    except TypeError:
        print("Erro: Certifique-se de que os inputs são números.")
        return None
    except Exception as e: # Captura qualquer outra exceção
        print(f"Ocorreu um erro inesperado: {e}")
        return None
    else:
        print("Divisão realizada com sucesso!")
        return resultado
    finally:
        print("Fim da tentativa de divisão.")

print(dividir_numeros(10, 2))
print("-" * 20)
print(dividir_numeros(10, 0))
print("-" * 20)
print(dividir_numeros(10, 'a'))

Python possui muitas exceções built-in, como NameError, KeyError, IndexError, FileNotFoundError, etc. Você também pode levantar suas próprias exceções com raise ou usar assert para verificar condições.

Operações com Arquivos

Arquivos permitem que os programas armazenem e recuperem dados de forma persistente no disco rígido, em vez de apenas na memória volátil.

Abertura e Fechamento de Arquivos

A função open() é usada para abrir arquivos. Ela retorna um objeto de arquivo. É crucial fechar o arquivo com close() após o uso para liberar recursos do sistema operacional.

A maneira mais segura de lidar com arquivos é usar a instrução with, que garante que o arquivo seja fechado automaticamente, mesmo se ocorrerem erros.

# Exemplo de leitura de arquivo
try:
    with open('meu_arquivo.txt', 'r', encoding='utf-8') as f:
        conteudo = f.read()
        print("Conteúdo do arquivo:")
        print(conteudo)
except FileNotFoundError:
    print("O arquivo 'meu_arquivo.txt' não foi encontrado.")

# Exemplo de escrita em arquivo
with open('saida.txt', 'w', encoding='utf-8') as f:
    f.write("Esta é a primeira linha.\n")
    f.write("Esta é a segunda linha.\n")
    print("Dados gravados em 'saida.txt'.")

Modos de Abertura de Arquivos

O segundo argumento de open() é o modo, que especifica a intenção (leitura, escrita, etc.) e o tipo (texto ou binário).

  • 'r' (read): Leitura. O arquivo deve existir.
  • 'w' (write): Escrita. Cria o arquivo se não existir; se existir, TRUNCA (apaga) o conteúdo.
  • 'a' (append): Anexação. Cria o arquivo se não existir; se existir, adiciona o conteúdo ao final.
  • 't' (text): Modo texto (padrão). Requer encoding.
  • 'b' (binary): Modo binário. Não usa encoding.

Combinando modos (ex: 'rt', 'wb', 'a+'):

  • 'rt' (read text): Ler arquivo de texto (padrão).
  • 'wt' (write text): Escrever em arquivo de texto.
  • 'at' (append text): Anexar em arquivo de texto.
  • 'rb' (read binary): Ler arquivo binário (imagens, vídeos).
  • 'wb' (write binary): Escrever em arquivo binário.
  • 'r+': Leitura e escrita. O ponteiro inicia no começo.
  • 'w+': Escrita e leitura. Trunca o arquivo no início.
  • 'a+': Anexação e leitura. O ponteiro de escrita inicia no final.
# Leitura de linha por linha
with open('saida.txt', 'r', encoding='utf-8') as f:
    print("Lendo linha por linha:")
    for linha in f:
        print(linha.strip()) # .strip() para remover quebras de linha

# Escrevendo múltiplas linhas de uma lista
lista_de_frutas = ["Maçã\n", "Banana\n", "Cereja\n"]
with open('frutas.txt', 'w', encoding='utf-8') as f:
    f.writelines(lista_de_frutas)
    print("Lista de frutas gravada.")

# Copiando um arquivo binário (ex: imagem)
# with open('imagem_original.jpg', 'rb') as src, open('imagem_copia.jpg', 'wb') as dest:
#     dest.write(src.read())
# print("Imagem copiada.")

Movimentação do Ponteiro em Arquivos (seek, tell)

O ponteiro do arquivo indica a posição atual para leitura/escrita. f.tell(): Retorna a posição atual do ponteiro (em bytes no modo binário; em caracteres no modo texto, mas pode ser complexo com codificações variáveis). f.seek(offset, whence): Move o ponteiro. offset: Número de bytes/caracteres para mover. whence: Ponto de referência (0: início do arquivo - padrão, 1: posição atual, 2: fim do arquivo). whence=1 e whence=2 só funcionam no modo binário.

with open('saida.txt', 'r+', encoding='utf-8') as f:
    print(f"Conteúdo inicial: {f.read()}")
    posicao_atual = f.tell()
    print(f"Ponteiro atual: {posicao_atual}") # Deverá ser o tamanho do arquivo
    f.seek(0) # Volta para o início do arquivo
    print(f"Conteúdo do início: {f.read(7)}") # Lê 7 caracteres
    f.write("NOVO") # Sobrescreve
    f.seek(0) # Volta para o início para ler
    print(f"Conteúdo após sobrescrever: {f.read()}")

with open('teste_binario.bin', 'wb+') as f_bin:
    f_bin.write(b'abcdefg')
    f_bin.seek(2, 0) # Move 2 bytes do início
    print(f"Lendo do byte 2: {f_bin.read(3)}") # Output: b'cde'
    f_bin.seek(-1, 2) # Move 1 byte do fim (para trás)
    print(f"Lendo do fim (para trás): {f_bin.read(1)}") # Output: b'g'

Modificando o Conteúdo de um Arquivo

Arquivos não podem ser modificados diretamente no meio. As estratégias são:

  1. **Ler tudo para a memória, modificar, e sobrescerver o arquivo.**
    • **Vantagem:** Simples de implementar.
    • **Desvantagem:** Ineficiente para arquivos grandes, pois carrega o arquivo inteiro na RAM.
  2. **Usar um arquivo temporário.**
    • **Vantagem:** Eficiente para arquivos grandes, pois processa linha por linha.
    • **Desvantagem:** Requer espaço em disco para o arquivo temporário.
import os

# Conteúdo original para os exemplos
with open('dados_originais.txt', 'w', encoding='utf-8') as f:
    f.write("Linha 1: Texto a ser modificado.\n")
    f.write("Linha 2: Outro texto.\n")
    f.write("Linha 3: Substituir isso.\n")

# Método 1: Carregar tudo para memória
print("--- Método 1: Memória ---")
with open('dados_originais.txt', 'r', encoding='utf-8') as f:
    conteudo = f.read()

novo_conteudo = conteudo.replace("modificado", "ALTERADO")

with open('dados_originais.txt', 'w', encoding='utf-8') as f:
    f.write(novo_conteudo)

print("Arquivo modificado (Método 1).")
with open('dados_originais.txt', 'r', encoding='utf-8') as f:
    print(f.read())


# Resetar arquivo para Método 2
with open('dados_originais.txt', 'w', encoding='utf-8') as f:
    f.write("Linha 1: Texto a ser modificado.\n")
    f.write("Linha 2: Outro texto.\n")
    f.write("Linha 3: Substituir isso.\n")

# Método 2: Usar arquivo temporário
print("--- Método 2: Arquivo Temporário ---")
nome_arquivo_original = 'dados_originais.txt'
nome_arquivo_temp = 'temp_dados.txt'

with open(nome_arquivo_original, 'r', encoding='utf-8') as infile, \
     open(nome_arquivo_temp, 'w', encoding='utf-8') as outfile:
    for linha in infile:
        nova_linha = linha.replace("isso", "AQUELE TEXTO")
        outfile.write(nova_linha)

os.remove(nome_arquivo_original) # Remove o arquivo original
os.rename(nome_arquivo_temp, nome_arquivo_original) # Renomeia o temporário

print("Arquivo modificado (Método 2).")
with open(nome_arquivo_original, 'r', encoding='utf-8') as f:
    print(f.read())

Web Scraping Básico com urllib

Web scraping é o processo de extrair dados de websites. O módulo urllib do Python oferece funcionalidades básicas para interagir com URLs.

urllib.request.urlopen()

Simula um navegador para enviar requisições a servidores e receber as respostas.

  • Retorna um objeto http.client.HTTPResponse.
  • Métodos úteis: read(), readline(), readlines(), getcode() (código de status HTTP), geturl(), getheaders().
import urllib.request

try:
    resposta = urllib.request.urlopen('http://www.example.com')
    html_conteudo = resposta.read().decode('utf-8')
    print(f"Status Code: {resposta.getcode()}")
    print(f"URL: {resposta.geturl()}")
    print("Cabeçalhos:")
    for chave, valor in resposta.getheaders():
        print(f"  {chave}: {valor}")
    # print(html_conteudo[:500]) # Imprime os primeiros 500 caracteres do HTML
except Exception as e:
    print(f"Erro ao acessar a URL: {e}")

urllib.request.urlretrieve()

Útil para baixar arquivos de uma URL e salvá-los localmente.

# import urllib.request
#
# # Baixar uma página HTML
# urllib.request.urlretrieve('http://www.example.com', 'pagina_example.html')
# print("Página HTML baixada.")
#
# # Baixar uma imagem (substitua pela URL real de uma imagem)
# # url_imagem = 'https://www.python.org/static/img/python-logo.png'
# # urllib.request.urlretrieve(url_imagem, 'python_logo.png')
# # print("Imagem baixada.")

Objetos Request e Cabeçalhos HTTP (User-Agent)

Servidores web podem bloquear requisições que não se parecem com as de um navegador real (anti-scraping). A customização de cabeçalhos HTTP, como o User-Agent, é uma técnica comum para contornar isso.

import urllib.request

url_destino = 'https://httpbin.org/get' # Um serviço para testar requisições HTTP

# Cabeçalhos HTTP, incluindo um User-Agent que simula um navegador
headers_personalizados = {
    'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/92.0.4515.159 Safari/537.36',
    'Accept-Language': 'pt-BR,pt;q=0.9,en-US;q=0.8,en;q=0.7',
    'Referer': 'https://www.google.com/'
}

# Cria um objeto Request com a URL e os cabeçalhos
req = urllib.request.Request(url=url_destino, headers=headers_personalizados)

try:
    resposta = urllib.request.urlopen(req)
    conteudo = resposta.read().decode('utf-8')
    print("Resposta do servidor com cabeçalhos personalizados:")
    print(conteudo)
except Exception as e:
    print(f"Erro ao fazer requisição: {e}")

Codificação e Decodificação de URLs (urllib.parse)

Caracteres especiais (como espaços ou caracteres não-ASCII) em URLs precisam ser codificados (percent-encoding) para serem transmitidos corretamente. urllib.parse ajuda com isso.

  • urllib.parse.quote(string): Codifica uma string para ser parte de uma URL.
  • urllib.parse.urlencode(dict): Codifica um dicionário de parâmetros para uma string de query (ex: key1=value1&amp;key2=value2).
import urllib.parse
import urllib.request

termo_pesquisa = "programação Python"
url_base_google = "https://www.google.com/search?q="

# Codifica o termo de pesquisa
termo_codificado = urllib.parse.quote(termo_pesquisa)
url_completa = url_base_google + termo_codificado
print(f"URL codificada: {url_completa}")

# Exemplo com múltiplos parâmetros (GET)
parametros = {
    'q': 'web scraping',
    'lang': 'pt-BR',
    'source': 'chrome'
}
query_string = urllib.parse.urlencode(parametros)
url_com_parametros = "https://www.google.com/search?" + query_string
print(f"URL com múltiplos parâmetros: {url_com_parametros}")

# Requisição POST: os dados são enviados no corpo da requisição
url_post_target = 'https://httpbin.org/post'
dados_post = {'nome_usuario': 'teste_py', 'senha': '123'}

# Codifica os dados para POST (formato url-encoded e depois para bytes)
dados_codificados = urllib.parse.urlencode(dados_post).encode('utf-8')

req_post = urllib.request.Request(url=url_post_target, data=dados_codificados, headers=headers_personalizados)
try:
    resposta_post = urllib.request.urlopen(req_post)
    conteudo_post = resposta_post.read().decode('utf-8')
    print("\nResposta POST:")
    print(conteudo_post)
except Exception as e:
    print(f"Erro ao fazer requisição POST: {e}")

Tratamento de Erros HTTP (URLError, HTTPError)

É importante lidar com erros que podem ocorrer ao fazer requisições web. urllib.error fornece classes de exceção para isso.

  • URLError: Erros gerais de URL (ex: falha de rede, nome de host desconhecido).
  • HTTPError: Erros específicos do protocolo HTTP (ex: 404 Not Found, 500 Internal Server Error). É uma subclasse de URLError.
import urllib.request
import urllib.error

url_inexistente = 'http://www.site-que-nao-existe12345.com/'
url_404 = 'https://httpbin.org/status/404'

def tentar_acesso(url):
    print(f"\nTentando acessar: {url}")
    try:
        req = urllib.request.Request(url, headers=headers_personalizados)
        resposta = urllib.request.urlopen(req)
        print(f"Sucesso! Status: {resposta.getcode()}")
    except urllib.error.HTTPError as e:
        print(f"Erro HTTP: {e.code} - {e.reason}")
    except urllib.error.URLError as e:
        print(f"Erro de URL (conexão/rede): {e.reason}")
    except Exception as e:
        print(f"Erro inesperado: {e}")

tentar_acesso(url_inexistente)
tentar_acesso(url_404)
tentar_acesso('https://www.google.com')

Gerenciamento de Cookies (Sessões)

Cookies são pequenos pedaços de dados que os servidores enviam aos navegadores para armazenar informações sobre o usuário. Eles são cruciais para manter sessões de login. Para lidar com cookies e manter sessões em web scraping, usamos HTTPCookieProcessor.

import urllib.request
import http.cookiejar

# Cria um "jarro de cookies" para armazenar os cookies
cookie_jar = http.cookiejar.CookieJar()

# Cria um handler que processará os cookies
cookie_handler = urllib.request.HTTPCookieProcessor(cookie_jar)

# Cria um "opener" que usará o handler de cookies
# Este opener será como um "navegador" que gerencia cookies
opener = urllib.request.build_opener(cookie_handler)

# Agora, o 'opener' pode ser usado para abrir URLs, e ele gerenciará os cookies
url_com_login = 'https://httpbin.org/cookies/set?user=guest&session=abc' # Define cookies
url_verificar_cookies = 'https://httpbin.org/cookies' # Verifica cookies

print("--- Definindo Cookies ---")
resposta_set = opener.open(url_com_login)
print(f"Cookies definidos para {url_com_login}")

print("\n--- Verificando Cookies ---")
resposta_get = opener.open(url_verificar_cookies)
conteudo_cookies = resposta_get.read().decode('utf-8')
print(conteudo_cookies)

# Você pode inspecionar o cookie_jar
print("\nConteúdo do Cookie Jar:")
for cookie in cookie_jar:
    print(f"  {cookie.name}: {cookie.value}")

Uso de Proxies

Proxies agem como intermediários entre seu computador e o servidor de destino. Eles são úteis para:

  • Contornar bloqueios de IP.
  • Acessar conteúdo restrito geograficamente.
  • Anonimato.

O urllib.request.ProxyHandler permite configurar um proxy.

import urllib.request
import random

# Lista de IPs de proxy (substitua por proxies reais e funcionais)
# Cuidado: Proxies públicos podem ser lentos, não confiáveis ou maliciosos.
# Para uso real, considere serviços de proxies pagos.
lista_de_proxies = [
    {'http': 'http://203.110.244.170:80'}, # Exemplo de proxy fictício
    {'http': 'http://185.19.24.162:8080'}, # Exemplo de proxy fictício
    {'http': 'http://103.15.228.163:80'}   # Exemplo de proxy fictício
]

def acessar_com_proxy(url):
    proxy_selecionado = random.choice(lista_de_proxies)
    print(f"\nTentando acessar {url} com proxy: {proxy_selecionado}")

    # Cria um handler para o proxy
    proxy_handler = urllib.request.ProxyHandler(proxy_selecionado)

    # Cria um opener que usa o handler do proxy
    proxy_opener = urllib.request.build_opener(proxy_handler)

    # Define o opener como o opener padrão para todas as requisições (opcional, mas comum)
    # urllib.request.install_opener(proxy_opener)

    try:
        # Usa o opener para abrir a URL
        req = urllib.request.Request(url, headers=headers_personalizados)
        resposta = proxy_opener.open(req, timeout=10) # Timeout para não travar em proxy lento
        conteudo = resposta.read().decode('utf-8')
        print("Acesso bem-sucedido via proxy. Conteúdo (parcial):")
        print(conteudo[:200]) # Imprime parte do conteúdo
    except urllib.error.URLError as e:
        print(f"Erro de URL com proxy: {e.reason}")
    except Exception as e:
        print(f"Erro ao acessar com proxy: {e}")

# Teste com um site que mostra seu IP
acessar_com_proxy('https://httpbin.org/ip')

Análise de Conteúdo (Parsing)

Após baixar o conteúdo de uma página web, o próximo passo é extrair os dados desejados. Existem várias bibliotecas para isso, dependendo do formato do conteúdo (HTML, XML, JSON).

Análise de HTML/XML com XPath (lxml)

XPath é uma linguagem de consulta para selecionar nós em um documento XML (ou HTML). A biblioteca lxml é muito eficiente para isso em Python.

Sintaxe básica do XPath:

  • //: Seleciona nós em qualquer nível.
  • /: Seleciona um nó filho direto.
  • tagname: Seleciona nós com um nome de tag específico.
  • @atributo: Seleciona o valor de um atributo.
  • \[condicao\]: Filtra nós com base em uma condição (ex: \[@id="valor"\]).
  • text(): Seleciona o conteúdo de texto de um nó.
from lxml import etree

html_string = """
<div id="conteudo">
    <h1>Título Principal</h1>
    <ul>
        <li class="item-destaque">Item 1</li>
        <li>Item 2</li>
        <li class="item-normal">Item 3</li>
    </ul>
    <p>Este é um parágrafo <span>importante</span>.</p>
    <a href="/link1" data-id="101">Link Um</a>
    <a href="/link2">Link Dois</a>
</div>
"""

# Cria um objeto ETree a partir da string HTML
tree = etree.HTML(html_string)

# Exemplos de consultas XPath:
print("--- XPath Queries ---")

# 1. Título principal
titulo = tree.xpath('//div[@id="conteudo"]/h1/text()')
print(f"Título: {titulo[0] if titulo else 'N/A'}")

# 2. Todos os itens da lista
itens_lista = tree.xpath('//div[@id="conteudo"]/ul/li/text()')
print(f"Itens da lista: {itens_lista}")

# 3. O item com a classe 'item-destaque'
item_destaque = tree.xpath('//li[@class="item-destaque"]/text()')
print(f"Item destaque: {item_destaque[0] if item_destaque else 'N/A'}")

# 4. O atributo 'href' de todos os links
links_href = tree.xpath('//a/@href')
print(f"Atributos href: {links_href}")

# 5. O atributo 'data-id' de um link específico
data_id_link = tree.xpath('//a[text()="Link Um"]/@data-id')
print(f"Data-ID do Link Um: {data_id_link[0] if data_id_link else 'N/A'}")

# 6. Conteúdo do span dentro do parágrafo
span_text = tree.xpath('//p/span/text()')
print(f"Texto do span: {span_text[0] if span_text else 'N/A'}")

Análise de JSON com JSONPath (jsonpath)

JSONPath é uma linguagem de consulta para documentos JSON, análoga ao XPath para XML. A biblioteca jsonpath em Python oferece essa funcionalidade.

import json
import jsonpath

json_data = {
    "produtos": [
        {"id": 1, "nome": "Teclado Mecânico", "marca": "Logi", "preco": 150.00, "em_estoque": True},
        {"id": 2, "nome": "Mouse Gamer", "marca": "Razer", "preco": 80.00, "em_estoque": False},
        {"id": 3, "nome": "Monitor Ultrawide", "marca": "Dell", "preco": 700.00, "em_estoque": True},
        {"id": 4, "nome": "Webcam HD", "marca": "Logi", "preco": 60.00, "em_estoque": True}
    ],
    "categorias": ["Eletrônicos", "Periféricos", "Acessórios"]
}

print("--- JSONPath Queries ---")

# 1. Nomes de todos os produtos
nomes_produtos = jsonpath.jsonpath(json_data, '$.produtos[*].nome')
print(f"Nomes dos produtos: {nomes_produtos}")

# 2. Preços de todos os produtos
precos = jsonpath.jsonpath(json_data, '$..preco')
print(f"Preços: {precos}")

# 3. O segundo produto da lista (índice 1)
segundo_produto = jsonpath.jsonpath(json_data, '$.produtos[1]')
print(f"Segundo produto: {segundo_produto}")

# 4. Produtos com preço superior a 100
produtos_caros = jsonpath.jsonpath(json_data, '$.produtos[?(@.preco > 100)]')
print(f"Produtos caros: {produtos_caros}")

# 5. Todos os produtos que estão em estoque
em_estoque = jsonpath.jsonpath(json_data, '$.produtos[?(@.em_estoque == true)]')
print(f"Produtos em estoque: {em_estoque}")

# 6. Categorias (retorna a lista de categorias)
categorias = jsonpath.jsonpath(json_data, '$.categorias')
print(f"Categorias: {categorias}")

Análise de HTML com BeautifulSoup (bs4)

BeautifulSoup é uma biblioteca Python para extrair dados de arquivos HTML e XML. Ela cria uma árvore de parse a partir do documento, facilitando a navegação e busca de elementos.

from bs4 import BeautifulSoup

html_doc = """
<html>
<head>
    <title>Página de Exemplo</title>
</head>
<body>
    <div id="main-content">
        <h1 class="page-title">Bem-vindo!</h1>
        <p>Este é um <strong>parágrafo</strong> de exemplo.</p>
        <ul>
            <li id="item1">Primeiro Item</li>
            <li class="special-item">Segundo Item</li>
            <li>Terceiro Item</li>
        </ul>
        <a href="/saiba-mais" class="btn">Saiba Mais</a>
    </div>
    <div class="footer"><span>&copy; 2023</span></div>
</body>
</html>
"""

# Cria um objeto BeautifulSoup
soup = BeautifulSoup(html_doc, 'lxml') # 'lxml' é o parser, rápido e eficiente

print("--- BeautifulSoup Parsing ---")

# 1. Acessar a tag <title>
titulo_tag = soup.title
print(f"Título da página: {titulo_tag.string}")

# 2. Encontrar o primeiro <h1>
h1_tag = soup.find('h1')
print(f"Texto do H1: {h1_tag.get_text()}")
print(f"Classe do H1: {h1_tag['class']}")

# 3. Encontrar todas as tags <li>
todos_li = soup.find_all('li')
print(f"Todos os itens da lista: {[li.get_text() for li in todos_li]}")

# 4. Encontrar um <li> específico por ID ou classe
item_por_id = soup.find(id='item1')
print(f"Item por ID: {item_por_id.get_text()}")
item_por_classe = soup.find('li', class_='special-item')
print(f"Item por classe: {item_por_classe.get_text()}")

# 5. Usando seletores CSS com `select()` (retorna uma lista)
# Selecionar elementos por tag
lista_elementos = soup.select('li')
print(f"Elementos 'li' (select): {[e.get_text() for e in lista_elementos]}")

# Selecionar por ID
main_content = soup.select('#main-content')
print(f"Div principal por ID: {main_content[0].h1.get_text()}")

# Selecionar por classe
page_title = soup.select('.page-title')
print(f"Título por classe: {page_title[0].get_text()}")

# Selecionar elementos aninhados (descendentes)
paragrafo_strong = soup.select('p strong')
print(f"Texto em strong dentro de p: {paragrafo_strong[0].get_text()}")

# Selecionar elementos com um atributo específico
link_btn = soup.select('a[class="btn"]')
print(f"Link com classe 'btn': {link_btn[0]['href']}")

# 6. Acessar atributos de tags
link_tag = soup.find('a')
print(f"Atributo 'href' do link: {link_tag.get('href')}")

Selenium para Automação Web

Selenium é uma ferramenta poderosa para automatizar interações com navegadores web. Originalmente desenvolvida para testes, é amplamente usada em web scraping dinâmico, onde o JavaScript renderiza o conteúdo da página.

Configuração do Selenium

Para usar o Selenium, você precisa instalar a biblioteca Python (pip install selenium) e baixar um driver de navegador (ex: chromedriver para Google Chrome) que corresponda à versão do seu navegador.

Uso Básico do Selenium

from selenium import webdriver
from selenium.webdriver.common.by import By
import time

# O caminho para o chromedriver.exe (certifique-se de que está no seu PATH ou especifique o caminho completo)
# driver_path = 'caminho/para/seu/chromedriver.exe'
# browser = webdriver.Chrome(driver_path) # Se o driver não estiver no PATH

browser = webdriver.Chrome() # Se o chromedriver estiver no PATH do sistema

try:
    # 1. Navegar para uma URL
    browser.get("https://www.google.com")
    print(f"Título da página: {browser.title}")

    # 2. Interagir com elementos: Encontrar um elemento e enviar texto
    campo_pesquisa = browser.find_element(By.NAME, "q") # Encontra o campo de pesquisa pelo atributo 'name'
    campo_pesquisa.send_keys("Selenium Python")
    time.sleep(2)

    # 3. Clicar em um botão
    botao_pesquisar = browser.find_element(By.NAME, "btnK") # Botão "Pesquisar no Google"
    botao_pesquisar.click()
    time.sleep(3)

    print(f"Novo título da página: {browser.title}")

    # 4. Executar JavaScript
    # Rola a página para baixo
    browser.execute_script("window.scrollTo(0, document.body.scrollHeight);")
    time.sleep(2)

    # 5. Navegar para trás e para frente no histórico do navegador
    browser.back() # Volta para a página inicial do Google
    time.sleep(2)
    browser.forward() # Volta para os resultados da pesquisa
    time.sleep(2)

    # 6. Obter o HTML completo da página (page source)
    # page_html = browser.page_source
    # print(page_html[:500]) # Imprime os primeiros 500 caracteres

except Exception as e:
    print(f"Ocorreu um erro durante a automação: {e}")
finally:
    # 7. Fechar o navegador
    browser.quit()
    print("Navegador fechado.")

Localização de Elementos (Element Locators)

O Selenium oferece vários métodos para encontrar elementos em uma página web. O uso de By (do selenium.webdriver.common.by) com find\_element() ou find\_elements() é a forma mais flexível e recomendada.

  • By.ID: browser.find\_element(By.ID, "idDoElemento")
  • By.NAME: browser.find\_element(By.NAME, "nameDoElemento")
  • By.XPATH: browser.find\_element(By.XPATH, "//tag\[@atributo='valor'\]")
  • By.CSS\_SELECTOR: browser.find\_element(By.CSS\_SELECTOR, "#id .classe")
  • By.CLASS\_NAME: browser.find\_element(By.CLASS\_NAME, "classeDoElemento")
  • By.TAG\_NAME: browser.find\_element(By.TAG\_NAME, "div")
  • By.LINK\_TEXT: browser.find\_element(By.LINK\_TEXT, "Texto do Link")
  • By.PARTIAL\_LINK\_TEXT: browser.find\_element(By.PARTIAL\_LINK\_TEXT, "Parte do Texto")

Informações e Interações com Elementos

Após localizar um elemento, você pode obter suas propriedades ou interagir com ele.

  • element.get\_attribute("nome\_do\_atributo"): Obtém o valor de um atributo HTML (ex: href, class).
  • element.tag\_name: Obtém o nome da tag HTML do elemento (ex: "div", "a").
  • element.text: Obtém o texto visível dentro do elemento.
  • element.send\_keys("texto"): Digita texto em campos de entrada.
  • element.click(): Clica no elemento.
  • element.clear(): Limpa o conteúdo de um campo de texto.
  • element.submit(): Envia um formulário (se o elemento for parte de um formulário).

Tags: Python programação estrutura de dados fluxo de controle funções

Publicado em 7-17 15:05