0% acharam este documento útil (0 voto)
8 visualizações457 páginas

Livro Programar Python

O livro 'Programar Python', organizado por Maria Inês Vasconcellos Furtado e coautores, é um manual didático que visa ensinar programação em Python, combinando teoria e prática. Com uma abordagem prática, o livro inclui exercícios, exemplos comentados e estudos de caso, permitindo que os leitores aprofundem seus conhecimentos de forma autônoma. A obra é fruto da experiência dos autores no ensino e desenvolvimento de sistemas, oferecendo uma base sólida para estudantes e interessados na linguagem Python.

Enviado por

sanches
Direitos autorais
© All Rights Reserved
Levamos muito a sério os direitos de conteúdo. Se você suspeita que este conteúdo é seu, reivindique-o aqui.
Formatos disponíveis
Baixe no formato PDF, TXT ou leia on-line no Scribd
0% acharam este documento útil (0 voto)
8 visualizações457 páginas

Livro Programar Python

O livro 'Programar Python', organizado por Maria Inês Vasconcellos Furtado e coautores, é um manual didático que visa ensinar programação em Python, combinando teoria e prática. Com uma abordagem prática, o livro inclui exercícios, exemplos comentados e estudos de caso, permitindo que os leitores aprofundem seus conhecimentos de forma autônoma. A obra é fruto da experiência dos autores no ensino e desenvolvimento de sistemas, oferecendo uma base sólida para estudantes e interessados na linguagem Python.

Enviado por

sanches
Direitos autorais
© All Rights Reserved
Levamos muito a sério os direitos de conteúdo. Se você suspeita que este conteúdo é seu, reivindique-o aqui.
Formatos disponíveis
Baixe no formato PDF, TXT ou leia on-line no Scribd

PROGRAMAR PYTHON

Organizadora:
Maria Inês Vasconcellos Furtado

Autores:
Maria Inês Vasconcellos Furtado
Pedro Ramos Brandão
José Cláudio Garcia Damaso
Lúcio Pereira de Andrade
Inês Gonçalves Saúde de Almeida

Edição: 1ª, 2026


ISBN: 978-989-33-9406-9
DOI: 10.5281/zenodo.18902707

Licença: CC BY 4.0 (Creative Commons Attribution International)


[Link]

Lisboa, Portugal: Zenodo, Março, 2026

2
PREFÁCIO
A experiência do outro é o segredo da mudança 1.¹

Experiência para compartilhar não falta para os autores deste livro. Inês
Gonçalves Saúde de Almeida leciona Programação em Python desde 2020 e,
profissionalmente, utiliza Python para a análise de dados, além de ter
desenvolvido um chatbot de inteligência artificial em Python para apoio a
alunos e professores. José Cláudio Garcia Damaso atua como professor em
cursos de graduação há 23 anos e possui experiência de mais de 20 anos de
mercado no desenvolvimento de sistemas nas áreas financeiro-contábil,
acadêmica e administrativa usando diversas linguagens de programação.
Lúcio Pereira de Andrade possui mais de 30 anos de experiência em análise,
desenvolvimento e ensino de sistemas de informação, sua relação com
Python tem se dado pela aplicação em pesquisa em visão computacional e
pelo ensino de introdução à lógica de programação. Maria Inês Vasconcellos
Furtado utiliza Python no desenvolvimento de algoritmos e sistemas
inteligentes e também no ensino em sala de aula, leciona no ensino superior
há quase 30 anos, com experiência sólida em programação, IA e tecnologias
emergentes. Pedro Ramos Brandão realiza investigação em cibersegurança
utilizando Inteligência Artificial, com algoritmos desenvolvidos em Python.

Se eu iniciasse esse prefácio dizendo que “qualquer semelhança com pessoas


vivas e fatos reais não será mera coincidência”, talvez caísse num chavão um
tanto desgastado. No entanto, estaria afirmando a essência mesma deste
trabalho como o começo de uma reformulação desses autores para chegar ao
outro que diariamente os desafia na sala de aula.

Logo, a semelhança com o real existe porque foi esta a opção deste grupo de
professores: trazer à luz os resultados de uma experiência no exercício de

1
1 PAZ, Octavio. Convergências: ensaios sobre a arte e a literatura. Tradução por Moacir Werneck de Castro. Rio de
Janeiro: Rocco, 1991. p. 54. Tradução de Convergences.

3
lecionar. E o que temos? Um manual didático que alinha capítulos que
apresentam a teoria sempre seguidos de seções que destacam a aplicabilidade
do que foi exposto. Sabemos que, quando damos ênfase ao carácter prático
do que ensinamos, estabelecemos uma relação de sedução com os nossos
alunos. Isso não se limita à proposta de exercícios, encontramos exemplos
práticos comentados e estudos de casos. As respostas das atividades
contempladas ao final do livro permitem o estudo e o aprofundamento por
parte do leitor com autonomia para ampliar o seu saber nesta temática.

Enfim, este trabalho, fruto da inquietação destes educadores, se torna um


estímulo para que estudantes e leitores interessados em Python tenham a
oportunidade de conhecer e aprofundar seu caminhar nesta linguagem de
programação. Assim, o exercício de trazer à luz a experiência deste grupo
amadurecida no cotidiano da sala de aula com todas as suas fraturas traz,
também, o desejo de que todas elas - de alguma forma - se tornem coletivas,
embora nunca definitivas.

Continuem, sim, a experiência do outro imbuída com o segredo da mudança.

Lisboa, março, 2026

Silvana dos Santos Ambrosoli

4
DEDICATÓRIA
Às nossas famílias, pelo apoio, e aos
nossos alunos, pela inspiração.

5
ÍNDICE
1 CONCEITOS BÁSICOS DE PROGRAMAÇÃO EM PYTHON ............ 13
1.1 INSTALAÇÃO DO PYTHON ........................................................................ 14
1.2 O INTERPRETADOR PYTHON .................................................................... 15
1.3 IDLE: AMBIENTE DE DESENVOLVIMENTO INTEGRADO........................... 16
1.3.1 Aceder ao IDLE .............................................................................. 17
1.4 PRIMEIRO PROGRAMA EM PYTHON .......................................................... 18
1.5 CARACTERÍSTICAS PRINCIPAIS DO PYTHON ............................................. 19
1.6 EXERCÍCIOS............................................................................................. 20
2 TIPOS DE DADOS E ESTRUTURAS BÁSICAS ..................................... 22
2.1 NÚMEROS, TEXTO E BOOLEANOS ............................................................. 22
2.2 CONVERSÃO DE TIPOS ............................................................................. 24
2.3 CONSTANTES E BOAS PRÁTICAS .............................................................. 25
2.4 COMENTÁRIOS E DOCSTRINGS ................................................................ 26
2.4.1 Comentários Simples ...................................................................... 26
2.4.2 Docstrings Básicas ......................................................................... 26
2.4.3 Quando Usar # e ´´´ ....................................................................... 27
2.5 BIBLIOTECAS........................................................................................... 28
2.5.1 Importação de biblioteca ................................................................ 29
2.5.2 Bibliotecas Mais Utilizadas ............................................................ 32
2.6 EXERCÍCIOS PROPOSTOS ......................................................................... 34
3 OPERADORES E EXPRESSÕES .............................................................. 36
3.1 OPERADORES ARITMÉTICOS .................................................................... 36
3.1.1 Precedência de Operadores ........................................................... 38
3.1.2 Operadores de atribuição composta .............................................. 39
3.2 OPERADORES RELACIONAIS .................................................................... 40
3.2.1 Comparações Encadeadas em Python............................................ 40
3.3 OPERADORES LÓGICOS ........................................................................... 43
3.3.1 Avaliação de Curto-Circuito (Short-Circuit Evaluation). .............. 43
3.4 OPERADORES DE IDENTIDADE ................................................................. 44
3.4.1 Diferença entre os operadores == e is .......................................... 45
3.5 OPERADORES DE ASSOCIAÇÃO ................................................................ 46
3.6 EXERCÍCIOS............................................................................................. 46
4 ENTRADA E SAÍDA DE DADOS .............................................................. 48
4.1 FUNÇÃO INPUT() PARA ENTRADA DE DADOS............................................ 48
4.2 CONVERSÃO DE TIPOS DE DADOS ............................................................ 49

6
4.3 FUNÇÃO PRINT() PARA SAÍDA DE DADOS ............................................... 51
4.4 PERSONALIZAÇÃO DA SAÍDA COM PRINT() .............................................. 52
4.5 TÉCNICAS DE FORMATAÇÃO DE STRINGS ................................................. 53
4.5.1 Concatenação com vírgulas e com o operador +........................... 53
4.5.2 Método [Link]() ........................................................................ 54
4.5.3 f-strings (Formatted String Literals) .............................................. 55
4.6 EXERCÍCIOS PROPOSTOS .......................................................................... 62
5 ESTRUTURAS DE CONTROLO .............................................................. 64
5.1 ESTRUTURAS CONDICIONAIS (IF, ELIF, ELSE) .................................... 64
5.1.1 Conceito fundamental em Python: indentação ............................... 64
5.1.2 Estrutura if ..................................................................................... 66
5.1.3 Estrutura if-else .............................................................................. 67
5.1.4 Estrutura if-elif-else........................................................................ 68
5.1.5 Operador Ternário ......................................................................... 69
5.1.6 Condições múltiplas com operadores lógicos ................................ 71
5.2 CICLOS (FOR, WHILE) ............................................................................. 72
5.2.1 Ciclo for.......................................................................................... 72
5.2.2 Função range() ............................................................................... 73
5.2.3 Ciclo while ...................................................................................... 75
5.2.4 Controlo de ciclos: break e continue .............................................. 76
5.3 ESTRUTURAS DE CONTROLO ANINHADAS ............................................... 78
5.3.1 Ciclos Dentro de Ciclos.................................................................. 78
5.4 ESTRUTURAS CONDICIONAIS DENTRO DE CICLOS ................................... 79
5.5 CICLOS DENTRO DE ESTRUTURAS CONDICIONAIS ................................... 80
5.6 EXEMPLO PRÁTICO DE ESTRUTURAS ANINHADAS.................................... 82
5.7 GERAÇÃO DE NÚMEROS ALEATÓRIOS COM O MÓDULO RANDOM............... 85
5.7.1 [Link]() ........................................................................... 85
5.7.2 [Link](a, b) ...................................................................... 85
5.7.3 [Link](a, b) ..................................................................... 86
5.7.4 [Link](sequencia).............................................................. 86
5.7.5 [Link](lista) ...................................................................... 86
5.7.6 Exemplos de aplicações práticas .................................................... 87
5.7.7 Controlar a aleatoriedade com sementes (seeds) ....................... 89
5.7.8 Limitações e considerações ............................................................ 91
5.8 TRATAMENTO DE ERROS COM TRY-EXCEPT............................................. 92
5.8.1 Tipos comuns de erros .................................................................... 94
5.8.2 Múltiplos blocos except .................................................................. 94
5.8.3 Bloco else e finally.......................................................................... 94
5.8.4 Boas práticas .................................................................................. 95
5.9 EXERCÍCIOS PROPOSTOS .......................................................................... 96

7
6 FUNÇÕES EM PYTHON ......................................................................... 103
6.1 DEFINIÇÃO DE FUNÇÕES ........................................................................ 103
6.2 ÂMBITO DE VARIÁVEIS .......................................................................... 105
6.2.1 Âmbito Local e Global.................................................................. 105
6.2.2 Palavra-chave: global .............................................................. 107
6.2.3 Âmbito não local (variáveis em funções aninhadas) .................... 108
6.2.4 Exemplo comparativo de âmbito de variáveis .............................. 109
6.3 TIPOS DE FUNÇÕES ................................................................................ 111
6.3.1 Funções sem parâmetros e sem retorno ....................................... 111
6.3.2 Funções com parâmetros e sem retorno ....................................... 112
6.3.3 Funções com parâmetro e com retorno ........................................ 113
6.3.4 Funções com múltiplos retornos................................................... 115
6.4 ARGUMENTOS OPCIONAIS E NOMEADOS ................................................ 116
6.4.1 Parâmetros com valores padrão (opcionais) ............................... 117
6.4.2 Múltiplos parâmetros opcionais ................................................... 118
6.4.3 Argumentos nomeados (keyword arguments) ............................... 120
6.5 FUNÇÕES ANÓNIMAS (LAMBDA).............................................................. 121
6.6 MAIN() E O GUARDIÃO IF __NAME__ == "__MAIN__": ................. 122
6.6.1 Comportamento na prática........................................................... 126
6.6.2 Erros comuns e como os evitar..................................................... 128
6.7 EXEMPLOS PRÁTICOS ............................................................................ 131
6.7.1 Exemplo 1: Sistema de Pontuação para Jogo .............................. 132
6.7.2 Exemplo 2: Analisador de Estatísticas de Streamer ..................... 134
6.7.3 Exemplo 3: Sistema de Conquistas para App Fitness .................. 136
6.7.4 Exemplo 4: Mistura de Poções num Jogo de Fantasia................. 138
6.8 EXERCÍCIOS PROPOSTOS ........................................................................ 140
7 ESTRUTURAS DE DADOS EM PYTHON ............................................ 146
7.1 LISTAS................................................................................................... 146
7.1.1 Criar e aceder a listas .................................................................. 147
7.1.2 Alterar e adicionar elementos a uma lista .................................... 148
7.1.3 Iterar sobre listas ......................................................................... 150
7.1.4 List Comprehensions (Compreensões de Lista)............................ 153
7.1.5 Listas Multidimensionais .............................................................. 157
7.2 TUPLOS ................................................................................................. 158
7.2.1 Utilidade e Vantagens dos tuplos ................................................. 160
7.2.2 Desempacotamento de Tuplos (Unpacking) ................................. 162
7.2.3 Iterar sobre tuplos ........................................................................ 166
7.2.4 Tuplos multidimensionais ............................................................. 167
7.3 CONJUNTOS (SET).................................................................................. 168
7.3.1 Criar um set .................................................................................. 168
8
7.3.2 Operações matemáticas em conjuntos.......................................... 170
7.3.3 Métodos úteis em sets ................................................................... 171
7.4 DICIONÁRIOS (DICT) ............................................................................. 173
7.4.1 Criar e aceder a dicionários ........................................................ 173
7.4.2 Adicionar e alterar valores .......................................................... 174
7.4.3 Métodos úteis ................................................................................ 175
7.5 COMPARAÇÃO E BOAS PRÁTICAS .......................................................... 177
7.5.1 Guia Prático de Seleção por Cenário .......................................... 178
7.5.2 Considerações de Desempenho e Eficiência ................................ 182
7.6 USO PRÁTICO DE LAMBDA EM COLEÇÕES ............................................... 183
7.7 EXEMPLOS PRÁTICOS ............................................................................ 186
7.7.1 Análise de Redes Sociais (Conjuntos) .......................................... 186
7.7.2 Sistema de conquistas em jogos.................................................... 187
7.7.3 Playlist Inteligente ........................................................................ 188
7.7.4 Controlo de Gastos Pessoais ........................................................ 188
7.7.5 Torneio de e-sports ....................................................................... 189
7.7.6 Análise de hábitos digitais............................................................ 190
7.7.7 Sistemas de Recompensa .............................................................. 191
7.7.8 Match de Amizade ........................................................................ 192
7.8 EXERCÍCIOS PROPOSTOS ........................................................................ 193
8 MANIPULAÇÃO E FORMATAÇÃO DE STRINGS ............................ 200
8.1 INDEXAÇÃO E FATIAMENTO .................................................................. 200
8.1.1 Indexação ..................................................................................... 201
8.1.2 Fatiamento (slicing) ..................................................................... 202
8.2 MÉTODOS DE MANIPULAÇÃO ................................................................ 203
8.3 FORMATAÇÃO MODERNA (F-STRINGS) ................................................. 206
8.4 CASOS RESOLVIDOS E EXPLICADOS ...................................................... 213
8.4.1 Normalização de Nomes de Utilizador ......................................... 213
8.4.2 Extração de Informação de um E-mail......................................... 214
8.4.3 Estatísticas de Jogadores - Tabela de Classificação.................... 215
8.5 EXERCÍCIOS PROPOSTOS ....................................................................... 218
9 FICHEIROS EM PYTHON ...................................................................... 223
9.1 ABERTURA DE FICHEIROS COM OPEN() ................................................. 223
9.1.1 Nome e extensão de ficheiros ....................................................... 224
9.1.2 Texto vs. Binário........................................................................... 227
9.1.3 Fechar ficheiros com close() ........................................................ 230
9.1.4 O uso do with................................................................................ 230
9.1.5 Buffering e desempenho ............................................................... 231
9.2 MÉTODOS DE LEITURA DE FICHEIROS .................................................... 232

9
9.2.1 Ler todo o conteúdo (read()) ................................................... 232
9.2.2 Ler linha a linha (readline())................................................ 233
9.2.3 Ler todas as linhas (readlines()) ......................................... 233
9.2.4 Iterar diretamente sobre o ficheiro .............................................. 234
9.3 MÉTODOS DE ESCRITA EM FICHEIROS .................................................... 235
9.3.1 Escrever conteúdo (write()) .......................................................... 235
9.3.2 Escrever múltiplas linhas (writelines()) ....................................... 236
9.4 EXEMPLO PRÁTICO COM FICHEIROS DE TEXTO ...................................... 237
9.5 TRATAMENTO DE EXCEÇÕES ................................................................. 238
9.5.1 Exemplos práticos: leitura e cópia de ficheiros texto ................... 240
9.6 MANIPULAR FICHEIROS CSV E JSON ................................................... 244
9.6.1 Ficheiros CSV (Comma-Separated Values) ................................. 244
9.6.2 Ficheiros JSON (JavaScript Object Notation) ............................. 249
9.7 POSICIONAMENTO E ESCOAMENTO DE DADOS ....................................... 253
9.8 CODIFICAÇÃO DE CARACTERES: ESTRATÉGIA E GESTÃO DE ERROS........ 254
9.8.1 Erros ............................................................................................. 254
9.8.2 Estratégia recomendada ............................................................... 255
9.9 APLICAÇÕES DE FICHEIROS TEXTO ....................................................... 261
9.9.1 Migrar base de dados de clientes para API web .......................... 261
9.9.2 Relatório diário de vendas da loja ............................................... 262
9.9.3 Fechamento diário de caixa ......................................................... 263
9.9.4 Sistema de configuração de aplicação web .................................. 264
9.9.5 Conversor de Formatos (CSV ⇄ JSON)....................................... 265
9.10 FICHEIROS BINÁRIOS ............................................................................. 267
9.10.1 Modo de Abertura de Ficheiros Binários ..................................... 268
9.10.2 Leitura de Ficheiros Binários....................................................... 268
9.10.3 Escrita em Ficheiros Binários ...................................................... 269
9.10.4 Manipulação de Dados Binários .................................................. 270
9.10.5 Ficheiros Binários e struct ........................................................... 270
9.10.6 Assinaturas binárias (magic numbers) ......................................... 271
9.11 EXEMPLOS PRÁTICOS ............................................................................ 273
9.11.1 Analisador de Logs para Deteção de Intrusões............................ 273
9.11.2 Sistema de Treino de Modelo IA com Persistência de Dados ...... 275
9.11.3 Sistema de Proteção de Ficheiros Simples ................................... 277
9.12 EXERCÍCIOS PROPOSTOS ....................................................................... 281
10 PROGRAMAÇÃO ORIENTADA A OBJETOS ................................. 282
10.1 CLASSES E OBJETOS............................................................................... 282
10.2 ATRIBUTOS E MÉTODOS......................................................................... 285
10.3 ENCAPSULAMENTO ............................................................................... 288
10.4 HERANÇA .............................................................................................. 290

10
10.4.1 Herança múltipla .......................................................................... 292
10.5 SOBRESCRITA DE MÉTODOS................................................................... 295
10.6 EXERCÍCIOS PROPOSTOS ....................................................................... 296
11 BIBLIOTECAS ÚTEIS EM PYTHON ................................................ 299
11.1 MÓDULOS E PACOTES ............................................................................ 299
11.1.1 Criar módulos e pacotes próprios ................................................ 301
11.2 BIBLIOTECA PADRÃO – TÓPICOS AVANÇADOS E PRÁTICOS .................... 302
11.2.1 math: precisão, combinatória e utilidades.................................. 302
11.2.2 random: objetos geradores, pesos e amostragem ...................... 303
11.2.3 datetime: fusos horários e horário de verão com zoneinfo 304
11.3 INTRODUÇÃO A NUMPY (ARRAYS, VECTORIZAÇÃO E BROADCASTING) ... 305
11.3.1 Conceitos-chave ........................................................................... 305
11.3.2 Views vs. cópias ............................................................................ 307
11.3.3 Entrada/Saída e tipos de dados .................................................... 308
11.3.4 Estatística e aleatoriedade ........................................................... 309
11.4 INTRODUÇÃO A MATPLOTLIB (API ORIENTADA A OBJETOS) ................ 310
11.4.1 Estrutura geral e filosofia da biblioteca ....................................... 311
11.4.2 Tipos fundamentais de gráficos .................................................... 312
11.4.3 Personalização e estilo ................................................................. 317
11.5 EXERCÍCIOS PROPOSTOS ........................................................................ 318
12 DESENVOLVENDO INTERFACES GRÁFICAS COM TKINTER 321
12.1 INTRODUÇÃO AO TKINTER .................................................................... 321
12.2 CONFIGURAÇÃO INICIAL ....................................................................... 322
12.3 ESTRUTURA BÁSICA DE UMA APLICAÇÃO TKINTER .............................. 323
12.4 WIDGETS FUNDAMENTAIS .................................................................... 325
12.4.1 Label (Rótulos) ............................................................................. 325
12.4.2 Button (Botões) ............................................................................. 327
12.4.3 Entry (Campos de Entrada).......................................................... 327
12.4.4 Text (Área de Texto) ..................................................................... 330
12.5 GERENCIADORES DE LAYOUT ............................................................... 334
12.6 WIDGETS AVANÇADOS ......................................................................... 337
12.7 MENUS E CAIXAS DE DIÁLOGO ............................................................. 340
12.8 APLICAÇÃO: MINI EDITOR DE TEXTO.................................................... 342
12.9 BOAS PRÁTICAS DE DESENVOLVIMENTO .............................................. 344
12.10 CONCLUSÃO ...................................................................................... 344
12.11 EXERCÍCIO PROPOSTO ....................................................................... 345
13 DO DATA MINING AO DASHBOARD.............................................. 347
13.1 IMPORTAÇÃO DE DADOS ........................................................................ 348

11
13.1.1 Padrão .csv ................................................................................... 349
13.1.2 APIs: Conceito e Acesso ............................................................... 350
13.1.3 Organização do Projeto em Módulos ........................................... 354
13.2 EDA: ANÁLISE EXPLORATÓRIA DE DADOS........................................... 355
13.2.1 Análise do conjunto de dados ....................................................... 356
13.2.2 Deteção de Outliers ...................................................................... 363
13.2.3 Correlação entre variáveis ........................................................... 366
13.3 LIMPEZA DE DADOS .............................................................................. 368
13.4 VISUALIZAÇÃO DE DADOS E TOMADA DE DECISÃO .............................. 372
13.5 EXERCÍCIOS PROPOSTOS ....................................................................... 378
14 APLICAÇÕES DE PYTHON EM CIBERSEGURANÇA ................. 380
14.1 FUNDAMENTOS DA SEGURANÇA DA INFORMAÇÃO................................ 381
14.2 GERAÇÃO DE SENHAS E CHAVES SEGURAS ........................................... 381
14.3 HASHING E VERIFICAÇÃO DE INTEGRIDADE .......................................... 382
14.4 CRIPTOGRAFIA SIMÉTRICA .................................................................... 383
14.5 CRIPTOGRAFIA ASSIMÉTRICA E ASSINATURAS DIGITAIS....................... 385
14.6 COMUNICAÇÃO SEGURA COM HTTPS E SSH........................................ 388
14.7 ANÁLISE E VARREDURA DE REDE ......................................................... 390
14.8 BOAS PRÁTICAS E ÉTICA PROFISSIONAL ............................................... 393
14.9 EXERCÍCIOS PROPOSTOS ....................................................................... 394
15 RESPOSTAS........................................................................................... 395
15.1 CAPÍTULO 2 ....................................................................................... 395
15.2 CAPÍTULO 3 ....................................................................................... 397
15.3 CAPÍTULO 4 ....................................................................................... 399
15.4 CAPÍTULO 5 ....................................................................................... 401
15.5 CAPÍTULO 6 ....................................................................................... 411
15.6 CAPÍTULO 7 ....................................................................................... 417
15.7 CAPÍTULO 8 ....................................................................................... 427
15.8 CAPÍTULO 9 ....................................................................................... 436
15.9 CAPÍTULO 10 ..................................................................................... 447

12
1 CONCEITOS BÁSICOS DE PROGRAMAÇÃO EM PYTHON

A linguagem Python é atualmente uma das mais utilizadas no mundo. Criada


por Guido van Rossum e lançada em 1991, tornou-se popular pela sua
simplicidade, clareza e pela vasta comunidade de utilizadores. Ao longo dos
anos consolidou-se como uma linguagem versátil, adequada tanto para quem
inicia o estudo da programação como para profissionais experientes que
procuram rapidez e produtividade. A sintaxe concisa do Python, com
ausência de elementos desnecessários e uma grande quantidade de
bibliotecas disponíveis fazem com que seja uma escolha natural em
contextos de investigação, indústria e ensino.
A linguagem Python encontra-se presente em praticamente todas as áreas
ligadas à informática e às ciências. É possível usá-la em Ciência de Dados
para análise estatística e tratamento de grandes volumes de informação (Big
Data), em Inteligência Artificial para treinar modelos de aprendizagem
automática, no desenvolvimento web através de frameworks robustas como
Django e Flask, na automação de tarefas rotineiras através de scripts simples
e no ensino como primeira linguagem pela clareza que transmite aos
estudantes. Esta amplitude de aplicação explica a sua enorme popularidade
mundial.
Python segue uma filosofia de código limpo e legível, com uma escrita clara,
próxima da linguagem natural, em vez de símbolos complexos. Uma das suas
maiores forças é a biblioteca padrão, que inclui funções e módulos prontos a
usar para resolver problemas do dia a dia. Esta biblioteca cobre desde

13
operações matemáticas elementares até à manipulação de ficheiros,
tratamento de datas e horas, compressão de dados, utilização de protocolos
de rede ou até execução de testes automáticos.
É importante ressaltar que o Python é uma linguagem interpretada, ou seja,
o código é lido e executado linha a linha pelo interpretador, sem necessidade
de compilação prévia. Esta característica permite testar instruções de forma
imediata, através do REPL (Read-Eval-Print-Loop), e facilita a deteção de
erros.
Uma das marcas mais distintivas do Python é o uso obrigatório da indentação
para organizar blocos de código. Em vez de recorrer a chavetas ou palavras-
chave adicionais, o recuo define claramente onde começa e termina cada
bloco, o que torna o programa mais legível e reduz ambiguidades.

1.1 Instalação do Python

O Python está disponível para transferência gratuita no site oficial:


[Link] A instalação é simples e adaptada a diferentes
sistemas operativos. Nas distribuições modernas de Linux e macOS já se
encontra habitualmente incluído, pelo que dispensa qualquer configuração
adicional. Para Windows, basta executar o instalador e assinalar a opção
“Add Python to PATH”, para garantir que o interpretador fique acessível a
partir da linha de comandos. Após instalar, recomenda-se confirmar a versão
e, para tal, escrever no terminal:
python --version
ou, em algumas distribuições, onde o comando Python pode referir-se à
versão 2.x, deve-se usar:
14
python3 --version
Se surgir no ecrã uma indicação semelhante a Python 3.x.x, a instalação
foi concluída com sucesso. A versão mais recente é sempre a mais indicada
para iniciar, uma vez que inclui melhorias de desempenho e funcionalidades
adicionais.
Para além do interpretador oficial (CPython), existem outras
implementações, como a PyPy (focada em desempenho) ou a Jython (que
corre na Java Virtual Machine), entretanto CPython é a mais comum e
recomendada para a generalidade dos utilizadores.
Após a instalação, já pode começar a programar escrevendo código num
ficheiro com a extensão .py e executar no terminal ou utilizar o Ambiente
de Desenvolvimento Integrado (IDE) que vem por predefinição, chamado
IDLE, que fornece um ambiente mais amigável com realce de sintaxe e
depuração. Para projetos mais complexos, os programadores costumam optar
por IDEs mais robustas, como o PyCharm, ou editores de código como o
Visual Studio Code, com extensões para Python.

1.2 O interpretador Python

O interpretador Python é o programa que lê e executa o código. A forma mais


básica de o utilizar é através do terminal ou da linha de comandos. Para o
iniciar, basta escrever python ou python3, sendo que este último é
comum em sistemas Unix para garantir o uso da versão 3, e pressionar Enter.
Imediatamente, surgirá o prompt interativo (>>>), a indicar que o
interpretador está pronto a receber instruções.
>>>

15
Neste ambiente, podes experimentar comandos simples e ver os resultados
instantaneamente. É uma ferramenta extremamente útil para testes rápidos,
explorar funcionalidades da linguagem ou realizar cálculos como se de uma
calculadora avançada se tratasse.

O interpretador é útil para testes rápidos e para explorar a linguagem.

1.3 IDLE: Ambiente de Desenvolvimento Integrado

O IDLE (Integrated Development and Learning Environment) é o ambiente


de desenvolvimento integrado que vem incluído por predefinição na
instalação do Python. Concebido para ser simples e intuitivo, é uma
ferramenta excelente para principiantes, pois permite escrever, executar e
depurar código de forma organizada. Oferece funcionalidades essenciais
como realce de sintaxe, auto completar código e um debugger interactivo,
tudo dentro de uma interface minimalista e fácil de usar.
A janela principal inclui uma shell interactiva (REPL) para testes rápidos e
um editor para criar e gerir ficheiros de código mais extensos (.py). Apesar
de existirem alternativas mais poderosas no mercado, como o PyCharm ou o
VS Code, o IDLE mantém-se como uma opção válida e prática para aprender
os conceitos básicos da programação em Python sem configurações
complexas.

16
1.3.1 Aceder ao IDLE

Aceder ao IDLE é um processo simples que varia ligeiramente consoante o


seu sistema operativo.
− Windows: procurar por "IDLE" no menu Iniciar, onde aparece
automaticamente após a instalação do Python. Como alternativa, podes
abrir a linha de comandos (CMD ou PowerShell) e digitar
simplesmente idle.
− macOS: o método mais comum é abrir o Terminal e executar o comando
idle3. Se instalaste o Python a partir do site oficial, também poderás
encontrá-lo na pasta de Aplicações.
− Linux: o processo é maioritariamente feito através do terminal. A
maioria das distribuições permite abrir o IDLE com o comando idle3.
No entanto, é importante notar que muitas versões de Linux não incluem
o IDLE pré-instalado. Entretanto, se o comando não funcionar, podes
instalá-lo facilmente, como por exemplo, no Ubuntu ou Debian usando
sudo apt install idle3.

Independentemente do método utilizado, ao abrir o IDLE vai deparar-se com


a Python Shell, um ambiente interativo onde pode testar código linha a linha.
Para criar programas completos, basta ir ao menu File e selecionar New File
para abrir um editor onde podes escrever e guardar o seu código com a
extensão .py.

17
1.4 Primeiro programa em Python

O primeiro programa tradicional em qualquer linguagem de programação é


o "Olá, Mundo!", e com o Python não é diferente. Este exemplo simples
demonstra como é fácil começar e pode ser escrito e executado de duas
formas:
1. Diretamente no interpretador interativo: Basta abrir o terminal (como
descrito na secção 1.2), escrever o comando e pressionar Enter para ver
o resultado imediato.

2. Num ficheiro: Pode criar um novo ficheiro de texto com a extensão


.py (por exemplo, ola_mundo.py) usando o IDLE ou outro editor de
texto. Escreva a linha de código, guarde o ficheiro e execute-o
diretamente no IDLE (Run > Run Module) ou no terminal com o
comando python ola_mundo.py (ou python3
ola_mundo.py, se necessário).

É importante observar que, ao contrário de linguagens como C ou Java, em


Python não é necessário declarar uma função principal (main) nem incluir
cabeçalhos ou bibliotecas para uma operação básica. O código é executado
sequencialmente, de cima para baixo, o que simplifica muito a escrita dos
primeiros

18
1.5 Características principais do Python

A simplicidade e poder do Python derivam de um conjunto de características


fundamentais que o distinguem de outras linguagens:
− Tipagem Dinâmica: Não é necessário declarar explicitamente o tipo de
uma variável (como int ou string) ao criá-la. O interpretador infere
o tipo automaticamente com base no valor que lhe é atribuído. Por
exemplo, ao escrever x = 5, o Python percebe que x é um inteiro.
− Sintaxe Clara e Minimalista: A linguagem foi concebida para ser legível.
Elimina a necessidade de caracteres como chavetas {} para definir
blocos de código ou ponto e vírgula ; para terminar instruções. Em vez
disso, usa indentação obrigatória (os recuos no início da linha) para
estruturar o código. Isto força uma escrita organizada e consistente.
− Multiplataforma: O Python é uma linguagem portátil. O mesmo código
pode correr sem modificações nos principais sistemas operativos:
Windows, macOS e Linux. Isto deve-se à existência de interpretadores
específicos desenvolvidos para cada plataforma.
− Natureza Interpretada e Interativa: Como já mencionado, o código é
executado linha a linha por um interpretador. Esta característica permite
a existência de um modo interativo (REPL), que funciona como uma
calculadora avançada ou um laboratório para testes rápidos e
aprendizagem.
− Ecossistema Rico e Extensível: Uma das maiores forças do Python é a
sua vasta coleção de bibliotecas (também chamadas de packages ou
módulos). Para além da extensa biblioteca padrão, a comunidade

19
desenvolveu milhares de bibliotecas externas para as mais diversas áreas.
Plataformas como o PyPI (Python Package Index) permitem instalar
facilmente bibliotecas como:
⋅ numpy e pandas para cálculo científico e análise de dados.
⋅ matplotlib e seaborn para visualização de dados e criação
de gráficos.
⋅ Flask e Django para desenvolvimento web.
⋅ requests para fazer pedidos HTTP.
Esta extensibilidade permite que developers resolvam problemas complexos
com poucas linhas de código, aproveitando o trabalho já realizado pela
comunidade.

1.6 Exercícios

1. Instalar o Python e Abrir o Interpretador.


Se ainda não o fez, siga as instruções da secção 1.1 para transferir e instalar
o Python a partir do site oficial ([Link]

2. Abra o interpretador interativo e teste as duas formas:


a. Terminal/Linha de Comandos  Abrir o terminal (Linux/macOS)
ou CMD/PowerShell (Windows). Escrever python ou python3 e
pressione Enter. Deverá aparecer o prompt >>>
b. Procure por IDLE no menu Iniciar do Windows ou execute idle3
no terminal de outras sistemas.

20
3. Operações no Interpretador: Executar as seguintes operações no
interpretador, ou seja, escrever cada linha e pressionar Enter para ver o
resultado.

4. Programa Básico num Ficheiro: Escrever um programa num ficheiro


chamado [Link] que exibe no ecrã uma mensagem.
a. Abrir o IDLE ou qualquer editor de texto
b. Criar um novo ficheiro com o a seguinte linha de código:
print("Bem-vindo ao mundo do Python!")
c. Guardar o ficheiro com o nome [Link]
d. Executar o programa.
− No terminal: Navegar até a pasta onde guardaste o ficheiro e
executar:
python [Link]
− No IDLE: Com o ficheiro aberto, pressione F5 ou vá ao menu
Run > Run Module

21
2 TIPOS DE DADOS E ESTRUTURAS BÁSICAS

Em Python, tudo é um objeto, e cada objeto tem um tipo que define o que é
e que operações pode-se fazer com ele. Os tipos de dados básicos são a base
para toda a programação em Python.
Em Python não é necessário declarar previamente o tipo de uma variável. O
interpretador identifica automaticamente o tipo de dado atribuído e ajusta o
seu comportamento de acordo com o valor. Esta característica torna a escrita
do código mais simples e próxima da linguagem natural, mas exige atenção
para compreender que diferentes tipos de dados podem influenciar o
resultado das operações.

2.1 Números, texto e booleanos

Python suporta vários tipos numéricos e os mais comuns são apesentadas a


seguir.
a) Números
O Python suporta diferentes tipos numéricos. Os inteiros (int) representam
valores sem casas decimais, como -3, 0 ou 42. Os reais (float)
permitem trabalhar com números que possuem casas decimais, como 3.141
ou -0.5. Para além destes, a linguagem inclui ainda os complexos
(complex), que possuem uma parte real e uma parte imaginária, sendo
representados, por exemplo, como 2 + 3j.

22
b) Texto (str)
Em Python, as sequências de caracteres podem ser delimitadas por aspas
simples ('...') ou por aspas duplas ("..."), sem diferença de
significado.
Para além destas formas, existe ainda a possibilidade de usar aspas triplas
('''...'''). Este recurso é especialmente útil para escrever texto em
várias linhas sem necessidade de caracteres de escape e para documentação
(docstrings). O conteúdo é preservado tal como foi escrito, incluindo as
quebras de linha.

E a saída a ser exibida no ecrã é:

Deves ter atenção a qualquer indentação colocada dentro de aspas triplas,


pois esta formatação faz parte da string. Se o texto for escrito com recuo no
código, esses espaços aparecerão na saída no ecrã.
23
c) Booleanos (bool)
Os valores booleanos representam estados lógicos, podendo assumir apenas
duas possibilidades: Verdadeiro (True) ou Falso (False). São
fundamentais em expressões condicionais e estruturas de controlo, pois
permitem decidir que partes do programa devem ou não ser executadas.

2.2 Conversão de tipos

Em muitas situações é necessário converter valores entre tipos diferentes.


Python disponibiliza funções específicas para isso:
− int(): converte para inteiro
− float(): converte para número real (ou decimal)
− str(): converte para texto (cadeia de caracteres)
− bool(): converte para valor lógico (booleano).

Um exemplo de uso ocorre quando se lê um valor do teclado com a função


input(), o Python sempre devolve esse valor como string (str), mesmo
que o utilizador escreva apenas algarismos. É, então necessário transformar
o valor da variável para outro tipo de dado adequado para se poderem realizar
as operações matemáticas.
Considere, por exemplo, que a variável x guarda o texto "25". Apesar de
representar um número, trata-se de uma string e não pode ser usado
diretamente em cálculos. Ao converter esse valor para inteiro, torna-se
possível somar com outro número.

24
O valor da variável x era uma cadeia ("25"), mas após a conversão para
inteiro a soma resulta em 30.

2.3 Constantes e boas práticas

Embora o Python não possua um mecanismo nativo para declarar constantes


de forma rígida, a comunidade adotou uma convenção sólida: escrever os
identificadores dessas variáveis inteiramente em MAIÚSCULAS. Esta
prática sinaliza claramente a intenção de que aquele valor deve ser tratado
como constante e não deve ser modificado em qualquer outro ponto do
código. Por exemplo:

Para além desta convenção específica para constantes, é fundamental adoptar


boas práticas de nomenclatura de uma forma geral. Utilizar nomes
descritivos e autoexplicativos para todas as variáveis, evitando abreviações
obscuras ou excessivas, é crucial. Um código legível e bem estruturado não
só facilita a compreensão e a manutenção por parte de outros programadores
(ou de si mesmo no futuro), como também reduz significativamente a
probabilidade de introdução de erros.

25
2.4 Comentários e Docstrings

A documentação é uma parte fundamental da programação, mesmo em


códigos mais simples. O Python oferece duas formas principais para
documentar o trabalho: os comentários e as docstrings, cada um com o seu
propósito específico.

2.4.1 Comentários Simples

Os comentários são notas ou explicações que o Python ignora


completamente. São feitos para programadores. Tudo o que vem após o
símbolo # numa linha é tratado como comentário.
Os comentários são úteis para explicar uma linha de código específica, o
porquê de uma decisão ou para "desligar" temporariamente um código sem
o apagar.
Segue um exemplo simples com comentários, sendo a última linha deste
exemplo a apresentação de um comando comentado, portanto, o
interpretador irá ignorá-lo.

2.4.2 Docstrings Básicas

Se os comentários são notas, as docstrings são os manuais de instruções. Elas


são strings (texto) delimitadas por três aspas (''') ou três aspas duplas
26
("""), que ficam no início de um ficheiro ou logo após a definição de uma
função.
Diferente dos comentários, as docstrings são preservadas pelo Python e
podem ser acedidas por ferramentas de ajuda.
São úteis para explicar de forma clara o que um bloco de código (como por
exemplo uma função ou um programa inteiro) faz.
.
O programa a seguir mostra o uso de docastring.

2.4.3 Quando Usar # e ´´´

Comentários com # e docstrings com """…""" servem propósitos


diferentes e complementares.
Use comentários (#) para explicar como ou porquê uma pequena parte do
código funciona. São pensamentos rápidos e específicos.
Use docstrings (""") para explicar o que um bloco maior de código (como
um programa ou função) faz. São uma descrição geral.

Por ser um erro comum, vale ressaltar que não se deve usar aspas triplas
como “comentário de várias linhas” no meio do código, pois isso não é
comentário, é uma string literal que o Python cria e descarta em tempo de

27
execução. Para ser considerada docstring deve estar na primeira linha da
definição.
Veja a seguir um exemplo prático simples, onde as primeiras linhas são
docstrings e não comentário.

Mesmo em programas simples, a documentação o ajuda e a outras pessoas a


melhor perceber o código mais tarde. É como deixar instruções para seu "eu
do futuro". Um código bem comentado é mais fácil de corrigir.

2.5 Bibliotecas

O Python já inclui diversas funcionalidades prontas na sua biblioteca padrão.


Para além disso, existem muitas bibliotecas externas criadas pela
comunidade, que podem ser instaladas e utilizadas em qualquer projeto.
Essas bibliotecas agrupam funções, classes e variáveis em módulos, que
podem ser importados sempre que necessário. A importação permite aceder
a funcionalidades adicionais sem a necessidade de escrever todo o código
manualmente.

28
2.5.1 Importação de biblioteca

Existem várias formas de importar módulos em Python:


a) Importação simples
Importa o módulo inteiro. Para aceder a qualquer função ou variável, é
preciso usar o nome do módulo como prefixo. A sintaxe é:
import nomeBiblioteca
Ao importar uma biblioteca, importa-se o módulo inteiro. Entretanto, para
aceder às suas funções, é necessário sempre usar o nome do módulo
como prefixo, como no exemplo a seguir, onde se importa a biblioteca
matemática e utiliza o módulo de cálculo da raiz quadrada de um número.

Esta é a forma mais segura e a recomendada para evitar conflitos de nomes,


especialmente em programas maiores.
A vantagem é que fica sempre explícito de onde veio a função, no caso
sqrt(), deixando o código mais legível e evitando que uma função sua
sobrescreva uma do módulo.

b) Importação específica
Na segunda forma de importação, utiliza-se a instrução from ...
import ....
Nesse caso, ao invés de trazer o módulo inteiro para o programa, importam-
se apenas partes específicas, como uma função, uma classe ou uma

29
constante. Esses elementos passam a ficar disponíveis diretamente no espaço
de nomes do programa, ou seja, podem ser usados sem necessidade de
escrever o prefixo do módulo antes.
Essa abordagem torna o código mais conciso e legível, sobretudo quando se
trata de funções ou classes que são usadas com frequência.
Por exemplo, ao importar apenas a função sqrt do módulo math, pode-se
calcular raízes quadradas escrevendo apenas sqrt(25) em vez de
[Link](25). Essa economia de digitação torna-se relevante em
programas maiores ou em situações onde uma função é chamada repetidas
vezes.
Contudo, deve-se ter cuidado, pois ao importar apenas partes específicas de
um módulo, perde-se o contexto explícito de onde cada função veio. Se no
mesmo programa existir outra função ou variável com o mesmo nome,
podem surgir conflitos. Por isso, o uso de from ... import ... deve
ser criterioso, reservado para casos em que as funções importadas são
realmente usadas muitas vezes e não há risco de ambiguidade.
Veja no exemplo a seguir, que importa duas funções de dois módulos
distintos.

30
Há uma variante comum desta importação que tem a seguinte sintaxe:
from ... import *
Nesse caso, todas as funções e variáveis do módulo passam a estar
disponíveis diretamente no programa, podendo ser usadas apenas pelo nome,
sem o prefixo do módulo. Apesar disso, esta prática deve ser evitada, porque
mistura os nomes do módulo com os já existentes, aumenta o risco de
conflitos e pode originar erros difíceis de identificar e corrigir.
Veja o exemplo deste tipo de importação não recomendada em códigos.

c) Importação com Alias


Esta forma de importação pode ser vista como um “melhor dos dois
mundos”, porque permite trazer para o programa um módulo inteiro ou
apenas uma função específica e, ao mesmo tempo, atribuir-lhe um codinome
mais curto ou mais descritivo. Esse recurso é especialmente útil em duas
situações: quando o nome original do módulo é demasiado longo e repetitivo
para ser escrito diversas vezes ao longo do código, ou quando já existe uma
convenção amplamente seguida pela comunidade de programadores para
utilizar um apelido padronizado.
A sintaxe geral é a seguinte:
from ... import ... as ...

31
O uso de codinomes facilita a escrita e torna o código mais limpo, sem
comprometer a legibilidade. Por exemplo, é muito comum encontrar o
módulo pandas importado como pd ou o [Link] como
plt. Esses apelidos já são reconhecidos e compreendidos pela maior parte
dos programadores Python, funcionando como uma espécie de linguagem
comum dentro da comunidade. Assim, o código mantém-se claro, compacto
e alinhado com as boas práticas mais difundidas.
Neste exemplo a seguir, o módulo pandas foi importado como pd,
seguindo a convenção adotada em praticamente todos os tutoriais e projetos.
Já a função sqrt recebeu o codinome raiz, tornando o código mais
descritivo e intuitivo.

2.5.2 Bibliotecas Mais Utilizadas

O Python já traz uma biblioteca padrão bastante extensa, que cobre tarefas
comuns como cálculos matemáticos, geração de números aleatórios,
manipulação de datas e interação com o sistema operativo. Para além disso,
existem também bibliotecas externas muito populares, criadas pela

32
comunidade, que expandem enormemente as capacidades da linguagem em
áreas como ciência de dados, estatística, inteligência artificial e visualização
gráfica.
A linguagem Python disponibiliza uma biblioteca padrão muito extensa, cuja
documentação oficial se encontra no site [Link]. Ali estão descritos
todos os módulos que acompanham a instalação da linguagem, incluindo
exemplos de utilização e explicações detalhadas. Para uma consulta rápida
de todos os nomes disponíveis existe ainda o Python Module Index, que
organiza em ordem alfabética todos os módulos integrados. Algumas destas
bibliotecas estão apresentadas na Tabela 1.
Tabela 1 - Bibliotecas Padrão do Python

Biblioteca Finalidade
math Funções matemáticas
random Geração de números aleatórios
datetime Manipulação de datas e horas
os Acesso ao sistema operativo e ficheiros
sys Informação e interação com o interpretador

Quando o projeto exige funcionalidades adicionais que não fazem parte da


biblioteca padrão, é possível recorrer a bibliotecas externas criadas pela
comunidade. O sítio oficial para essas bibliotecas é o PyPI – Python Package
Index, que funciona como um repositório central. Nesse portal podem ser
encontradas milhares de bibliotecas para áreas como ciência de dados,
inteligência artificial, desenvolvimento web, automação e muitas outras
aplicações. A Tabela 1 apresenta um exemplo de algumas destas bibliotecas
externas populares.

33
Tabela 2 - Bibliotecas Externas Populares do Python

Biblioteca Finalidade
numpy Operações numéricas e matrizes
pandas Análise e manipulação de dados em tabelas
matplotlib Criação de gráficos e visualizações
scikit-learn Algoritmos de aprendizagem automática (IA)
tensorflow Redes neuronais e deep learning

As bibliotecas da biblioteca padrão já vêm instaladas com o Python, prontas


para uso imediato. Já as bibliotecas externas disponíveis no PyPI precisam
de ser instaladas separadamente, geralmente através do comando
pip install nome_da_biblioteca.

2.6 Exercícios Propostos

1) Fazer um programa que tenha duas variáveis: a = 10 (inteiro) e b =


3.5 (float). Realize as seguintes operações e mostre o resultado e o
tipo de cada resultado:
a) a + b
b) a * b
c) a / b
DICA para mostrar o tipo: usar a função type() dentro do print()

2) Dadas as variáveis nome = "Paulo" e apelido = "Marques",


criar uma terceira variável nome_completo que concatene as duas
com um espaço entre elas. Mostrar o resultado.

34
DICA: Concatenar significa juntar textos (strings). Pode-se usar o
operador + para unir duas ou mais strings, lembrando de incluir espaços
ou outros separadores manualmente, quando necessário. Por exemplo:
“a”+ “ ” +”b” é “a b”

3) Considere que foi dada a variável numero_str = "15", faça um


programa para convertê-la para inteiro e somar 5. Em seguida, converter
o resultado para string e exibir a string final e o respetivo tipo.

4) Criar um programa com duas variáveis: x = 10 e y = 20. Exiba


True ou False se:
a) x é maior que y
b) x é diferente de y
c) x é igual a y / 2

5) Definir uma constante chamada TAXA_JUROS = 0.05 (em


maiúsculas). Calcular o juro de um valor valor = 1000 e armazene
em juro. Mostre o resultado.

35
3 OPERADORES E EXPRESSÕES

Os operadores são os símbolos que permitem ao programador realizar


operações específicas sobre valores (operandos) e variáveis, formando as
chamadas expressões. O domínio do uso de operadores é fundamental para
a implementação da lógica e do fluxo de um programa.
Em Python, os operadores são categorizados principalmente em:
Aritméticos, Relacionais (de Comparação), Lógicos e Especiais. Este
capítulo explora cada uma dessas categorias em detalhe, com exemplos
práticos e nuances importantes.

3.1 Operadores Aritméticos

Os operadores aritméticos são utilizados para executar operações


matemáticas convencionais entre valores numéricos, resultando em um novo
valor. Tais operadores estão apresentados na Tabela 3:

Tabela 3 - Operadores Aritméticos

Operador Nome Descrição


+ Adição Soma dois operandos
- Subtração Subtrai o operando direito do esquerdo
* Multiplicação Multiplica dois operandos
Divide o operando esquerdo pelo direito. Sempre
/ Divisão
retorna float
// Divisão inteira Divide e retorna apenas a parte inteira do resultado
% Módulo Retorna o resto da divisão do esquerdo pelo direito
Eleva o operando esquerdo à potência do operando
** Exponenciação
direito

36
O seguinte exemplo demonstra a utilização desses operadores em código:

Ao calcular a operação 7/3 em Python, o resultado exibido com um valor


terminado por …3335 em vez do valor matemático exato 2,333... (uma
dízima periódica infinita). Esta diferença ocorre porque o Python, que assim
como a maioria das linguagens de programação, utiliza uma representação
binária para números decimais, baseada no padrão IEEE 754 de ponto
flutuante.
Devido à representação finita de memória, o computador não pode
armazenar uma sequência infinita de decimais. E, em vez disso, recorre a
uma aproximação que, por vezes, introduz pequenos erros de
arredondamento, o que resulta em valores divergentes do real.
Este comportamento é esperado e denomina-se imprecisão de ponto
flutuante. É fundamental estar ciente desse fenómeno ao trabalhar com
operações que exigem precisão decimal absoluta, tal como em cálculos
financeiros ou científicos, situações em que se recomenda o uso de módulos
especializados, como decimal ou fractions.

37
3.1.1 Precedência de Operadores

A avaliação de expressões em Python segue as regras de precedência


matemática convencional. A ordem de precedência dos operadores, da maior
para a menor, é a seguinte:
1. Parênteses ( ) – são sempre avaliados primeiro, independentemente
dos operadores.
2. Exponenciação **
3. Operadores multiplicativos seguem a ordem:
− Multiplicação *
− Divisão /
− Divisão inteira //
− Módulo (resto da divisão) %
4. Operadores aditivos:
− Adição +
− Subtração -

Como regra prática, quando os operadores possuem a mesma precedência, a


avaliação é realizada da esquerda para a direita, com exceção da
exponenciação, que é avaliada da direita para a esquerda.
Para um exemplo simples, considere a ordem de avaliação da seguinte
expressão 10 + 3 * 2 ** 2:
1. 2 ** 2 → 4
2. 3 * 4 → 12
3. 10 + 12 → 22

38
O uso de parênteses pode alterar explicitamente a ordem de avaliação,
proporcionando clareza e controle sobre o cálculo.

3.1.2 Operadores de atribuição composta

Em Python, bem como noutras linguagens, existe uma forma abreviada de


atualizar o valor de uma variável com base no seu próprio valor.
Para melhor perceber este conceito, considere o comando:
variavel = variavel + 1
Este comando pode ser abreviado como variavel += 1
O operador += é denominado de atribuição composta e executa exatamente
a mesma operação, mas de forma mais curta e legível.
Da mesma forma, pode-se usar outros operadores, conforme pode ser
observado na Tabela 4.
Tabela 4 - Operadores de atribuição composta

Operador Equivalente longo Exemplo (x = 10) Resultado final


+= x=x+n x += 5 15
-= x=x-n x -= 3 7
*= x=x*n x *= 2 20
/= x=x/n x /= 4 2.5
//= x = x // n x //= 4 2
%= x=x%n x %= 3 1
**= x = x ** n x **= 2 100
&= x = x & n (E bit a bit) x &= 6 2
` =` `x = x n` (OU bit a bit)
^= x = x ^ n (XOR bit a bit) x ^= 6 12
<<= x = x << n (desloca bits) x <<= 1 20
>>= x = x >> n (desloca bits) x >>= 1 5

39
Os operadores de atribuição composta tornam o código mais curto, claro e
legível, evitando repetição desnecessária.

3.2 Operadores Relacionais

Estes operadores são utilizados para comparar dois valores e o resultado de


qualquer expressão que utilize operadores relacionais é sempre um valor
booleano: True (Verdadeiro) ou False (Falso).
São extremamente usados para controlar o fluxo de programas com
estruturas condicionais (if, elif e else) e loops.
Tais operadores estão apresentados na Tabela 5.
Tabela 5 - Operadores Relacionais

Operador Significado Exemplo Resultado


== Igual a 5 == 2 False
!= Diferente de 5 != 2 True
> Maior que 5 > 2 True
< Menor que 5 < 2 False
>= Maior ou igual 5 >= 2 True
<= Menor ou igual 5 <= 2 False

3.2.1 Comparações Encadeadas em Python

Diferente de muitas outras linguagens de programação, Python permite que


você encadeie operadores de comparação de forma natural e intuitiva, assim
como faria em uma expressão matemática.

40
Este conceito permite que uma expressão do tipo a < b < c seja avaliada
pelo Python como (a < b) and (b < c). A expressão só retornará
True se todas as comparações individuais forem verdadeiras.
A seguir é apresentado um exemplo para verificar se um número está em um
intervalo e exibir a resposta.

É possível também fazer múltiplas comparações, como no exemplo a seguir:

Vale ressaltar que apesar dos operadores utilizados nos exemplos


apresentados anteriormente terem sido sempre iguais, é possível haver a
comparação com operadores diferentes, por exemplo:

O exemplo a seguir verifica a ordem alfabética entre strings, demonstrando


como comparar sequências de caracteres.

41
Dentre as vantagens das comparações encadeadas, exemplificadas
anteriormente, pode-se citar:
− Mais legível: A expressão se assemelha à notação matemática
convencional
− Mais concisa: Evita a repetição de variáveis e operadores lógicos
− Mais elegante: O código fica mais limpo e fácil de entender
− Mais eficiente: Em alguns casos, o Python pode otimizar a avaliação.

Mas tenha cuidado, pois embora seja poderoso, o uso deste recurso deve ser
de maneira controlada. Cadeias muito longas podem dificultar a leitura,
como no exemplo a seguir, que apesar de correcto, está confuso.

Esse recurso do Python torna o código mais expressivo e matemático, sendo


particularmente útil para verificar se valores estão dentro de intervalos
específicos.

42
3.3 Operadores Lógicos

Os operadores lógicos são usados para combinar ou modificar expressões


condicionais booleanas, formando expressões lógicas mais complexas. Tais
operadores estão a ser apresentados na Tabela 6:
Tabela 6 - Operadores Booleanos

Operador Descrição Exemplo


E Lógico: Retorna True apenas se ambos
and True and False
os operandos forem True
OU Lógico: Retorna True se pelo
or True or False
menos um dos operandos for True

not NÃO Lógico: Inverte o estado lógico do not True


operando

Como exemplo, observe o seguinte código.

3.3.1 Avaliação de Curto-Circuito (Short-Circuit Evaluation).

A ideia de “Curto-Circuito (Short-Circuit)”, comum nas linguagens de


programação modernas, também existe em Python.
Isso significa que a avaliação das expressões com os operadores and e or é
interrompida assim que o resultado final é determinado.
Para o caso do and, se o primeiro operando for False, o segundo já não é
avaliado. No caso do operador or, se o primeiro termo for True, o segundo
já é ignorado.

43
Diz-se que Python é "preguiçoso" (lazy) na avaliação, pois apenas avalia o
mínimo necessário para obter o resultado final. A principal vantagem é
eficiência e economia de processamento, pois evita cálculos desnecessários.
Para além, a prevenção de erros, caso o segundo operando envolva uma
operação inválida ou pesada, conforme no exemplo apresentado a seguir, que
ocasionaria um erro caso não fosse esta ideia.

3.4 Operadores de Identidade

Os operadores de identidade (is e is not) verificam se duas variáveis


apontam para exatamente o mesmo objeto na memória, não se os valores são
iguais, e estão apresentados na Tabela 7.
Tabela 7 - Operadores de Identidade

Operador Descrição Exemplo


is Verdadeiro se as variáveis são o mesmo objeto. x is y
is not Verdadeiro se as variáveis não são o mesmo objeto. x is not y

O exemplo seguinte ajuda a compreender melhor este comportamento.

44
3.4.1 Diferença entre os operadores == e is

Há uma diferença crucial entre == e is, pois embora possam parecer


similares em alguns casos, estes operadores possuem propósitos
fundamentalmente distintos:
− == (Igualdade de Valor): Verifica se os conteúdos ou valores dos objetos
são equivalentes, independentemente de onde estão armazenados na
memória.
− is (Identidade do Objeto): Verifica se duas variáveis referenciam
exatamente o mesmo objeto na memória, ou seja, se apontam para o
mesmo endereço.
Esta distinção é essencial para evitar bugs sutis e escrever código Python
idiomático e correto. A confusão entre esses operadores é uma das fontes
mais comuns de erros para programadores iniciantes em Python.
A Tabela 8 apresenta de forma resumida as situações em que se deve utilizar
cada um destes operadores.
Tabela 8 - Uso dos operadores == e is

Operador Usar quando…


Comparar valores numéricos
== Verificar se strings têm o mesmo conteúdo
(igualdade de
valores) Verificar se listas ou dicionários têm os mesmos elementos
Comparar o conteúdo de objetos personalizados
Verificar se uma variável é None
is Verificar se duas variáveis referem ao mesmo objeto
(identidade de
objeto) Trabalhar com singletons (como None, True, False)
Comparar a identidade de objetos personalizados

Há uma regra prática de uso: utilize is para identidade e == para igualdade.

45
3.5 Operadores de Associação

Os operadores de associação (in e not in) são usados para testar se um


valor está ou não presente numa sequência, como uma string, lista, tuplo ou
dicionário (onde testa as chaves). Tais conceitos serão explicados no capítulo
7, mas estão apresentados na Tabela 9.
Tabela 9 - Operadores de Associação

Operador Descrição Exemplo Resultado


Verdadeiro se o valor é "Py" in
is True
encontrado na sequência "Python"
Verdadeiro se o valor não é "Java" not in
is not True
encontrado na sequência "Python"

Considere o exemplo a seguir para melhor perceberes o conceito. Não se


preocupe, por enquanto com o conceito de lista, pois este será apresentado
posteriormente, no Capítulo 7.

3.6 Exercícios

1) Precedência de Operadores: Faça um programa em Python para calcular


o resultado da expressão: 10 + 3 * 2 ** 2 / 4 - 1. Explique a
ordem das operações.

46
2) Dadas as variáveis a = 5, b = 10 e c = 15, faça um programa
para verificar se:
a) a < b < c
b) a + b == c
c) c % a != 0

3) Dadas as variáveis idade = 18 e tem_cartao = True, faça um


programa que represente as afirmações a seguir e exiba o resultado:
a) A idade é maior ou igual a 18 E tem cartão
b) A idade é menor que 18 OU não tem cartão

4) Crie duas variáveis desta forma, num programa:


lista1 = [“cpu”, “pc”, “ram”] e
lista2 = [“cpu”, “pc”, “ram”]

Verifique a resposta e explique a diferença:


a) Se lista1 == lista2
b) Se lista1 is lista2

5) Dada a string fruta = "banana", faça um programa que verifique:


a) Se "a" está em fruta
b) Se "maçã" não está em fruta
c) Se "nana" é uma substring de fruta

47
4 ENTRADA E SAÍDA DE DADOS

Um programa de computador torna-se verdadeiramente útil quando


consegue interagir com o utilizador. Esta interação baseia-se em dois fluxos
principais:
− Entrada de dados: o programa solicita e recebe informações do
utilizador, normalmente através do teclado
− Saída de dados: o programa apresenta resultados ou mensagens ao
utilizador, geralmente no ecrã
Em Python, estas operações realizam-se com duas funções fundamentais:
input() e print().

4.1 Função input() para entrada de dados

A função input() é utilizada para ler dados introduzidos pelo utilizador


através do teclado.
Sempre que esta função é executada, o programa pausa a sua execução e
aguarda que o utilizador introduza uma resposta e prima a tecla Enter. A
sintaxe básica é a seguinte:

variavel = input("Mensagem para o utilizador: ")

O texto colocado entre aspas (simples ou duplas) será exibido no ecrã e


servirá como instrução ou pergunta para o utilizador.
O programa ficará em pausa até que o utilizador escreva uma resposta e
pressione a tecla Enter.
48
É necessário ter atenção, pois o valor introduzido será sempre armazenado
na variável como uma string (texto), mesmo quando se trate apenas de dígitos
numéricos.
Veja a seguir, dois simples exemplos práticos.

Mais uma vez é importante chamar a atenção de que a idade é armazenada


como a string "18", e não como o número 18.

4.2 Conversão de tipos de dados

Como input() devolve sempre uma string, é necessário converter


explicitamente o valor quando se pretende trabalhar com outros tipos de
dados:
a) Conversão para inteiro
Para ler um número inteiro, deve-se usar a função int() aplicada ao
resultado de input(), como no exemplo a seguir.

49
b) Conversão para decimal:

Se o utilizador digitar 10, a variável quadrado passa a conter o quadrado


de 10, mas como é float, dentro da variável quadrado tem-se 100.0.
É importante perceber que se o utilizador introduzir um valor que não possa
ser convertido (por exemplo, se o utilizador escrever "dez" quando se espera
um número), o programa terminará com um erro. Mais à frente, será
apresentada técnicas para lidar com estas situações excecionais.

c) Conversão para booleano (bool)


A função bool() converte qualquer string não vazia em True. Para obter
valores booleanos de forma útil, combinar input() com uma condição:

50
Se o utilizador escrever S, a variável continuar armazena o valor True.
Em qualquer outro caso, armazena o valor False.

4.3 Função print() para saída de dados

A função print() é utilizada para mostrar informação ao utilizador. É uma


das funções mais utilizadas em Python, permitindo verificar resultados,
apresentar informações e acompanhar o fluxo de execução do programa.
A sua sintaxe básica está apresentada a seguir.

print(objeto1, objeto2, ..., objetoN)

Como pode-se observar na sintaxe, a função print() pode receber um ou


vários argumentos (objetos) e, por predefinição, os argumentos são
separados por um espaço. E, para além, cada chamada a print() termina
com uma mudança de linha. Veja os exemplos a seguir.

Para compreender como funciona a quebra de linha, veja o exemplo seguinte,


que utiliza dois print() com comportamentos distintos.
É importante saber que cada chamada à função print() termina com uma
nova linha e, a seguir, há o uso explícito do carácter especial \n, que força a
quebra de linha dentro da própria string.

51
4.4 Personalização da saída com print()

A função print() oferece parâmetros opcionais que permitem


personalizar significativamente a formatação da saída. Neste tópico
apresentam-se duas das personalizações mais comuns, o sep e o end.

a) Controlo do separador (sep)


Por predefinição, os elementos são separados por um espaço. Este
comportamento pode ser alterado com o argumento sep:

b) Controlo do terminador de linha (end)


Por predefinição, print() termina com um caractere de nova linha (\n),
conforme visto no exemplo anterior. Entretanto pode-se alterar este
comportamento com o argumento end, conforme apresentado no exemplo a
seguir.

52
4.5 Técnicas de formatação de strings

Para criar mensagens de saída dinâmicas que incorporam valores de


variáveis, existem várias técnicas em Python, cada uma com as suas
vantagens.

4.5.1 Concatenação com vírgulas e com o operador +

a) Com vírgulas
A forma mais simples de combinar múltiplos valores numa só mensagem é
separá-los por vírgulas na função print(). O Python trata
automaticamente da conversão para string e insere um espaço entre cada
elemento. A vantagem é não ser necessário converter outros tipos de dados
para string manualmente, como no exemplo seguinte que usa a vírgula.

b) Com o operador +
O operador de concatenação + une strings de forma direta. No entanto, requer
mais atenção:

53
− Todos os elementos devem ser strings. Números e outros tipos precisam
de ser, obrigatoriamente, convertidos com str()
− Os espaços entre palavras devem ser incluídos explicitamente nas strings
A grande desvantagem é que o código é mais verboso e propenso a erro se
alguma conversão for esquecida.

É extremamente importante perceber que os espaços no início e fim das


strings são cruciais.

4.5.2 Método [Link]()

Este é um método mais flexível e robusto que utiliza marcadores de posição


{} dentro de uma string principal. Os valores a inserir são passados como
argumentos para o método .format(). Dentre as vantagens, cita-se:
− Separa-se claramente o texto dos dados
− Permite-se a reutilização e reordenação dos valores
− Oferece opções avançadas de formatação, como o número de casas
decimais e alinhamento.

54
No exemplo seguinte utilizam-se índices para indicar a posição dos valores.
Esta forma de exibição torna o código mais flexível, permitindo inclusive
reutilizar os mesmos valores em diferentes partes da frase.

A formatação de casas decimais com .format() usa a seguinte sintaxe


dentro dos marcadores de posição: índice:formato}
"{0:.nf}".format(valor), onde:
− n - Número de casas decimais desejadas
− f - Indicação de número de vírgula flutuante (float).
A seguir apresenta-se um exemplo com este tipo de formatação.

4.5.3 f-strings (Formatted String Literals)

Este é o método mais moderno, legível e eficiente. Basta prefixar a string


com f ou F e colocar as expressões diretamente dentro de {}.

55
Dentre as vantagens, cita-se:
− O código torna-se muito mais legível, pois os valores são inseridos
diretamente no local onde serão exibidos
− Permitem executar operações e funções diretamente dentro das chavetas
{}, sem necessidade de cálculos prévios
− São mais eficientes em termos de velocidade de execução
comparativamente a outros métodos de formatação.

As f-strings não servem apenas para mostrar valores diretamente. Elas


também permitem formatar como esses valores são apresentados, tornando a
saída mais limpa e profissional.

a) Controlo de Casas Decimais


Muitas vezes, os cálculos resultam em números com muitas casas decimais.
Tal como o .format(), pode-se usar f-string para definir quantas
casas decimais mostrar. Considere o exemplo a seguir.

56
A expressão :.2f dentro das chavetas significa:
− O símbolo : inicia a formatação
− O símbolo .2 define o número de casas decimais
− A letra f indica que é um número de vírgula flutuante (float).

b) Formatação de Percentagens
É também comum mostrar um valor decimal como uma percentagem. As f-
strings tornam isto muito simples. A seguir há um exemplo pertinente.

A expressão :.1% pode ser assim percebida:


− O símbolo : inicia a formatação
− O .1 define o número de casas decimais
− O % multiplica o valor por 100 e insere o símbolo de percentagem.

c) Alinhamento de Texto
É possível controlar a posição do texto dentro de um espaço definido, ou seja,
controlar o alinhamento do texto ao formatar strings. Para alinhar o conteúdo
à direita, à esquerda, ao centro ou definindo ainda o número total de espaços
a ocupar, conforme o exemplo a seguir.

57
d) Preencher com Caracteres
Preenche o espaço extra com um carácter específico e, neste exemplo,
também é possível definir este preenchimento.
Veja este exemplo para ilustrar esta forma de exibir.

A expressão {nome:*^10} funciona assim:


− Começa por reservar um espaço de 10 caracteres para mostrar o texto
− No centro desse espaço, é colocada a string "Ana", que tem 3 caracteres
e preenche os outros 7 espaços com o símbolo * (10 - 3 = 7).
o Como o preenchimento é centralizado (^), estes 7 caracteres * são
divididos da forma mais igualitária possível entre os dois lados, ou
seja, 3 asteriscos à esquerda, 3 à direita e fica 1 asterisco restante
o A regra é que a metade "quebrada" (0.5) vai para a direita
o Logo o resultado é ***Ana**** (3 * à esquerda e 4 à direita).

58
e) Formatação de Números Inteiros
Na formatação de números inteiros, é possível adicionar separadores de
milhares para facilitar a leitura. O Python permite escolher diferentes
símbolos, como a vírgula ou o sublinhado. Veja o exemplo a seguir.

f) Formatação de Datas
Na formatação de datas, utilizam-se diretivas específicas que permitem
exibir o dia, o mês, o ano ou a hora em diferentes formatos. A seguir é
apresentado um exemplo.

g) Sistemas Numéricos
O Python permite exibir números em diferentes sistemas de numeração,
como hexadecimal, octal e binário, através de especificadores de formato,
conforme o exemplo.

59
h) Combinar Várias Formatações
É possível combinar diferentes opções de formatação num mesmo valor.
Assim, é possível definir separadores de milhares, o número de casas
decimais e o tipo de dado, tornando a apresentação mais clara e profissional,
como este exemplo.

Onde a formatação é a seguinte:


:  Inicia a especificação de formatação
,  Adiciona separador de milhares (vírgula para milhares)
.2  Define 2 casas decimais após o ponto
f  Indica que é um número de ponto flutuante (float)

Estas opções permitem controlar precisamente a apresentação dos dados,


tornando a saída do programa mais profissional e legível.

60
i) Formatação de Números Inteiros
O especificador d indica que o valor deve ser tratado como inteiro decimal.
É útil para controlar o tamanho mínimo do campo, preenchendo com zeros
ou espaços à esquerda.
Neste exemplo a seguir, dentro do print() se apresentam as seguintes
formatações:
− 05d → reserva 5 posições, completa com zeros e mostra como
inteiro decimal
− 8d → reserva 8 posições, completa com espaços e mostra como
inteiro decimal.

j) Formatação de Números Decimais (float)


O especificador f força a exibição como número de vírgula flutuante,
permitindo definir quantas casas decimais serão apresentadas.
Neste exemplo a seguir, dentro do comando print(), tem-se:
− .2f → mostra 2 casas decimais
− 8.4f → reserva 8 posições no total e mostra 4 casas decimais.

61
k) Formatação de Strings
O especificador s garante que o valor seja tratado como texto. É
especialmente útil quando se precisa alinhar ou limitar o tamanho da string.
Exemplo da formatação de strings com os seguintes códigos no print():
− .3s → limita a string a 3 caracteres
− 10s → reserva 10 posições para a string, completando o espaço com
espaços em branco.

4.6 Exercícios propostos

1. Escrever um programa que solicite o nome e a cidade do utilizador e


mostre no ecrã:
"Olá [nome]! É um prazer saber que vens de [cidade].
Bem-vindo/a!"

2. Converter horas em minutos e segundos e, para isso, ler um número de


horas e mostrar o equivalente em minutos e segundos. Exemplo:
2.5 horas = 150 minutos = 9000 segundos

62
3. Simular um recibo simples. Ler o nome de um produto, preço unitário e
quantidade. Calcular e mostrar o total a pagar com IVA (23%),
formatado como moeda. Exemplo
Produto: Livro
Total: 12.50 €
IVA (23%): 2.88 €
Total com IVA: 15.38 €

4. Ler um número de 6 dígitos e formate-o de várias formas:


o Com separadores de milhar: 123456 → 123,456
o Como valor monetário: 123456 → 123456.00 €
o Alinhado à direita com 10 caracteres e preenchimento com
zeros: 000123456
o Em hexadecimal: 1e240

5. Implementar um programa que leia três palavras do usuário e as exiba


formatadas em uma única linha, separadas por hífen. Exemplo de uso do
programa:

6. Ler o número de respostas corretas e o total de perguntas de um teste.


Mostrar a percentagem de acertos com uma casa decimal. Exemplo de
saída:
Acertaste 7 de 10 questões (70.0%)

63
5 ESTRUTURAS DE CONTROLO

As estruturas de controlo constituem um pilar fundamental da programação,


e permitem conduzir o fluxo de execução de um programa. Através delas, os
programas transcendem a mera execução sequencial de instruções, ganhando
a capacidade de tomar decisões (execução condicional) e de repetir
operações (execução iterativa), tornando-se assim dinâmicos, flexíveis e
capazes de resolver problemas complexos. Em Python, como na maioria das
linguagens de programação, estas estruturas dividem-se em dois grandes
grupos: condicionais e ciclos.

5.1 Estruturas condicionais (if, elif, else)

As estruturas condicionais, também conhecidas como estruturas de seleção,


permitem que um programa avalie condições e execute diferentes blocos de
código consoante o resultado dessa avaliação seja verdadeiro (True) ou
falso (False). A execução do programa ramifica-se consoante a lógica
definida.

5.1.1 Conceito fundamental em Python: indentação

Antes de explorar as estruturas de controlo, é essencial compreender um


aspeto único e fundamental da sintaxe Python: a indentação (ou endentação).
Ao contrário de muitas outras linguagens de programação que utilizam
chavetas {} ou palavras-chave como begin/end para delimitar blocos de
código, o Python utiliza espaços em branco para definir a estrutura do

64
programa. Isto não é apenas uma mera convenção de estilo, mas sim uma
regra sintática obrigatória.
A indentação refere-se aos espaços em branco no início de uma linha de
código. Em Python, ela define a hierarquia e o agrupamento lógico das
instruções. As regras básicas são:
− Blocos são definidos por indentação: Todas as linhas de um mesmo
bloco de código devem ter exatamente o mesmo nível de indentação
− O padrão recomendado é 4 espaços: Embora se possa usar tabs ou outro
número de espaços, a PEP 8 (guia de estilo oficial do Python),
recomenda 4 espaços por nível de indentação
− Misturar tabs e espaços não é permitido: Escolha um método e use-o
consistentemente em todo o projeto
− A mudança de indentação marca o início/fim de blocos: Um aumento na
indentação inicia um novo bloco e uma diminuição encerra o bloco atual.

Em resumo, a indentação é o que define a estrutura de um programa em


Python. Como seleções (if, else, elif) e ciclos (for, while) são
blocos de código, todos dependem obrigatoriamente de uma indentação
correta. Qualquer erro nesse aspeto resultará em falhas de execução, pelo que
dominar a indentação é o primeiro passo para compreender o funcionamento
das estruturas de controlo.

65
5.1.2 Estrutura if

No núcleo das estruturas condicionais encontra-se a avaliação booleana, isto


é, o Python avalia a expressão após a palavra-chave if. Se o resultado for
True, o bloco de código indentado sob o if é executado. Qualquer valor
pode ser avaliado num contexto booleano. Valores como 0, None,
sequências vazias ([], " ", ()) são considerados False. Quase todo
o resto é considerado True.

Esta é a forma mais simples de controlo condicional, pois executa um bloco


de código somente se uma condição for verdadeira. A sintaxe básica do if
está a ser apresentada a seguir. Observe que após a condição há o símbolo :
e, para além, todas as instruções estão indentadas.

Veja a seguir a importância da indentação num exemplo. A condição


associada idade >= 18 é avaliada. Se for True, o bloco print()
indentado será executado. Se for False, o interpretador ignora o bloco e
continua a execução do programa

66
5.1.3 Estrutura if-else

Estende a estrutura if para fornecer um caminho alternativo de execução


quando a condição é falsa (False). É uma estrutura de seleção binária. A
sintaxe está apresentada a seguir.

Neste tipo de estrutura condicional, o programa nunca segue os dois ramos


ao mesmo tempo. Se a condição for verdadeira, executa o bloco associado
ao if. Caso contrário, executa o bloco do else.
O else não tem condição associada, pois representa sempre o caminho
alternativo quando a condição inicial não é satisfeita.
Assim, o fluxo segue sempre um caminho ou outro, mas nunca os dois, como
pode ser observado no exemplo a seguir:

67
5.1.4 Estrutura if-elif-else

Permite verificar múltiplas condições de forma sequencial. elif é uma


contração de "else if". O interpretador testa cada condição por ordem
até encontrar uma que seja verdadeira e executa o seu bloco. Se nenhuma for
verdadeira, executa o bloco else (se existir).
Esta é a sintaxe deste comando:

Vale ressaltar que o primeiro if avalia a condição inicial. Cada elif


introduz uma nova condição a ser testada, mas só é verificada se todas as
anteriores forem falsas. Já o else não tem condição associada e somente
será executado se nenhuma das condições anteriores for satisfeita.
Assim, o programa segue um único caminho entre os vários disponíveis,
conforme pode ser observado no exemplo a seguir.

68
5.1.5 Operador Ternário

O operador ternário é uma forma concisa de escrever estruturas condicionais


simples em uma única linha. É útil quando se precisa atribuir um valor ou
executar uma expressão simples baseada em uma condição.
A sintaxe do operador ternário é:
valor_se_verdadeiro if condição else valor_se_falso

Para ilustrar de forma clara o funcionamento deste operador, a seguir


apresenta-se uma comparação entre uma estrutura convencional if-else
e a sua forma equivalente utilizando o operador ternário.

69
Considere o exemplo seguinte, que verifica se um número é maior ou igual
a zero e, nesse caso, o considera como positivo.

O uso do operador ternário ocorre quando há uma condição simples, com


ações simples (atribuições ou expressões básicas) e para tornar o código mais
compacto e legível.
Evite aninhar operadores ternários, pois isso torna o código difícil de ler,
como no exemplo a seguir, que apesar de funcionar, é de difícil
entendimento.

Uma boa prática é preferir clareza à brevidade, ou seja, se o operador ternário


tornar o código confuso, use if-else tradicional.

70
5.1.6 Condições múltiplas com operadores lógicos

É possível, e por vezes necessário, formar condições mais complexas e


precisas combinando expressões booleanas usando os operadores lógicos
and, or e not.
Apesar de já ter sido anteriormente apresentado, vale relembrar que:
− and: Será True apenas se ambas as expressões forem verdadeiras
− or: Será True se pelo menos uma das expressões for verdadeira
− not: Inverte o valor booleano da expressão.

Os três exemplos a seguir demonstram essas situações na prática. Note o uso


do método .lower() para garantir a conversão da string para minúsculas
e uniformizar a comparação.

71
5.2 Ciclos (for, while)

Os ciclos, laços, loops ou estruturas de repetição, são utilizados para executar


um bloco de código repetidamente, o que é essencial para processar coleções
de dados ou repetir ações até que uma condição seja satisfeita.
Cada repetição é chamada de iteração. Os ciclos for são tipicamente usados
para iteração definida, ou seja, quando se sabe quantas vezes vamos iterar.
Já os ciclos while são usados para iteração indefinida, ou seja, itera-se até
uma condição mudar.

5.2.1 Ciclo for

O ciclo for em Python é utilizado para percorrer os elementos de qualquer


iterável, tais como strings, listas, tuplas, dicionários ou sets. A cada iteração,
uma variável assume o valor do próximo elemento na sequência e possui a
seguinte sintaxe:

Tal como no bloco das estruturas de seleção, faz-se obrigatório o uso da


indentação para delimitar o bloco.

72
A seguir há exemplos pertinentes ao comando for. Não se preocupe ainda
com o conceito de lista e dicionário, apenas tente perceber o funcionamento
do comando for.

5.2.2 Função range()

A função range() é a forma mais comum de indicar quantas vezes um


ciclo for deve repetir.
Tal função gera uma sequência de números inteiros imutável (não pode ser
alterada) e pode ser utilizada de diferentes maneiras:
− range(fim) Gera números de 0 até fim-1
− range(inicio, fim) Gera números de inicio até fim-1
− range(inicio, fim, passo)  Gera números de inicio até
fim-1, incrementando por passo.

73
Para melhor perceber o funcionamento há um exemplo prático para cada
sintaxe, logo a seguir.

Por vezes, o valor da variável do ciclo não interessa, apenas o facto de a


repetição acontecer. Nesses casos, usa-se o sublinhado (_) para deixar claro
que a variável de controlo não será utilizada. O exemplo a seguir mostra isso
ao simular uma contagem de preparação antes da descolagem de um
foguetão.

74
Neste exemplo o ciclo repete a frase � Motor ligado! cinco vezes,
mas o valor do contador não é usado, por isso é substituído por _.
A função [Link](1) faz o programa pausar durante 1 segundo antes
de continuar para a próxima iteração, criando o efeito de espera realista até
o momento da descolagem.

5.2.3 Ciclo while

O ciclo while executa um bloco de código enquanto uma determinada


condição booleana se mantiver verdadeira. É crucial que, dentro do bloco,
algo altere o estado do programa para que a condição eventualmente se torne
falsa; caso contrário, o ciclo executar-se-á infinitamente (ciclo infinito).
A sintaxe do comando while é:

Considere o seguinte exemplo, onde o ciclo continua enquanto a condição


contador < 5 for True. A linha contador += 1 é crítica para
modificar a condição.

75
5.2.4 Controlo de ciclos: break e continue

As palavras-chave break e continue permitem um controlo mais fino


sobre a execução dos ciclos, estando quase sempre associadas a estruturas
condicionais (if, elif) dentro do ciclo. Estes comandos permitem alterar o
comportamento padrão da iteração com base em condições específicas.
− break: Termina imediatamente o ciclo em que se encontra, saltando
para a primeira instrução após o ciclo. É tipicamente usado dentro de um
bloco if quando uma condição de saída é satisfeita, interrompendo a
repetição
− continue: Salta imediatamente para a próxima iteração do ciclo,
ignorando qualquer código que reste no bloco da iteração atual. É
tipicamente usado dentro de um bloco if para ignorar elementos
específicos.

Nos exemplos a seguir, que ilustram o uso do break e continue,


considere sempre que o print exibe um valor em cada linha, mesmo que
aqui a saída esteja apresentada de forma resumida numa única linha.

Exemplo 1: O comando break permite interromper um ciclo antes do fim.


Neste caso, o ciclo for deveria ir de 0 a 9, mas a condição faz com que seja
interrompido assim que i atinge o valor 5:

76
Exemplo 2: Já o comando continue faz o oposto, ou seja, não termina o
ciclo, apenas salta a iteração atual e segue para a seguinte. Neste exemplo,
quando i vale 2, o print(i) é ignorado e o ciclo continua normalmente.

Exemplo 3: O uso de break e continue torna-se ainda mais claro em


situações práticas. Aqui neste exemplo, ao processar uma lista até encontrar
um valor inválido, se interrompe o ciclo ao surgir um número negativo.

Exemplo 4: O uso do continue pode ser para filtrar elementos de


interesse, como neste exemplo onde se quer apenas processar números pares,

77
5.3 Estruturas de Controlo Aninhadas

Uma das capacidades mais poderosas da programação é a capacidade de


aninhar estruturas de controlo, ou seja, colocar uma estrutura dentro de outra.
Isto permite criar lógicas complexas e sofisticadas a partir de blocos básicos.

5.3.1 Ciclos Dentro de Ciclos

Pode-se colocar um ciclo dentro de outro ciclo, criando estruturas iterativas


complexas. Isto é particularmente útil para trabalhar com padrões, tabelas,
ou qualquer estrutura bidimensional.
O exemplo a seguir simula um relógio digital em formato de 24 horas. O
código usa dois ciclos aninhados: o primeiro percorre todas as horas do dia,
de 0 a 23, e o segundo percorre os minutos de cada hora, de 0 a 59. A
cada combinação de hora e minuto é exibido um horário no formato hh:mm,
sempre com dois dígitos, como 07:05. Assim, o programa gera todos os
horários de um dia completo, desde 00:00 até 23:59.

Um outro exemplo consiste em gerar a tabuada completa de multiplicação,


exibindo no ecrã todos os resultados das combinações de 1 a 10. Para isso,
utilizam-se ciclos for aninhados.

78
A saída do programa segue a formatação mostrada abaixo, mas apenas a
tabuada do 1 é apresentada como exemplo. As restantes tabuadas são
exibidas em sequência, mas foram omitidas para simplificar a amostra.

5.4 Estruturas Condicionais Dentro de Ciclos

É bastante comum combinar condições com ciclos para que o programa tome
decisões a cada iteração. Essa prática permite filtrar valores, aplicar regras
específicas e resolver problemas mais elaborados.

79
Um exemplo clássico é a identificação de múltiplos comuns. Dentro de um
mesmo intervalo, pode-se verificar quais números satisfazem duas condições
ao mesmo tempo.
No exemplo a seguir, o programa percorre os números de 1 a 50 e seleciona
apenas aqueles que são divisíveis tanto por 3 como por 5, exibindo-os como
resultado.

A saída deste programa é:

5.5 Ciclos Dentro de Estruturas Condicionais

As estruturas condicionais e os ciclos podem ser combinados para tornar o


programa mais flexível.
Ao colocar um ciclo dentro de um bloco if, garante-se que a repetição só
acontece quando a condição for satisfeita.
Isso é útil em situações em que nem sempre se deseja executar um ciclo,
como por exemplo pedir autorização ao utilizador antes de iniciar uma
contagem regressiva para o Ano Novo, conforme o exemplo a seguir.

80
Uma possível saída (a considerar que o utilizador irá digitar s) está a ser
apresentada a seguir.

É importante destacar que, neste código, apenas a resposta s (minúscula) é


considerada válida para executar a contagem regressiva. Se o utilizador
digitar qualquer outro carácter, incluindo n ou outra letra qualquer, o
programa seguirá diretamente para o bloco do else, exibindo a mensagem
Não há contagem regressiva.
Isto significa que a indicação (s/n) no pedido de entrada é apenas uma
convenção para o utilizador e não uma validação real de que só s ou n foram
digitados. O programa não impede que seja introduzido outro valor qualquer.

81
Uma versão melhorada deste código, a garantir que se aceite apenas s/n é
apresentada a seguir.

5.6 Exemplo Prático de estruturas aninhadas

Antes de apresentar o exemplo, vale ressaltar que o aninhamento de


estruturas de controlo segue as mesmas regras de indentação aprendidas
anteriormente. Cada nível de aninhamento requer um novo nível de
indentação, mantendo a estrutura visual clara e lógica do código.
Este exemplo é um pouco mais complexo, pois combina estruturas aninhadas
(um if dentro de outro) e utiliza também um ciclo while com limite de
tentativas. O programa simula um caixa eletrónico e pede ao utilizador que
introduza o PIN, valida se o formato está correto (4 dígitos) e compara com
o PIN guardado no sistema.
Se o PIN for válido, mostra o saldo disponível; caso contrário, diminui o
número de tentativas restantes. Vale ressaltar que o método isdigit()

82
permite verificar se uma string contém apenas dígitos numéricos, portanto
sendo útil para validar se o PIN foi inserido corretamente.
Após três erros, o cartão é bloqueado e o acesso é negado.

No exemplo anterior foi utilizado o método isdigit() para garantir que o PIN
continha apenas dígitos numéricos. Esse é apenas um entre vários métodos
disponíveis para validar e manipular strings em Python. Para facilitar a
consulta, apresenta-se a seguir a Tabela 10, que reúne alguns dos métodos
mais utilizados no dia a dia para esse tipo de verificação.

83
Tabela 10 - Métodos úteis de validação em strings

Método Verifica se Exemplo


isdigit() São dígitos "1234".isdigit()
isalpha() São letras "Python".isalpha()

isalnum() Há apenas letras e "abc123".isalnum()


números (sem espaços)
isspace() São espaços em branco " ".isspace()
islower() São minúsculos "python".islower()
isupper() São maiúsculos "PYTHON".isupper()

startswith(x) Começa com o valor "aula".startswith("au")


especificado

endswith(x) Termina com o valor "[Link]".endswith(".txt")


especificado

É importante destacar que em Python, como em praticamente todas as


linguagens de programação, pode-se combinar estruturas de controlo de
várias formas.
Pode haver um while dentro de outro while, um for dentro de outro for
ou até mesmo misturar ciclos diferentes.
Da mesma forma, as instruções condicionais (if, elif, else) podem
estar aninhadas, isto é, colocadas umas dentro das outras para tratar casos
mais específicos.
Essas combinações permitem criar programas mais complexos e poderosos,
capazes de simular situações reais, como o exemplo do caixa eletrónico. No
entanto, quanto mais níveis de aninhamento existir, maior a necessidade de
organizar bem o código e usar indentação correta, para manter a clareza e
facilitar a leitura.

84
Em resumo: as estruturas de controlo podem ser usadas isoladamente ou
combinadas e é essa flexibilidade que dá ao programador o poder de resolver
problemas de diferentes níveis de complexidade.

5.7 Geração de Números Aleatórios com o módulo random

Ao trabalhar com ciclos, pode-se considerar útil, e até necessário em certos


casos, gerar valores aleatórios.
A geração de números aleatórios é uma funcionalidade essencial em
programação, com aplicações que vão desde jogos e simulações até
criptografia e testes de software. Em Python, esta funcionalidade é fornecida
pelo módulo random.
Para usar funções de aleatoriedade, primeiro é necessário importar o módulo:
import random
As principais funções deste módulo são apresentadas nas alíneas a seguir.

5.7.1 [Link]()

Retorna um número decimal aleatório no intervalo [0.0, 1.0). Observe que o


intervalo inclui 0.0, mas exclui 1.0.

5.7.2 [Link](a, b)

Retorna um número inteiro aleatório no intervalo [a, b]

85
5.7.3 [Link](a, b)

Retorna um número decimal aleatório no intervalo fechado [a, b]

5.7.4 [Link](sequencia)

Escolhe um elemento aleatório de uma lista, tupla ou string

5.7.5 [Link](lista)

Mistura os elementos de uma lista in-place, ou seja, modifica a lista original

86
5.7.6 Exemplos de aplicações práticas

Exemplo 1: Gerar números aleatórios é essencial em jogos, como no


lançamento de dados. Neste exemplo, dois dados são simulados e o programa
mostra os valores obtidos e a sua soma.

Exemplo 2: Valores aleatórios para criar dados fictícios de teste. No


exemplo seguinte, são geradas idades dentro de um intervalo definido.

Exemplo 3: Um lançamento de foguete, onde o ciclo for possui um


sublinhado (_) que está a ser útil já que o valor da variável de controlo não
tem importância, apenas a repetição. Está a ser usado em conjunto com o

87
módulo random para gerar resultados diferentes em cada execução. O dado
é lançado cinco vezes e o ciclo serve apenas para repetir a ação. Cada
lançamento gera um número aleatório entre 1 e 6, como num jogo de dados.

Exemplo 4: Um dos usos mais corriqueiros é gerar números aleatórios em


jogos. Neste exemplo, o programa escolhe secretamente um número entre 1
e 100 e o jogador tenta adivinhar. A cada palpite, o programa informa se o
número secreto é maior ou menor. O ciclo continua até que o jogador acerte
e no fim se exibe o número de tentativas feitas. Caso o utilizador digite algo
que não seja um número, o programa trata o erro e pede novamente a entrada.

88
5.7.7 Controlar a aleatoriedade com sementes (seeds)

O módulo random do Python gera o que chamamos de números


pseudoaleatórios. Isso significa que os números não são verdadeiramente
aleatórios, mas sim gerados por um algoritmo determinístico que produz uma
sequência que parece aleatória.
A semente (seed) é o valor inicial usado para inicializar o algoritmo gerador
de números aleatórios. Quando se utiliza a mesma semente, obtém-se
exatamente a mesma sequência de números "aleatórios".
O uso de uma seed garante:
− Reprodutibilidade: Para testes e debugging, é essencial poder reproduzir
os mesmos resultados
− Consistência: Em aplicações científicas, para garantir que os resultados
podem ser verificados
− Aprendizagem: Para demonstrar que o mesmo código produz os mesmos
resultados
A sintaxe é [Link](valor_da_semente)
O valor da semente pode ser qualquer número inteiro ou None (este é o
padrão e usa o relógio do sistema para variedade).

Um exemplo de reprodutibilidade básica é apresentado a seguir.

89
Já no exemplo a seguir é feita uma comparação da geração aleatória de
números com e sem semente.

90
O seu resultado com seed pode ser diferente do apresentado neste exemplo,
pois a versão do Python ou a implementação interna do gerador de números
aleatórios pode variar. Isso é normal e esperado, pois diferentes versões do
Python podem produzir sequências diferentes com a mesma seed.

É importante ter algumas considerações ao usar seeds em Python. Não se


devem usar seeds fixas em aplicações de produção que exijam verdadeira
aleatoriedade, como em jogos ou sistemas de segurança. A definição de uma
seed afeta todo o módulo random, ou seja, todas as funções subsequentes
passam a gerar valores baseados nessa mesma seed.
Para voltar ao comportamento verdadeiramente aleatório, pode-se usar
[Link]() sem argumentos, ou [Link](None), que têm
o mesmo efeito.
Em aplicações multi-thread, o uso de seeds requer ainda mais cuidado.

5.7.8 Limitações e considerações

É fundamental compreender que o módulo random gera números


pseudoaleatórios e não verdadeiramente aleatórios. Estes números são
produzidos por algoritmos determinísticos que criam sequências numéricas
que apenas aparentam ser aleatórias. Cada número na sequência é calculado
a partir do anterior usando uma fórmula matemática, começando por um
valor inicial chamado seed (semente).
A mesma seed produz sempre a mesma sequência de números e esta
sequência eventualmente repetir-se-á após um longo período

91
O uso é amplo e engloba jogos e entretenimento, simulações não críticas,
amostragem estatística básica, testes e desenvolvimento e aplicações
educacionais. Não é adequado utilizar para aplicações de criptografia e
segurança, sistemas de autenticação, geração de chaves seguras, jogos de
azar online com apostas reais e aplicações financeiras críticas
Para estes cenários mais sensíveis que requerem verdadeira aleatoriedade
criptográfica, Python oferece módulos especializados, como o secrets
(Python 3.6+) e o módulo [Link]()
Por fim, considere que a geração de números pseudoaleatórios é geralmente
eficiente, mas em contextos específicos pode afetar o desempenho do
programa.

5.8 Tratamento de Erros com try-except

Em programação, os erros são inevitáveis, especialmente quando se interage


com utilizadores ou com dados externos. O Python oferece um mecanismo
elegante para lidar com estes erros: a estrutura try-except.
É importante tratar os erros, pois quando um programa os encontra, não sabe
como processar, então ocorrem os "crashes" (término abrupto). Isto cria uma
má experiência para o utilizador. O tratamento de erros permite que o
programa continue a executar mesmo quando ocorram problemas
inesperados.
Considere que é realizada a entrada de dados, sem tratamento, para um
número inteiro. Se o utilizador digitar, por exemplo abc, ocorre um erro,
conforme apresentado a seguir:

92
A sintaxe básica do try-except, que evitará tais problemas, está
apresentada a seguir.

Este exemplo a seguir mostra como usar um ciclo while juntamente com
try e except para garantir que a idade introduzida é válida. O programa
pede ao utilizador a idade e tenta converter a resposta para um número
inteiro. Se o valor não for numérico, o except captura o erro e o pedido
repete-se. Além disso, há uma verificação extra que não permite idades
negativas: sempre que o utilizador introduz um número menor que zero, é
exibida uma mensagem de erro e o pedido é repetido até que um valor correto
seja introduzido. Quando finalmente se obtém uma idade válida, o programa
calcula, exibe quantos anos a pessoa terá dentro de dez anos e o ciclo termina.

93
5.8.1 Tipos comuns de erros

Os tipos mais comuns de erros são aqui apresentados:


− ValueError: Valor inadequado para uma operação, como por
exemplo, converter "abc" para número
− TypeError: Operação com tipo de dados incorreto, como por
exemplo, somar texto com número
− ZeroDivisionError: Divisão por zero
− IndexError: Acesso a índice inexistente numa lista
− FileNotFoundError: Ficheiro não encontrado

5.8.2 Múltiplos blocos except

É possível capturar diferentes tipos de erros com múltiplos blocos except


com um único try. No exemplo a seguir trata-se estes problemas.

5.8.3 Bloco else e finally

Além dos blocos try e except, o Python também disponibiliza as


cláusulas else e finally.

94
O else é executado apenas se nenhum erro ocorrer no bloco try, de
maneira a funcionar como um caminho seguro após a tentativa.
Já o finally é executado sempre, independentemente de ter ocorrido erro
ou não, sendo útil para ações que não podem ser ignoradas, como fechar um
ficheiro ou libertar recursos.
O exemplo a seguir mostra a utilização conjunta dessas estruturas.

5.8.4 Boas práticas

Tratar erros em Python não é apenas uma questão de evitar falhas: é também
uma forma de tornar o programa mais robusto, profissional e agradável de
usar. Para isso, algumas boas práticas fazem toda a diferença.
Antes de mais, seja específico. Evite usar um except genérico, que captura
tudo, sem distinção do problema. Capture apenas os erros que realmente
espera, como ValueError ou ZeroDivisionError. Isso facilita a
manutenção do código e evita surpresas.
Além disso, dê sempre feedback útil para o utilizador. Se o utilizador
introduzir um valor inválido, não basta dizer que houve erro. Indique o que

95
aconteceu e, se possível, como corrigir. Isso transforma uma falha em
oportunidade de aprendizagem.
Outro ponto importante é manter o bloco try pequeno. Coloque dentro deste
bloco apenas o que pode gerar erro. Isso torna a lógica mais clara e evita
apanhar exceções de trechos que não deveriam estar ali.
E, claro, use múltiplos except quando precisar tratar situações diferentes
de forma distinta. Dividir o tratamento torna o programa mais organizado e
fácil de compreender.
Por fim, se seguires estas práticas faz com que os programas sejam:
− Mais robustos, porque não “crasham” com entradas inesperadas
− Mais amigáveis, porque oferecem mensagens claras
− Mais profissionais, porque lidam graciosamente com exceções
− Mais seguros, porque evitam comportamentos indesejados.

5.9 Exercícios propostos

1) Escrever um programa para ler um número e indicar se é par ou ímpar.


Resolver de duas maneiras: com if-else e com operador ternário.

2) Criar um jogo onde o programa gera um número aleatório entre 1 e 100


e o utilizador deve adivinhar, recebendo dicas ("maior" ou "menor").
Dicas:
− Usar import random e [Link](1, 100) para
gerar o número aleatório

96
− Usar um ciclo while para continuar a solicitar palpites até acertar
− Usar if-elif-else para comparar o palpite com o número
secreto
− Usar try-except para evitar erros se o utilizador não digitar um
número

3) Escrever um programa que peça ao utilizador para introduzir uma


palavra ou frase e conte quantas vogais existem no texto inserido. O
programa deve considerar tanto vogais minúsculas como maiúsculas,
além de vogais com acentos comuns na língua portuguesa. Ao final exiba
o resultado.
Dicas:
− Usar lower() para converter todo o texto para minúsculas
− Usa um ciclo for para percorrer cada caractere do texto
− Usar uma condição if com in para verificar se o caractere é
uma vogal

4) Escrever um programa que simule o lançamento de dois dados de 6 faces.


− Mostrar todas as combinações possíveis
− Calcular e mostrar a soma de cada combinação
− No final, exibir quantas combinações diferentes existem
Dica: usar dois ciclos for aninhados.
Para melhor entendimento, a seguir é apresentado um trecho final, como
exemplo, da saída esperada do programa.

97
5) Criar uma contagem regressiva para um lançamento que começa em 3
minutos e exibir no ecrã tempo no formato "T-2:59", "T-2:58",
..., "T-0:01"
Incluir uma mensagem especial a cada minuto e terminar com
"LANÇAMENTO!"
Desafio adicional: Fazer uma pausa de 1 segundo entre cada segundo,
para tal usar import time e [Link](1).

6) Desenvolver um programa que pergunte ao utilizados quantas séries e


quantas repetições o usuário quer fazer. Simular o treino mostrando cada
série e repetição. Inclua mensagens de descanso entre séries e, ao final,
mostrar uma mensagem de incentivo.
Veja um exemplo de saída deste programa:

98
7) Escreva um programa que leia dois números inteiros e calcule a divisão
do primeiro pelo segundo. O programa deve garantir que apenas valores
numéricos inteiros sejam aceites como entrada. Para além, lembre-se que
não é permitido dividir por zero.
O resultado da divisão deve ser exibido apenas quando ambos os valores
forem válidos.
Usar estruturas de tratamento de exceções (try-except) para capturar
e tratar os erros adequados, mostrando mensagens claras ao utilizador.

8) Criar um programa que simule o lançamento de um dado de seis faces


um número definido pelo utilizador. O programa deve pedir ao utilizador
99
quantas vezes deseja lançar o dado e validar a entrada (número inteiro
positivo), mostrar o resultado de cada lançamento e, ao final, apresentar
estatísticas detalhadas, incluindo:
o Soma total dos valores lançados.
o Média dos resultados.
o Valor ou valores mais frequentes.
o Distribuição completa, mostrando quantas vezes cada face
saiu e a respetiva percentagem.
Este é um exemplo de saída esperada:

100
9) Criar um programa interativo que apresente um menu com diferentes
opções de geração de padrões numéricos. O utilizador deve poder
escolher entre:
− Gerar um triângulo numérico, em que cada linha mostra números em
ordem crescente até atingir a altura definida pelo utilizador
− Construir uma tabuada personalizada de um número à escolha, até
um limite definido pelo utilizador
− Mostrar a sequência de Fibonacci, exibindo um número de termos
especificado
− Listar todos os números primos até um limite fornecido pelo
utilizador.
O programa deve validar as entradas, tratando erros de digitação e
valores inválidos, além de apresentar mensagens claras ao utilizador em
cada situação.
Dicas:
− Use ciclos aninhados (for dentro de for) para construir o triângulo
numérico
− Para a tabuada, utilize um ciclo for simples e a operação de
multiplicação
− Na sequência de Fibonacci, lembre-se de atualizar dois valores a
cada iteração (a e b)
− Para verificar se um número é primo, teste a divisão apenas até à raiz
quadrada desse número
− Utilize condicionais (if/elif/else) para controlar a escolha do
menu.

101
− Acrescente tratamento de exceções (try/except) para lidar com
entradas inválidas e interrupções do utilizador.
Exemplo de execução:

102
6 FUNÇÕES EM PYTHON

As funções são um dos blocos mais importantes na programação. Sempre


que um programa cresce em tamanho e complexidade, torna-se essencial
dividir o código em partes menores, organizadas e reutilizáveis. É aqui que
entram as funções: pequenos blocos que recebem dados, processam
informações e podem devolver resultados.
Além de evitarem repetição de código, as funções tornam os programas mais
legíveis e fáceis de manter. Ao longo deste capítulo serão apresentados os
principais conceitos: como definir funções, chamá-las, passar parâmetros e
trabalhar com diferentes tipos de retornos.

6.1 Definição de funções

Uma função é definida com a palavra-chave def, seguida do nome da função


e, entre parênteses, os seus parâmetros, caso existam. O corpo da função é
um bloco indentado, tal como já utilizado em ciclos e condicionais.
A sintaxe básica de uma função está apresentada a seguir.

Estes são os elementos essenciais para uma função em Python:


− def: Palavra-chave que inicia a definição da função
− nome_da_funcao: Identificador único que segue as mesmas regras
de nomes de variáveis
103
− Parâmetros: São opcionais, entre parênteses, separados por vírgulas
− Dois pontos: Indicam o início do bloco da função
− Indentação: não é apenas uma questão de estilo, é obrigatória e indica
quais linhas pertencem ao bloco da função (normalmente 4 espaços)
− return: Opcional, devolve valores ao chamador

Pode-se pensar numa função como uma pequena “máquina”: primeiro


define-se o que essa máquina deve fazer, mas nada acontece até ser acionada.
Isto significa que a definição apenas guarda o código para uso futuro. Para
que a função seja realmente executada, é necessário chamá-la pelo nome
seguido de parênteses (e, se existirem, os parâmetros). Só nesse momento as
instruções dentro dela são processadas.
Veja o exemplo a seguir:

A saída deste programa é a seguinte:

Antes de explorar os diferentes tipos de funções, é fundamental compreender


o conceito de âmbito ou âmbito de uma vaiável, ou seja, onde uma variável
"existe" e pode ser acedida em um código.

104
6.2 Âmbito de variáveis

Pense no âmbito de varável como diferentes níveis de acesso em um edifício:


− Variáveis globais estão no térreo, ou seja, visíveis para todos
− Variáveis locais estão em salas específicas, sendo somente acedidas
dentro daquele sítio
Cada função cria seu próprio ambiente isolado, protegendo suas variáveis do
resto do programa
Este entendimento é crucial porque determina onde suas variáveis são
válidas e por quanto tempo existem durante a execução do programa.

6.2.1 Âmbito Local e Global

Ao definir parâmetros numa função, criam-se variáveis que existem apenas


dentro dela. Essas variáveis pertencem ao chamado âmbito local da função:
− são criadas no momento em que a função é chamada
− só podem ser acedidas dentro dessa função
− deixam de existir automaticamente assim que a execução da função
termina.
Por outro lado, variáveis criadas fora de qualquer função pertencem ao
âmbito global. Estas variáveis existem durante toda a execução do programa
e podem ser usadas em qualquer parte do código, mesmo dentro das funções,
desde que não sejam redefinidas localmente.

Um ponto importante é a prioridade de nomes das variáveis: se uma função


declarar uma variável local com o mesmo nome de uma variável global, o
Python dará prioridade à variável local dentro do âmbito da função. Assim,

105
a variável global permanece inalterada, mesmo que a local seja modificada,
conforme se percebe pelo exemplo a seguir.

Tenha sempre em mente que, em Python, o âmbito é determinado por


funções, classes e módulos e não por blocos de controlo como if, for,
while. Isto significa que quando atribuis um valor a uma variável dentro de
um ciclo ou condicional, essa variável passa a existir no âmbito da função ou
módulo onde o bloco se encontra, não ficando confinada ao bloco. Como
consequência, as variáveis declaradas dentro dessas estruturas permanecem
acessíveis após o bloco terminar, desde que o ramo em questão tenha sido
executado.
Ou seja, se um ramo if não for executado, a variável não será criada e ao
tentar aceder a ela posteriormente resultará num NameError, conforme
exemplo a seguir.

106
Dentro de funções, qualquer atribuição a um nome em qualquer ponto faz
com que esse nome seja tratado como local em toda a função, devido à regra
LEGB (Local, Enclosing, Global, Built-in). Se tentares ler esse nome antes
de lhe atribuires um valor, ocorrerá um UnboundLocalError.

6.2.2 Palavra-chave: global

É possível, em casos especiais, usar a palavra-chave global dentro de uma


função para indicar que se deseja alterar diretamente uma variável global.
É importante compreender que, ao contrário de algumas linguagens de
programação, como C, o Python faz uma distinção muito clara entre ler e
modificar variáveis globais:
− Ler variáveis globais  para só aceder ao valor duma variável global
dentro duma função, não é necessário usar a palavra-chave global
− Alterar variáveis globais  para atribuir novo valor a uma variável
global dentro de uma função é obrigatório declarar essa variável como
global. Caso contrário, o Python interpretará que se trata de uma variável
local, criando uma nova dentro da função e ignorando a global.
O exemplo a seguir ilustra o uso da palavra-chave global.

107
Embora a palavra-chave global resolva a questão, deve ser usada com
cautela. Alterar variáveis globais dentro de funções pode tornar o código
mais confuso, difícil de manter e menos modular.
A boa prática é passar valores como parâmetros e devolver resultados com
return, em vez de depender de alterações globais.

6.2.3 Âmbito não local (variáveis em funções aninhadas)

Além do âmbito local e global, o Python também possui o chamado “âmbito


não local”, que surge em situações de funções aninhadas, também chamadas
nested functions.
Uma função aninhada é uma função definida dentro de outra função. Nesses
casos, as variáveis declaradas na função externa não são globais, mas
também não pertencem ao âmbito local da função interna. Elas ficam num
nível intermediário, ou seja, num âmbito não local.
Se a função interna apenas ler a variável da função externa, não há
necessidade de nenhuma palavra-chave especial. No entanto, para alterar o
valor dessa variável da função externa é necessário usar a palavra-chave
nonlocal.
Deve-se usar nonlocal quando precisar modificar uma variável de uma
função externa, mas não global, a partir de uma função interna. É uma forma
mais segura do que usar global, permitindo manter e atualizar informações
(estado) entre funções relacionadas
Apesar de ser um conceito um pouco confuso, veja o exemplo a seguir para
que possas esclarecer esta ideia.

108
Entretanto, se o código necessitar usar nonlocal com frequência, o
programador deve optar por usar classes, na orientação objeto, pois estas
oferecem uma maneira mais organizada de gerenciar estado compartilhado.

6.2.4 Exemplo comparativo de âmbito de variáveis

Ao usar funções é essencial compreender bem os âmbitos de variáveis.


O exemplo a seguir reúne, num único script, quatro cenários de âmbito em
Python. Primeiro, mostra a leitura direta de uma variável global, sem
necessidade de global. Depois, mostra a sombra de nomes, ou seja, quando
um identificador repetido num âmbito interno cria uma variável local que
encobre a variável global, mantendo-a inalterada.
Em seguida, ilustra a alteração explícita da variável global com global (má
prática, usada aqui apenas para fins didáticos).
Por fim, introduz o âmbito não local em funções aninhadas, utilizando
nonlocal para modificar a variável da função externa.

109
Observe as saídas comentadas, nas chamadas das funções, para que percebas
e distinga claramente entre ler, sombrear e modificar valores em cada nível
de âmbito.

110
Após executar este programa ilustrativo sobre âmbito de variáveis, obtém-se
a seguinte saída.

6.3 Tipos de funções

Em Python, as funções podem ser classificadas de diferentes formas


conforme suas características de parâmetros e valores de retorno. Esta
classificação ajuda a perceber melhor como estruturar e utilizar funções de
acordo com as necessidades específicas de cada programa.

6.3.1 Funções sem parâmetros e sem retorno

Estas são as funções mais básicas, que não recebem nenhum dado de entrada
(parâmetros) e não devolvem nenhum valor como resultado. São
normalmente utilizadas para executar ações específicas que não dependem
de dados externos e não precisam fornecer resultados para outras partes do
código, conforme apresentado no exemplo a seguir.

111
As suas principais características são:
− Não possuem parâmetros (parênteses vazios)
− Não utilizam a instrução return, saem da função após o fim de sua
execução, entretanto podem utilizar um return sem valor
− Executam uma ação, como exibir informações no ecrã
− Úteis para agrupar código que será reutilizado várias vezes

Vale ressaltar que apesar da função não devolver valor, implicitamente


devolve o valor especial None. Perceba este conceito no exemplo a seguir:

6.3.2 Funções com parâmetros e sem retorno

Estas funções recebem dados de entrada através de parâmetros, mas não


devolvem nenhum valor explicitamente. São úteis quando é necessário
processar informações ou executar ações baseadas em dados externos, mas
não há nada que retornar como um resultado calculado.
O exemplo seguinte ilustra este tipo de função, que recebe uma string, exibe
uma mensagem e não retorna nenhum valor.

112
Dentre as suas principais características destacam-se:
− Recebem um ou mais parâmetros. Não necessariamente do mesmo tipo
− Processam os dados recebidos
− Não utilizam return ou podem utilizar um return sem valor
− O valor retornado é sempre None (implícito).

6.3.3 Funções com parâmetro e com retorno

Estas são funções completas que tanto recebem dados de entrada quanto
devolvem um resultado processado. São extremamente comuns e úteis para
agrupar cálculos ou transformações de dados.
A função pode ter um ou vários parâmetros e, para perceber o conceito,
considere o exemplo da soma, onde a função somar(a, b) recebe dois
valores, calcula o resultado e devolve-o com return:

113
Os parâmetros passados para uma função não precisam ser todos do mesmo
tipo, nem é obrigatório haver apenas um valor de retorno possível. No
exemplo a seguir, a função recebe três parâmetros distintos (uma string, um
número inteiro e um valor booleano). A depender do valor lógico do
parâmetro booleano, o return devolve uma resposta diferente.
É importante destacar que, no momento em que o return é executado, a
função termina imediatamente: o valor é devolvido e nenhuma instrução
abaixo dessa linha é executada.

Este programa tem a seguinte saída.

114
Suas principais características são:
− Recebem parâmetros como entrada
− Processam esses parâmetros
− Utilizam return para devolver um resultado
− Podem ser usadas em expressões ou atribuições

6.3.4 Funções com múltiplos retornos

Python permite que uma função devolva vários valores de uma só vez.
Quando isso acontece, os valores são entregues juntos como um único
pacote, que pode ser recebido em várias variáveis ao mesmo tempo na
chamada da função.
Este tipo de função tem importância especial porque quando uma função
executa a instrução return, sua execução termina imediatamente. Se
houvesse múltiplas instruções return consecutivas, apenas a primeira seria
executada, desta forma, para devolver vários valores, deve-se fazê-lo em um
único return.
Neste exemplo a seguir, a função operacoes_matematicas(a, b)
calcula quatro resultados (soma, diferença, produto e
quociente) e devolve-os de uma só vez sob a forma de tuplo (este
conceito será apresentado no capítulo 7). Na chamada, é usado
desempacotamento múltiplo, soma, diff, prod, div =
operacoes_matematicas(...), para distribuir cada elemento do
tuplo por variáveis distintas, na mesma ordem do return.

115
Entre as características das funções com múltiplos retornos, cita-se:
− Devolvem vários valores numa única operação de retorno
− Podem ser recebidos em múltiplas variáveis simultaneamente
− Úteis quando uma função precisa fornecer vários resultados relacionados
− Simplificam o código evitando múltiplas chamadas para obter dados
conectados.

6.4 Argumentos opcionais e nomeados

Os argumentos opcionais e nomeados são funcionalidades poderosas do


Python que tornam as funções mais flexíveis e o código mais legível.
Um argumento opcional possui um valor pré-definido (valor por defeito) que
é utilizado automaticamente quando o chamador não fornece esse parâmetro.
Por outro lado, um argumento nomeado permite especificar explicitamente a
qual parâmetro um valor se destina, independentemente da ordem em que
são passados.

116
A regra para definir funções com estes tipos de argumentos é simples:
primeiro devem vir os parâmetros obrigatórios, seguidos pelos opcionais.
Esta ordem é essencial para que o Python possa interpretar corretamente os
argumentos fornecidos.
Como nota importante, os valores padrão são avaliados no momento da
definição da função. Por este motivo, deve evitar-se usar objetos mutáveis
(como listas ou dicionários, a serem vistos no capítulo 7) como valores
padrão, pois isso pode levar a comportamentos inesperados.

6.4.1 Parâmetros com valores padrão (opcionais)

Python oferece uma flexibilidade notável na definição de funções através dos


parâmetros opcionais, também conhecidos como parâmetros com valor
padrão. Esta funcionalidade permite que argumentos sejam omitidos na
chamada da função, assumindo automaticamente valores predefinidos
especificados na definição.
A sintaxe para definir parâmetros opcionais é simples. Basta, após o nome
do parâmetro, utilizar o operador de igualdade (=) seguido do valor padrão
desejado.
Este valor padrão funciona como uma inicialização local da variável, se
nenhum argumento for fornecido para aquele parâmetro, a variável recebe o
valor especificado na definição da função.
Neste exemplo, a função saudacao(nome, mensagem="Olá") tem
um parâmetro obrigatório (nome) e um opcional com valor por defeito
(mensagem). Se for passado apenas o nome, usa-se “Olá”; ao fornecer o

117
segundo argumento, a saudação é personalizada. As chamadas ilustram o uso
do valor por defeito e a substituição posicional do parâmetro opcional. A
função não devolve valor, limita-se a imprimir a mensagem formatada com
f-strings.

6.4.2 Múltiplos parâmetros opcionais

Quando uma função possui múltiplos parâmetros opcionais, estes devem ser
definidos após os parâmetros obrigatórios. Esta ordem é importante porque
o Python atribui os argumentos por posição.
Neste exemplo a seguir, a função criar_perfil tem dois parâmetros
obrigatórios (nome e idade) e dois opcionais com valores por defeito
(cidade="Lisboa" e pais="Portugal"). A função imprime os
dados do perfil e uma linha separadora.
Na primeira chamada, passam-se apenas nome e idade, sendo então usados
os valores padrão para cidade e pais.
Na segunda chamada, substitui-se apenas a cidade (o país permanece
“Portugal”).
Na terceira chamada, substituem-se cidade e país.

118
Se quiseres, também podes escrever as substituições com argumentos
nomeados (independentemente da ordem), como por exemplo, colocar como
parâmetros da função criar_perfil:
("Maria", 28, pais="Espanha", cidade="Madrid")

A saída deste programa será a seguinte:

119
6.4.3 Argumentos nomeados (keyword arguments)

Os argumentos nomeados representam um poderoso recurso do Python que


permite especificar explicitamente qual parâmetro recebe qual valor durante
a chamada da função. Esta abordagem oferece várias vantagens:
− Clareza no código: torna explícito o propósito de cada argumento
− Independência de ordem: os argumentos podem ser fornecidos em
qualquer ordem
− Flexibilidade combinatória: permite combinar argumentos posicionais e
nomeados.
No exemplo a seguir, a função calcular_juros recebe um parâmetro
obrigatório (valor) e dois opcionais com valores por defeito (taxa=0.05
e tempo=12). Dentro da função se calcula juros simples (valor * taxa
* tempo) e devolve o montante final (valor + juros). A seguir,
mostram-se quatro formas de chamada: (1) apenas com valor, usando
todos os padrões; (2) a taxa é alterada por posição; (3) o tempo é alterado
por argumento nomeado; e (4) usam-se argumentos nomeados fora de ordem
(taxa e valor). Repare que argumentos nomeados permitem trocar a
ordem sem ambiguidade, enquanto chamadas posicionais seguem a ordem
dos parâmetros.

120
Após a execução deste programa, obtém-se a seguinte saída:

6.5 Funções Anónimas (lambda)

Em Python é possível criar funções simples sem nome, chamadas funções


anónimas, usando a palavra-chave lambda. A sintaxe geral é:
lambda argumentos: expressão
O resultado da expressão é devolvido automaticamente, como apresentado
no exemplo a seguir.

As funções lambda são especialmente úteis quando aplicadas como


argumentos de outras funções, em particular em operações de ordenação,

121
filtragem ou transformação de coleções. O exemplo simples apresentado a
seguir introduz essa utilização, que será explorada em maior profundidade
no Capítulo 7, onde se apresentam listas, tuplos, conjuntos e dicionários.

6.6 main() e o guardião if __name__ == "__main__":

Em Python, não existe um ponto de entrada obrigatório como noutras


linguagens, mas é boa prática concentrar o fluxo principal do programa numa
função main(), executando-a apenas quando o ficheiro é executado
diretamente.
Este comportamento é controlado pelo guardião:
if __name__ == "__main__":
A variável __name__ é definida automaticamente pelo interpretador.
Quando o ficheiro é executado diretamente, o valor atribuído é __main__.
Já quando o ficheiro é importado como módulo noutro código, __name__
recebe o nome desse módulo.
Esta convenção evita, de forma elegante, que blocos de demonstração ou de
interação com o utilizador sejam executados inadvertidamente durante uma
importação.
Para além de aumentar a robustez, o padrão melhora significativamente a
organização do código: as funções e classes reutilizáveis podem ser definidas
no início do ficheiro, enquanto a orquestração do fluxo fica confinada à
função main(), o que também facilita a escrita de testes automatizados.

122
Em scripts mais simples, basta chamar main() dentro do guardião. No
entanto, para aplicações mais sólidas ou para uso em automação, é
recomendável devolver um código de saída explícito ao sistema operativo.
Consegue-se isso com [Link](main()), onde a função main()
retorna 0 em caso de sucesso e um valor diferente de zero para indicar que
ocorreu um erro.
A seguir apresenta-se uma versão genérica como um template ou "sintaxe"
padrão da main() com guardião if __name__ == "__main__".

123
Nesta estrutura genérica salienta-se os seguintes pontos:
− Importações → Todos os imports no início do ficheiro
− Definições → Funções, classes e constantes reutilizáveis
− Função main() → Contém a lógica principal dentro de um bloco try-
except
− Ponto de entrada → O guardião que controla a execução
124
− [Link]() → Garante o retorno do código de saída adequado

E para maior clareza, resumidamente, um código feito com a função


main() if __name__ == "__main__": e o guardião pode sempre usar
o modelo a seguir:

Neste exemplo a seguir, que segue a sugestão de template anterior,


primeiramente faz-se as importações no topo, seguindo as convenções do
Python.
Em seguida, vêm as definições das funções auxiliares, que podem ser
reutilizadas noutros módulos.
Por fim, a função main() contém a lógica principal do programa, e o
guardião if name == "main" assegura que esta só será executada
quando o ficheiro for executado diretamente.

125
Esta organização permite que o script funcione tanto como programa
independente quanto como módulo importável, mantendo o código limpo e
e de fácil entendimento.

6.6.1 Comportamento na prática

Na prática, há dois cenários distintos.

126
Quando o ficheiro é executado diretamente (por exemplo, com python
meu_programa.py), o interpretador define __name__ como
"__main__" e o bloco protegido pelo guardião é executado: arranca-se a
função main() e corre o fluxo do programa.
Um exemplo mínimo é o seguinte:

Ao executar este ficheiro na linha de comandos:


print(meu_programa.funcao_auxiliar(5)) # 10

O processo termina com código de saída 0 (sucesso).

Entretanto, quando o mesmo ficheiro é importado como módulo noutro


programa, o valor de __name__ passa a ser o nome do módulo e o guardião
impede a execução automática de main(). Assim, nada é impresso no
momento da importação e apenas as funções são disponibilizadas para
reutilização:

127
6.6.2 Erros comuns e como os evitar.

Um erro frequente é deixar “código solto” no topo do ficheiro com


print() ou input(). Esse código sempre será executado quando o
módulo for importado, o que raramente é desejável.

A correção consiste em mover essa lógica para a função main() e proteger


a execução com o guardião.

128
Outro deslize típico é esquecer o próprio guardião e chamar main()
diretamente; nesse caso, a função será executada tanto na execução direta
como na importação.

Também convém evitar que a main() cresça demasiadamente, pois


quando esta função se tornar extensa, deve-se extrair partes para funções
auxiliares de forma a melhorar a legibilidade e a testabilidade.
No exemplo a seguir, mostra-se o exemplo com a main() longa e, logo
após, devidamente organizada.

129
Finalmente, importa manter apenas as importações necessárias no topo do
ficheiro, reduzindo tempo de arranque e dependências desnecessárias
Para além, é possível chamar apenas main() sem [Link] para o caso
de scripts introdutórios. Em programas usados por outras ferramentas ou
pipelines, preferir [Link](main()) para devolver um código de
saída explícito ao sistema.

130
6.7 Exemplos práticos

Devido a importância, aqui será explorado um pouco de como as funções


podem ser aplicadas em situações reais de programação, desde operações
básicas até sistemas mais interativos. Estes exemplos demonstram a
versatilidade das funções na criação de código organizado e reutilizável.

131
6.7.1 Exemplo 1: Sistema de Pontuação para Jogo

O exemplo implementa um sistema de pontuação e classificação para um


jogo de plataforma, organizado em três funções bem definidas.

A função
calcular_pontuacao(moedas, inimigos_derrotados,
tempo_restante) soma a pontuação ponderada (moedas×10 +
inimigos×100 + tempo×5) e devolve o total.

A função
definir_nivel(pontuacao) classifica o jogador com base em
limiares fixos: Mestre (≥1000), Avançado (≥500),
Intermediário (≥200) ou Iniciante (abaixo de 200).

A função
exibir_resultado(nome_jogador, pontuacao, nivel)
trata apenas da apresentação formatada (nome, pontuação e nível).

O exemplo evidencia boas práticas: uso de parâmetros e return (sem


variáveis globais), docstrings curtas, indentação correta e separação entre
lógica e apresentação.
Com os dados fornecidos, todos os jogadores atingem o nível Mestre,
ilustrando como os pesos e limiares influenciam a classificação.

132
Após a execução deste código, a seguinte saída é exibida no ecrã.

133
6.7.2 Exemplo 2: Analisador de Estatísticas de Streamer

O exemplo implementa um analisador simples de transmissões ao vivo,


organizado em três funções que separam cálculo, avaliação e apresentação.
A função calcula_engajamento recebe o número de visualizadores, o
total de seguidores e a duração em horas, valida que há duração e seguidores
positivos e calcula a taxa de engajamento como a razão entre visualizadores
e seguidores, convertida em percentagem e limitada a um máximo de 100
por meio de min(), que calcula o mínimo entre os valores.
Em seguida, avaliar_desempenho invoca esse cálculo e classifica a
transmissão em “Excelente”, “Bom”, “Regular” ou “Baixo” de
acordo com limiares progressivos, devolvendo em conjunto o rótulo de
desempenho e a percentagem obtida.
Por fim, gerar_relatorio concentra apenas a apresentação.
O programa principal demonstra o fluxo completo com três chamadas diretas
a gerar_relatorio, sem usar listas, evidenciando boas práticas como
passagem de dados por parâmetros, retorno explícito de valores, docstrings
curtas e indentação correta; observa-se ainda que o montante de doações é

134
informado no relatório, mas não influencia a classificação, permitindo
futuras extensões onde métricas financeiras possam ser ponderadas.

No ecrã exibe-se o seguinte relatório.

135
6.7.3 Exemplo 3: Sistema de Conquistas para App Fitness

O exemplo apresenta um sistema de conquistas para uma app de fitness,


estruturado em duas funções principais. A função verificar_meta
recebe como parâmetros o número de passos, as calorias gastas, os minutos
de exercício e os dias consecutivos de atividade. Em seguida os compara com
metas fixas (10 000 passos, 300 calorias e 30 minutos de exercício) e devolve
uma conquista em forma de texto. A depender das metas diárias atingidas,
uma mensagem é exibida no ecrã.
A segunda função, mostrar_relatorio, organiza a apresentação dos
resultados. Ela mostra os valores registados (passos, calorias, minutos e dias

136
consecutivos) e exibe a conquista devolvida pela primeira função,
produzindo assim um relatório claro e completo para o utilizador.

Após executar este código, exibe-se no ecrã o seguinte relatório.

137
6.7.4 Exemplo 4: Mistura de Poções num Jogo de Fantasia

Em um jogo de aventura no qual o jogador é um aprendiz de feiticeiro.


Durante a jornada, ele encontra diferentes ingredientes mágicos, como
“dragão”, “fada” e “sapo”, e deve misturá-los para descobrir os efeitos da
poção resultante.
O sistema do jogo funciona de forma simples:
A função misturar_ingredientes recebe três ingredientes escolhidos
pelo jogador e devolve o efeito mágico correspondente.
A função mostrar_relatorio apresenta um relatório final, exibindo os
ingredientes usados e o efeito obtido.
A função main organiza tudo da seguinte forma: solicita os três ingredientes
ao jogador, chama as outras funções e mostra no ecrã a poção criada.

138
Um exemplo de relatório de saída é apresentado a seguir.

139
6.8 Exercícios propostos

1) Criar um programa com duas funções que permitam calcular e exibir a


conversão de temperaturas. Estas são as funções:
− celsius_fahrenheit(celsius): converte temperatura de
Celsius para Fahrenheit
− fahrenheit_celsius(fahrenheit): converte temperatura de
Fahrenheit para Celsius
− O utilizador deve entrar com a Temperatura e em qual escala (Celsius ou
Fahrenheit) está a temperatura.
− Use try/exception para garantir que a temperatura esteja em
número.
Fórmulas:
°F = (°C × 9/5) + 32
°C = (°F - 32) × 5/9

2) Fazer uma função verificar_password(password) que verifica


se uma palavra-passe:
− Tem pelo menos 8 caracteres
− Contém pelo menos uma letra maiúscula

140
− Contém pelo menos uma letra minúscula
− Contém pelo menos um número
A função deve retornar True se a palavra-passe for válida e False caso
contrário.
Este é um exemplo de uso:

3) Implementar uma função chamada gerar_saudacao(nome) que


produz uma mensagem personalizada conforme a hora atual do sistema.
O programa deve:
− Obter a hora atual utilizando o módulo datetime
([Link]().hour).
− Retornar uma mensagem adaptada:
o "Bom dia, [nome]!" entre 6h e 12h
o "Boa tarde, [nome]!" entre 13h e 18h
o "Boa noite, [nome]!" em qualquer outro horário
− Sugestão: utilize condicionais if / elif / else para selecionar a
saudação correta.

141
4) Criar uma função gerar_relatorio_produto(nome, preco,
quantidade) que devolve um relatório formatado com as
informações de um produto. O relatório deve incluir:
− Nome do produto
− Preço unitário (formatado com duas casas decimais e símbolo €)
− Quantidade em estoque
− Valor total do estoque (preço × quantidade)
O programa principal deve:
− Repetidamente pedir ao utilizador o nome, preço e quantidade de
produtos
− Terminar a entrada quando o utilizador digitar "sair"
− Mostrar o relatório individual de cada produto logo após o cadastro
− Manter um acumulador com o valor total de todos os produtos
− No fim, exibir um relatório geral com a soma acumulada.
Dica: Para terminar o programa, comparar o nome do produto digitado com
"sair" usando .lower() para aceitar maiúsculas e minúsculas.
Exemplo de uso:

142
5) Implementar uma calculadora simples com funções em Python. O
programa deve conter quatro funções principais: somar, subtrair,
multiplicar e dividir. Cada função deve receber dois parâmetros e
devolver o resultado da operação correspondente.
No caso da divisão, deve-se usar tratamento de exceções (try /
except) para evitar o erro de divisão por zero.
O programa deve pedir ao utilizador dois números inteiros através do
teclado e, em seguida, exibir os resultados das quatro operações.
O código deve ser organizado, com mensagens claras para o utilizador,
conforme sugestão a seguir.

143
6) Implementar um jogo de adivinhação numérica usando funções em
Python. O programa deve permitir ao jogador adivinhar um número
secreto gerado aleatoriamente entre 1 e 100. O jogo deve ser
implementado usando as seguintes funções:
gerar_numero_secreto()
− Gera e retorna um número inteiro aleatório entre 1 e 100
obter_palpite()
− Solicita e valida a entrada do usuário
− Garante que o palpite seja um número inteiro entre 1 e 100
− Retorna o palpite validado
dar_dica(palpite, numero_secreto)
− Recebe o palpite do jogador e o número secreto
− Retorna uma string a indicar se o número é mais alto ou mais baixo
− Exemplo: "Tente um número MAIOR!" ou "Tente um número
MENOR!"
jogar_adivinhacao()
− Função principal que orquestra o jogo
− Controla o fluxo do jogo e o número de tentativas
− Exibe mensagens de vitória ou derrota
144
Regras do Jogo:
− O jogador tem no máximo 10 tentativas
− A cada palpite errado, o jogo fornece uma dica
− O jogo termina quando o jogador acerta ou esgota todas as tentativas
Dica: Implementar tratamento de erros para entradas inválidas

145
7 ESTRUTURAS DE DADOS EM PYTHON

Em programação, muitas vezes é necessário guardar vários valores e tratá-


los de forma organizada. Seja, por exemplo, guardar os nomes de todos os
alunos de uma turma, os preços de produtos numa loja ou as coordenadas de
um jogo.
É aqui que entram as estruturas de dados, pois estas permitem armazenar
coleções de elementos, organizar e manipular conjuntos de informação de
forma eficiente.
O Python oferece quatro estruturas fundamentais, cada uma com
características específicas:
− Listas → Coleções ordenadas e mutáveis de elementos
− Tuplos → Coleções ordenadas, mas imutáveis
− Conjuntos (Sets) → Coleções não ordenadas de elementos únicos
− Dicionários → Pares chave-valor para acesso rápido à informação.
Dominar estas estruturas é essencial para escrever código Python eficiente,
onde cada qual tem características próprias, vantagens e desvantagens. A
seguir vamos explorar cada estrutura com exemplos práticos.

7.1 Listas

Uma lista é uma sequência ordenada de elementos, delimitada por parênteses


retos []. Os elementos devem ser separados por vírgulas.

146
É a estrutura mais versátil do Python, pois permite armazenar diferentes tipos
de dados e modificar os seus elementos, ou seja, as listas são mutáveis.

7.1.1 Criar e aceder a listas

A sintaxe geral para criar uma lista é a seguinte:


nome_lista = [elemento1, elemento2, ...]
É também possível criar uma lista vazia, para ser preenchida depois e possui
a seguinte sintaxe:
nome_lista = []
Cada elemento da lista pode ser acedido através de um índice, que indica a
sua posição. Tal como em outras linguagens, em Python, a contagem do
índice começa no zero e se tentares aceder a um índice que não existe, ocorre
um erro. Veja um exemplo logo a seguir.

Além dos índices positivos, o Python também permite o uso de índices


negativos. Nesse caso, a contagem inicia no último elemento da lista (-1) e
segue de trás para a frente. Esta forma é prática para aceder rapidamente aos
últimos elementos sem saber o tamanho da lista, como no exemplo a seguir.

147
7.1.2 Alterar e adicionar elementos a uma lista

As listas em Python possuem uma grande variedade de métodos que tornam


o seu uso extremamente flexível.
Um método é uma função associada a um objeto, e no caso das listas, os
métodos são “ações prontas” a serem executadas diretamente sobre a lista.
Com estes métodos é possível adicionar novos elementos, remover itens
existentes, reordenar, procurar valores, dentre muitas outras operações.
O facto das listas serem estruturas mutáveis faz com que se possa alterar o
seu conteúdo sempre que necessário, conforme a situaçao. Isso torna as listas
uma das estruturas mais poderosas e utilizadas na linguagem.
A Tabela 11 apresenta os métodos mais importantes das listas em Python,
com uma breve descrição.
Tabela 11 - Principais métodos de listas em Python

Método Descrição
append(x) Adiciona um elemento ao final da lista
insert(i, x) Insere o elemento x na posição i
remove(x) Remove a primeira ocorrência do elemento x
pop(i) Remove e devolve o elemento da posição i
clear() Remove todos os elementos (lista vazia)
sort() Ordena os elementos em ordem crescente
reverse() Inverte a ordem dos elementos
count(x) Conta quantas vezes x aparece na lista
index(x) Devolve o índice da primeira ocorrência de x
len(lista) (função) Devolve o tamanho da lista

Para melhor percepção, veja no exemplo único a seguir uma aplicação de


148
todos estes métodos, para que possas observar na prática o efeito de cada um.

149
Por ser uma estrutura flexível, uma lista em Python pode armazenar
elementos de diferentes naturezas. É comum haver listas apenas com strings,
apenas com números ou apenas com valores lógicos. No entanto, nada
impede que uma mesma lista misture vários tipos de dados, como no
exemplo a seguir:

Embora seja possível misturar tipos, em programas mais complexos


recomenda-se manter listas com elementos do mesmo tipo, pois isso torna o
código mais claro e fácil de manter.

7.1.3 Iterar sobre listas

Uma das operações mais comuns em programação é percorrer todos os


elementos de uma lista e executar uma ação com cada um deles. Esse
processo recebe o nome de iteração. Em Python, existem várias formas de
iterar sobre listas, cada uma adequada a situações diferentes.

a) Iterar com o ciclo for


A forma mais simples e intuitiva é usar o ciclo for. O Python percorre a lista
elemento a elemento, atribuindo temporariamente cada valor a uma variável.

150
O exemplo a seguir apresenta a iteração numa lista de strings e exibe os
elementos da lista em linhas distintas.

b) Iterar com índices


Em alguns casos, pode ser útil aceder também à posição de cada elemento.
Isso pode ser feito tanto com a função range() quanto com a função
enumerate().
A função range(), já apresentada anteriormente, gera uma sequência de
números inteiros, permitindo iterar pelos índices de uma coleção. É útil
quando se necessita da posição dos elementos para acesso ou modificação.
A função enumerate() adiciona um contador automático aos elementos
de um iterável, isto é, algo que pode ser percorrido num ciclo for, como listas
ou strings. Esta função retorna pares (índice, valor), permitindo aceder à
posição e ao conteúdo de cada elemento sem precisar usar índices
manualmente.

No exemplo a seguir há um exemplo, no qual os códigos possuem a mesma


saída no ecrã.

151
c) Imprimir todos os elementos sem os parênteses retos
Para mostrar a lista completa, mas sem parênteses retos e sem vírgulas, é
possível usar variações de iteração. No exemplo anterior é apresentada a
maneira de exibir através de um for iterável.
Mas é possível usar o operador * (expansão da lista), que mostra os
elementos da lista separados por espaços.

Há ainda o método join(), que pode ser utilizado apenas com listas de
strings. Este método permite juntar todos os elementos numa única linha,
usando como separador qualquer texto que o programador desejar. Esse
separador pode ser uma vírgula, um hífen, um espaço em branco ou até
152
mesmo ícones e emojis. Mas observe no exemplo que há espaço após a
vírgula, bem como antes e depois do hífen e dos emogis.

Em resumo:
− for → cada elemento numa linha
− *lista → todos elementos separados por espaço
− join() → todos elementos unidos por um separador à escolha.

7.1.4 List Comprehensions (Compreensões de Lista)

São uma forma compacta e eficiente de criar listas em Python, substituindo


ciclos tradicionais por uma sintaxe única dentro de parênteses retos.
Permitem transformar, filtrar e processar elementos numa única linha de
código.
A sintaxe básica das List Comprehensions é a seguinte
[expressão for elemento in iterável if condição]
Onde:
− Expressão: O que fazer com cada elemento

153
− Ciclo for: Para percorrer o iterável
− Condição if (opcional): Para filtrar elementos.

As List Comprehensions possuem vantagens adicionais, como a melhoria da


performance em comparação com loops tradicionais, bem como ter maior
versatilidade para criar listas complexas com múltiplas condições.

Este exemplo a seguir demonstra claramente como ambas as abordagens


produzem o mesmo resultado, mostrando na prática como simplificar código
através das List Comprehensions. A versão com compreensão é não apenas
mais concisa, mas também mais legível e eficiente.

Nos exemplos a seguir, demonstra-se três aplicações práticas, mais


avançadas, das List Comprehensions, mostrando diferentes variações da sua
sintaxe. Cada exemplo inclui o código, a saída correspondente e uma breve
explicação do resultado, de forma a ilustrar como esta funcionalidade
poderosa pode simplificar operações complexas de forma elegante e
eficiente.

154
As list comprehensions são uma forma poderosa e concisa de criar listas em
Python. Podem ir desde a forma mais básica até construções mais complexas
com filtros, condições, múltiplos ciclos e até listas aninhadas. Entretanto
senguem sempre o mesmo padrão:
[expressão for item in iterável ...]
A seguir, apresentam-se os principais tipos organizados em blocos, cada um
mostrando a sintaxe, um exemplo prático e o respetivo resultado. Por fim,
apresentam-se alguns exemplos de variações consideradas úteis.
a) Básica
− Sintaxe: [expr for item in iterable]
− Exemplo: [x*2 for x in range(3)]
− Resultado: [0, 2, 4]
155
b) Com filtro
− Sintaxe: [expr for item in iterable if cond]
− Exemplo: [x for x in range(5) if x%2==0]
− Resultado: [0, 2, 4]
c) Com if-else
− Sintaxe: [A if cond else B for item in iterable]
− Exemplo: ["Par" if x%2==0 else "Ímpar" for x in
range(3)]
− Resultado: ['Par', 'Ímpar', 'Par']
d) Múltiplos for
− Sintaxe: [expr for a in A for b in B]
− Exemplo: [(x,y) for x in [1,2] for y in [3,4]]
− Resultado: [(1,3), (1,4), (2,3), (2,4)]
e) Aninhada
− Sintaxe: [[exp for i in iter] for i in iter]
− Exemplo: [[x+y for x in [1,2]] for y in [3,4]]
− Resultado: [[4, 5], [5, 6]]
f) Outras variações úteis
i - Com função aplicada
o nomes = ["luís", "bruno", "david"]
o maiusculos = [[Link]() for n in nomes]
o # ['LUÍS', 'BRUNO', 'DAVID']
ii - Com transformação numérica
o cubos = [x**3 for x in range(5)]
o # [0, 1, 8, 27, 64]
156
iii - Com múltiplas condições
o par = [x for x in range(20)
if x % 2 == 0 if x > 10]
o # [12, 14, 16, 18]
iv - Achatamento de listas ou flatten
o listas = [[1,2], [3,4], [5,6]]
o flatten = [x for s in listas for x in s]
o # [1, 2, 3, 4, 5, 6]

7.1.5 Listas Multidimensionais

Uma lista em Python pode conter outras listas no seu interior. Quando isso
acontece, forma-se uma estrutura multidimensional, utilizada para
representar matrizes, tabelas, grelhas de dados, tabuleiros de jogos etc.
Tal como acontece em qualquer lista em Python, a contagem dos índices
inicia em 0. Isso aplica-se tanto à lista principal como a cada sub-lista que
esta contém.
No exemplo seguinte, a lista principal recebe o nome matriz. O acesso a
um elemento específico faz-se indicando dois índices em sequência: primeiro
o da linha, depois o da coluna.
Também é possível percorrer todos os elementos com ciclos for aninhados,
em que o primeiro ciclo percorre as linhas e o segundo percorre os elementos
dentro de cada linha.

157
7.2 Tuplos

Um tuplo é uma sequência ordenada de elementos, semelhante a uma lista.


A principal diferença é que os tuplos são imutáveis: depois de criados, não
podem ser alterados. Isso significa que não é possível adicionar, remover ou
modificar os seus elementos.
Um tuplo é definido com parênteses (), sendo os elementos separados por
vírgulas. A sintaxe geral para criar um tuplo é a seguinte:
nome_tuplo = (elemento1, elemento2, elemento3, ...)

Tal como as listas, também é possível criar um tuplo vazio, ou seja, não tem
elementos, mas continua a ser um objeto do tipo tuple, cuja sintaxe é:
nome_tuplo = ()

158
Entretanto, antes de apresentar um exemplo, tenha em mente que um tuplo
funciona como uma lista imutável:
− Os elementos são ordenados
− O acesso faz-se por índices numéricos
− A contagem dos índices inicia em 0
− Tal como nas listas, também é possível usar índices negativos. O índice
-1 corresponde ao último elemento, -2 ao penúltimo, e assim por diante.

Neste exemplo a seguir é possível observar que o acesso aos elementos de


um tuplo pode ser feito tanto com índices positivos (a contar do início) como
com índices negativos (a contar do fim). Caso seja indicado um índice que
não existe, ocorre um erro de execução.

Um tuplo vazio não serve para armazenar valores, já que não pode ser
alterado depois de criado. No entanto, pode ser útil em contextos específicos,
como quando uma função precisa devolver um tuplo vazio para indicar que
não encontrou resultados. Veja este exemplo a seguir.

159
Um detalhe importante surge quando se cria um tuplo com apenas um único
elemento. Nesse caso, é necessário incluir uma vírgula após o valor, caso
contrário o Python interpreta apenas como o próprio valor entre parênteses,
e não como um tuplo, como no exemplo a seguir.
um_elemento = (elemento,) # atenção à vírgula

O tuplo unitário não é comum no dia a dia, mas existe para manter
consistência em situações em que se espera sempre um tuplo, como no
retorno de funções.

7.2.1 Utilidade e Vantagens dos tuplos

A principal característica dos tuplos é a sua imutabilidade. Mas isso não é


uma limitação, e sim uma garantia de integridade, pois assegura que os dados

160
permanecem estáveis depois de criados. Essa estabilidade é essencial em
contextos onde a alteração dos valores não faz sentido, como no registo de
coordenadas geográficas, datas, entradas fixas ou em identificadores
compostos que servem como chave em dicionários. Nessas situações, o tuplo
atua como uma estrutura segura, sem modificações acidentais e a preservar
a consistência da informação em todo o programa. Dentre as principais
vantagens, cita-se:
− Integridade de dados garantida → um tuplo não é alterado
acidentalmente, sendo crucial para representar informações que devem
ser constantes durante a execução do programa
− Segurança como chaves em dicionários → a imutabilidade permite que
os tuplos sejam usados como chaves em dicionários, ao contrário das
listas. Isso é extremamente útil para criar mapeamentos complexos
− Retorno de múltiplos valores de funções → é uma prática comum usar
tuplos para que uma função retorne vários valores de uma vez.
Entre os exemplos práticos do uso, citam-se:
− Representar entidades imutáveis:
⋅ Coordenadas em um espaço 2D ou 3D: ponto = (x, y, z)
⋅ Data específica: data_nascimento = (26, 12, 1924)
⋅ Localização por GPS: gps = (-21.5356, -43.1986)
− Chaves compostas em dicionários (mais detalhes no tópico 7.3):

161
Pode-se também retornar múltiplos valores. Entretanto, a necessidade de
aceder os valores por índices (resultado[0], resultado[1]), como
neste próximo exemplo, ilustra uma limitação prática desta abordagem, pois
o programador precisa lembrar a ordem dos elementos no tuplo.

No próximo tópico, está a operação de desempacotamento que resolve


elegantemente o problema de lembrar a ordem dos elementos no tuplo, e
permite atribuir esses valores a variáveis descritivas de forma direta e clara.

7.2.2 Desempacotamento de Tuplos (Unpacking)

O desempacotamento é uma funcionalidade que permite atribuir os


elementos de um tuplo, ou de qualquer sequência, diretamente a um conjunto
de variáveis de forma concisa e legível. Esta operação de desempacotamento
associa cada elemento do tuplo, pela sua ordem, a uma variável
correspondente.

162
Em vez de aceder as posições com índices (dados[0], dados[1], ...),
se associa cada valor a uma variável distinta de forma concisa e legível.
A sintaxe básica consiste em colocar, do lado esquerdo da atribuição, um
conjunto de variáveis separadas por vírgulas, correspondendo à mesma
quantidade de elementos existentes no tuplo do lado direito.
variavel1, variavel2, variavel3 = tuplo
Dessa forma, cada variável recebe o valor correspondente na ordem em que
aparece no tuplo. Essa técnica é especialmente útil quando se trabalha com
dados agrupados, pois evita confusão com índices e melhora a clareza do
código. Portanto, o exemplo anterior, está aqui a ser reescrito utilizando o
conceito de desempacotamento:

Para além do uso básico, o desempacotamento em Python oferece algumas


técnicas avançadas que tornam o código ainda mais flexível, conforme as
alíneas a seguir.
a) Ignorar Valores com _
Quando nem todos os elementos do tuplo são necessários, é comum usar o
underscore (_) para ignorar valores que não serão utilizados.
Veja este exemplo a seguir.

163
b) Ignorar Múltiplos Valores com *_
Se for necessário descartar vários elementos ao mesmo tempo, é possível
utilizar *_, conforme apresenta-se o código a seguir.

O operador * é usado para capturar vários elementos duma vez e armazena-


os numa lista. A variável que vem após, o _, recebe todos os elementos do
meio como uma lista, ou seja, os elementos do meio (2, 3, 4, 5, 6)
são então empacotados numa lista [2, 3, 4, 5, 6] e atribuídos a _.

c) Desempacotamento com Operador * (Extended Unpacking)


O operador * também pode ser usado para agrupar múltiplos elementos numa
única variável, geralmente recebendo o nome resto. Essa técnica é útil
quando não se sabe o tamanho exato do tuplo ou quando apenas parte dos
dados interessa ao programador, como no exemplo a seguir.

164
d) Desempacotamento com ciclos for
É muito comum usar desempacotamento em estruturas de repetição. Quando
uma lista contém tuplos, o Python permite que cada tuplo seja
automaticamente separado em variáveis.
Perceba no exemplo a seguir que o desempacotamento nome, idade no
ciclo for torna o código mais limpo e legível do que se usar índices em cada
iteração.

e) Erros comuns a evitar


Um dos erros mais frequentes ocorre quando o número de variáveis não
corresponde ao número de elementos do tuplo. Nesse caso, o Python gera um
ValueError.
Veja o exemplo com tal erro logo a seguir.

165
7.2.3 Iterar sobre tuplos

Tal como acontece com listas, os tuplos também podem ser percorridos com
um ciclo for. Isso permite trabalhar elemento a elemento de forma simples.
No tópico anterior, na alínea d, foi apresentado um caso especial de iteração
em que o Python realiza automaticamente o desempacotamento de cada tuplo
dentro de uma lista, atribuindo os valores diretamente a variáveis distintas.
Agora o foco é no caso mais comum, em que se percorre um tuplo simples,
tratando cada elemento individualmente.
O exemplo a seguir mostra a simplicidade de percorrer um tuplo com ciclo.

166
7.2.4 Tuplos multidimensionais

Um tuplo também pode conter outros tuplos no seu interior, formando uma
estrutura multidimensional. Isso é semelhante ao que acontece com listas
multidimensionais, mas a diferença está no fato dos valores não poderem ser
alterados depois de definidos.
O exemplo seguinte ilustra o funcionamento de um tuplo multidimensional.
Para aceder a um elemento é necessário indicar dois índices em sequência: o
primeiro corresponde à linha e o segundo à coluna. Diferente das listas, o
conteúdo permanece fixo, ou seja, não é possível alterar matriz[0][1]
nem acrescentar novos elementos após a criação do tuplo.

Um outro exemplo, mais contextualizado, permite perceber que tuplos


multidimensionais não servem apenas para números em forma de matriz.
Pode, como neste exemplo, representar uma tabela de alunos ou uma agenda
semanal, em que cada subtuplo guarda informações diferentes.

167
7.3 Conjuntos (set)

Um set é uma coleção não ordenada e mutável de elementos únicos. A sua


principal utilidade é eliminar duplicados de forma eficiente e realizar
operações matemáticas de teoria de conjuntos, como união, interseção e
diferença.
Ao contrário das listas e tuplos, os conjuntos não suportam indexação nem
fatiamento, pois os elementos não estão armazenados numa ordem
específica.

7.3.1 Criar um set

Para criar um conjunto basta usar chavetas {} com os elementos separados


por vírgulas e com a seguinte sintaxe geral:
nome = {elemento1, elemento2, elemento3, ...}
168
Veja este exemplo que apresenta um set com um dos produtos repetido.
Embora rato apareça duas vezes na declaração, o conjunto guarda apenas
uma ocorrência, eliminando a duplicação.

No exemplo anterior observa-se que os elementos duplicados não são


mantidos e que, por se tratar de uma coleção não ordenada, a disposição dos
valores pode variar a cada execução ou de acordo com a forma como o
Python organiza internamente o conjunto. Este fato é visível no print()
de um set().
Se for necessário apresentar os elementos com uma ordem específica, deve-
se converter o conjunto numa lista e aplicar ordenação, como no exemplo a
seguir.

Para além, se o programador necessitar criar um conjunto vazio, deve-se usar


a função set(), pois as chavetas vazias {} criam um dicionário, a ser visto
no tópico 7.4. A sintaxe para criar um set vazio é a seguinte:
conjunto_vazio = set()

169
7.3.2 Operações matemáticas em conjuntos

Os conjuntos em Python implementam diretamente as operações clássicas da


teoria dos conjuntos, o que os torna ferramentas indispensáveis não apenas
para processamento de dados, mas também para aplicações matemáticas e
lógicas. A Tabela 12 resume as operações mais comuns, apresentando tanto a
sintaxe com operadores quanto com métodos, bem como a sua descrição.
Tabela 12 - Principais operações com conjuntos em Python

Operação Operador Método Descrição


Une todos os
União A | B [Link](B) elementos de ambos
os conjuntos

A & B [Link](B) Elementos comuns a


Interseção
ambos os conjuntos

A - B [Link](B) Elementos em A que


Diferença
não estão em B
Elementos que estão
Diferença. A ^ B A.symmetric_difference(B) em A ou em B, mas
Simétrica
não em ambos
Verifica se todos
Sub- A <= B [Link](B) elementos de A estão
conjunto
contidos em B.
Verifica se todos
Super- A >= B [Link](B) elementos de B estão
conjunto
contidos em A.
Verifica se os
— [Link](B)
conjuntos não têm
Disjunção
elementos em
comum.

Na coluna Operador da Tabela 12 observa-se que muitas destas operações


podem ser expressas através de símbolos matemáticos (|, &, -, ^),

170
tornando o código mais conciso e direto. Essa forma é prática quando se
realizam várias operações encadeadas, tal como numa expressão matemática.
No entanto, os métodos (union(), intersection(), etc.) tornam o
código mais explícito e legível.
A seguir apresenta-se um exemplo simples que demonstra essas operações
na prática.

7.3.3 Métodos úteis em sets

Embora os elementos de um conjunto não tenham ordem e não permitam


duplicados, os conjuntos são estruturas mutáveis, ou seja, é possível
acrescentar, remover ou limpar elementos a qualquer momento. Para isso, o
Python disponibiliza diversos métodos próprios.
A Tabela 13 apresenta os principais métodos para manipulação de conjuntos.
Tabela 13 - Métodos mais usados em conjuntos

171
Método Descrição
add(x) Adiciona o elemento x ao conjunto
remove(x) Remove x; gera erro se não existir
discard(x) Remove x; não gera erro se não existir
clear() Remove todos os elementos, deixando o conjunto vazio
len(set) Devolve o número de elementos no conjunto

issubset() Verifica se todos os elementos de um conjunto estão contidos


noutro (subconjunto)

issuperset() Verifica se um conjunto contém todos os elementos de outro


(superconjunto)
isdisjoint() Verifica se dois conjuntos não têm elementos em comum

Segue um exemplo único com todos os métodos apresentados na Tabela 13-

Neste exemplo observa-se como é possível adicionar, remover, verificar o


tamanho e esvaziar completamente um conjunto, bem como comparar dois
sets.

172
7.4 Dicionários (dict)

Um dicionário é uma coleção de pares “chave → valor”. Cada chave deve


ser única e funciona como um identificador para aceder ao valor associado.
Essa estrutura é muito útil quando é necessário guardar informações
organizadas de forma que cada elemento possa ser recuperado pelo seu nome
em vez de buscar por um índice numérico, como acontece nas listas e tuplos.

7.4.1 Criar e aceder a dicionários

Um dicionário em Python é definido com chavetas {}. Cada entrada é


composta por uma chave e um valor, separados por dois pontos (:). Quando
existem várias entradas, estas são separadas por vírgulas.
A sintaxe geral para criar um dicionário é:
nome_dicionario = {
chave1: valor1,
chave2: valor2,
chave3: valor3,
...
}
Considere como exemplo de criação e uso de um dicionário o exemplo a
seguir.

173
7.4.2 Adicionar e alterar valores

Os dicionários são mutáveis, portanto, é possível adicionar novos pares ou


alterar valores de chaves já existentes, como a seguir.

Este exemplo anterior evidencia duas limitações: por um lado, a declaração


do dicionário fica pouco legível; por outro, a impressão direta de toda a
agenda não apresenta os dados de forma agradável.
A seguir, mostra-o com o dicionário mais organizado e uma melhor saída no
ecrã.
Vale ressaltar que a Tabela 14 apresentará alguns métodos úteis para manipular
e consultar em dicionários, tal como o método items() que é utilizado no
exemplo.

174
7.4.3 Métodos úteis

Os dicionários disponibilizam vários métodos para manipulação e consulta.


A Tabela 1Tabela 14 apresenta alguns destes métodos.
Tabela 14 - Métodos úteis para manipulação e consulta em dicionários

Método Descrição
keys() Devolve todas as chaves do dicionário
values() Devolve todos os valores
items() Devolve pares (chave, valor)
get(chave) Devolve o valor da chave ou None se não existir
pop(chave) Remove a chave e devolve o valor associado
update(...) Atualiza ou insere pares chave → valor
clear() Remove todos os elementos

175
Um exemplo com a aplicação dos métodos listados na Tabela 14 é
apresentado a seguir. Para tornar a explicação mais concreta, escolheu-se
como cenário um dicionário de cartas de baralho, no qual cada carta funciona
como a chave e o respetivo valor numérico corresponde ao valor atribuído
no jogo.
Esse exemplo a seguir permite observar como cada método atua sobre o
dicionário. Desde a simples consulta de chaves e valores até a remoção de
elementos e limpeza completa da estrutura. Assim, além de compreender a
descrição teórica de cada método, é possível visualizar sua execução.

176
7.5 Comparação e Boas Práticas

A escolha da estrutura de dados adequada é uma das decisões mais


importantes no desenvolvimento de software. Cada estrutura em Python foi
confecionada para resolver problemas específicos, e compreender as suas
características distintivas é fundamental para escrever código eficiente,
legível e manutenível.

177
As Tabela 15 e Tabela 16 apresentam, respetivamente, uma breve comparação
entre algumas propriedades e características das estruturas apresentadas
neste capítulo.
Tabela 15 - Propriedades das Estruturas

Estrutura Mutabilidade Ordenação Indexação


Lista Mutável Ordenada Por índice
Tuplo Imutável Ordenada Por índice
Conjunto Mutável Não ordenada Não suportada
Dicionário Mutável Ordenada* Por chave

* Deve-se ter atenção ao fato de que nos dicionários, a ordem dos elementos
só passou a ser preservada a partir do Python 3.7. Antes disso, a
linguagem não garantia a ordem de inserção das chaves.

Tabela 16 - Outras Características das Estruturas

Estrutura Duplicados Sintaxe Casos de Uso Típicos

[1, 2, 3] Coleções que mudam


Lista Permitidos
frequentemente
Tuplo Permitidos (1, 2, 3) Dados fixos e constantes

{1, 2, 3} Eliminar duplicados,


Conjunto Proibidos
operações matemáticas
Dicionário Chaves únicas {"a": 1, "b": 2} Mapeamentos chave → valor

7.5.1 Guia Prático de Seleção por Cenário

a) Quando usar listas:


− A coleção precisa crescer ou diminuir dinamicamente
− A ordem dos elementos é semanticamente importante

178
− Precisas de aceder elementos por posição numérica
− Vais frequentemente adicionar, remover ou reordenar elementos

A seguir há 3 exemplos práticos:

a) Quando usar tuplos:


− Os dados devem permanecer constantes após a criação
− Precisas de usar a coleção como chave em dicionários
− A estrutura representa um registo fixo (como coordenadas, datas)
− Queres garantir a integridade dos dados contra modificações acidentais.

Observe os exemplos seguintes, em especial o último exemplo, onde os


tuplos são utilizados como chaves de um dicionário.

179
b) Quando usar conjuntos (sets):
− Precisas eliminar duplicados automaticamente
− A ordem dos elementos é irrelevante
− Vais realizar operações de teoria de conjuntos (união, interseção)
− Precisas verificar rapidamente se um elemento existe (in).

Os conjuntos são especialmente úteis em situações práticas do dia a dia da


programação. Permitem eliminar duplicados automaticamente, identificar
interseções entre grupos de elementos e realizar verificações de existência de
forma extremamente eficiente. Os exemplos seguintes ilustram esses usos
em diferentes contextos: uma lista de tags sem repetições, um sistema de
interesses comuns entre utilizadores e a deteção rápida de palavras
reservadas numa linguagem de programação.

c) Quando usar dicionários (dict):


− Precisas mapear chaves descritivas a valores
− A estrutura representa um objeto ou registo com atributos

180
− O acesso por nome é mais significativo que por índice numérico
− Precisas de agrupar informações relacionadas sob um mesmo contexto.

Os dicionários mostram todo o seu potencial quando utilizados para


representar dados estruturados através de pares chave → valor. Essa
flexibilidade permite guardar informações nomeadas, definir parâmetros de
configuração ou até mesmo contar ocorrências de elementos em textos ou
coleções de dados. Nos exemplos seguintes, um dicionário descreve um
perfil de utilizador com atributos, outro armazena configurações de uma
aplicação e um terceiro realiza a contagem de frequência de palavras.

Em resumo, a escolha da estrutura correta não é apenas uma decisão técnica,


mas uma opção de design que influencia a legibilidade, a manutenção e o
desempenho do código. Listas servem para coleções ordenadas e mutáveis,
tuplos garantem integridade de dados, conjuntos são ideais para eliminar

181
duplicados e verificar pertença rapidamente, enquanto dicionários permitem
mapear nomes a valores de forma clara e eficiente.

7.5.2 Considerações de Desempenho e Eficiência

Além das diferenças de organização e de funcionalidades, as estruturas de


dados também variam em termos de desempenho. Em coleções pequenas,
essas diferenças quase não se notam, mas quando o volume de dados cresce,
escolher a estrutura certa pode ter grande impacto na rapidez do programa.
A Tabela 17 compara o comportamento das operações mais comuns em listas,
conjuntos e dicionários de forma intuitiva. O tuplo não aparece nessa
comparação porque, por ser imutável, ele não tem operações como adicionar
ou remover elementos.
Tabela 17 - Comparação de desempenho em estruturas

Operação Lista Conjunto Dicionário


Procurar elemento Mais lenta Muito rápida Muito rápida
Acesso direto a posição Muito rápido Não aplicável Muito rápido (pela chave)
Inserir novo elemento Muito rápido Muito rápido Muito rápido
Remover elemento Mais lenta Muito rápida Muito rápida

Na prática estas estruturas podem assim serem usadas:


− Para verificar a existência de elementos em coleções grandes, conjuntos
e dicionários são muito mais eficientes do que listas
− Para guardar valores em sequência e aceder por posição, as listas
continuam a ser a melhor opção

182
− Conjuntos são imbatíveis quando é preciso eliminar duplicados ou
trabalhar com operações matemáticas de união, interseção e diferença
− Dicionários são ideais quando é necessário associar um valor a uma
chave nomeada, como num mapa de contactos ou numa tabela de
configurações.

7.6 Uso Prático de lambda em Coleções

As funções lambda, apresentadas no Capítulo 6, revelam todo o seu potencial


quando aplicadas em estruturas de dados como listas, tuplos, conjuntos e
dicionários. A sua principal vantagem é permitir a criação de pequenas
funções anónimas, usadas apenas quando são necessárias, sem a obrigação
de definir uma função completa com def.
Em coleções, o uso de lambda é particularmente comum em três contextos:
1. Ordenação personalizada com key = → a função sorted() e o método
.sort() aceitam o parâmetro key, que define a regra de comparação
para ordenar os elementos. Usar lambda nesse parâmetro permite
ordenar de acordo com qualquer critério, como um campo de um tuplo
ou o valor associado a uma chave num dicionário.
Veja este exemplo de uso do lambda para ordenar o resultado pelo
nome. O comando lambda x: x[1] significa que para cada
elemento x (que é um tuplo) vai usar o valor na posição 1, ou seja, a
idade, para ordenar.

183
2. Filtragem de elementos → a função filter()serve para selecionar
elementos de uma coleção com base numa condição. Recebe dois
argumentos: uma função que representa o critério e a coleção a ser
analisada. O resultado é uma sequência filtrada que contém apenas os
elementos que satisfazem a condição. Ao usar lambda, a condição pode
ser escrita de forma compacta e direta.
Neste exemplo a seguir são filtrados produtos com preço maior que 50.
O comando lambda produto: produto[1] > 50 atua como um
teste, onde cada elemento só é mantido se esta condição for verdadeira.

3. Transformação de valores → A função map() aplica uma função a


todos os elementos de uma coleção, devolvendo uma nova sequência
transformada. A map() também recebe dois argumentos: uma função
que define a transformação e a coleção original. O resultado é uma

184
sequência com todos os valores já modificados. O uso de lambda nesse
caso facilita a escrita de transformações rápidas e legíveis.
Veja um exemplo a seguir que aplica um desconto de 8% sobre todos os
produtos. O comando lambda preco: preco * 0.8 é a
transformação aplicada a cada elemento da lista original.

Pelos exemplos anteriores, nas alíneas 2 e 3, podes ter ficado em dúvida


sobre a diferença entre filter() e map(). Isso é normal, pois ambas
trabalham com coleções e usam lambda. Entretanto a distinção crucial é
simples. O filter() filtra os elementos e o map() modifica-os.
Pense desta forma: filter() é como uma peneira, pois deixa passar só o
que interessa, sem alterar o que passa. Já o map() é como um transformador,
pois altera cada elemento em outra coisa. Veja o exemplo a seguir.

185
Mas estes comandos estão a ser usados com o lambda, que tem clareza e
concisão. Em vez de ocupar várias linhas com uma função definida apenas
para ser usada uma única vez, o programador consegue expressar a regra de
forma compacta, dentro da própria chamada.
Contudo, é importante não abusar deste recurso. Quando a lógica for
demasiado longa ou complexa, é preferível criar uma função tradicional com
def, pois a legibilidade deve ser sempre uma prioridade.

7.7 Exemplos práticos

Para além da teoria e da apresentação dos métodos, é fundamental observar


como as estruturas de dados se aplicam em situações do dia a dia. Python
oferece soluções simples e expressivas para problemas como as aqui
exemplificadas. Os exemplos seguintes ilustram casos práticos onde listas,
tuplos, conjuntos e dicionários demonstram a sua utilidade de forma clara e
eficiente.

7.7.1 Análise de Redes Sociais (Conjuntos)

Os conjuntos também são bastante úteis em aplicações ligadas a redes


sociais, onde é comum lidar com seguidores, amigos e interesses partilhados.
Graças às operações de interseção e diferença, é possível identificar
rapidamente amigos em comum entre dois utilizadores ou até mesmo gerar
sugestões de novas amizades. O exemplo seguinte mostra como isso pode
ser feito de forma simples e eficiente em Python.

186
7.7.2 Sistema de conquistas em jogos

Os dicionários permitem organizar dados de forma estruturada, associando


cada chave a um conjunto de atributos. Esse recurso é particularmente útil
em sistemas de jogos, onde conquistas ou missões podem ter um nome, uma
pontuação de experiência (XP) e um estado de desbloqueio.
No exemplo seguinte, um dicionário guarda diferentes conquistas e, ao
percorrê-lo, o programa calcula o XP total acumulado apenas com as que já
foram desbloqueadas.

187
7.7.3 Playlist Inteligente

Outro cenário comum em aplicações modernas é o de sistemas de música,


nos quais é necessário organizar e manipular coleções de faixas. Um
dicionário dentro de uma lista pode representar cada música com atributos
como artista, título e género. A partir daí, é possível aplicar funções como
shuffle() para embaralhar a ordem e gerar uma experiência dinâmica
para o utilizador. O exemplo seguinte mostra como construir uma playlist e
reproduzi-la de forma aleatória.

7.7.4 Controlo de Gastos Pessoais

As estruturas de dados também são muito úteis na área da organização


financeira pessoal, permitindo registar e analisar despesas de forma simples.
Usando uma lista de dicionários, cada gasto pode incluir atributos como

188
categoria, valor e descrição. A partir desses registos, é possível agrupar os
valores por categoria e calcular totais mensais, fornecendo uma visão clara
dos principais focos de despesa. O exemplo seguinte mostra como
implementar esse controlo em Python.

7.7.5 Torneio de e-sports

Os tuplos são ideais para representar dados fixos que não devem ser
alterados, como acontece em torneios ou competições, onde as equipas
inscritas permanecem constantes durante o evento. Cada tuplo pode guardar
informações relevantes, como o nome da equipa, o país de origem e a sua
pontuação de classificação.

189
No exemplo seguinte, uma lista de tuplos é usada para organizar as equipas
participantes num torneio de e-sports, gerar automaticamente o calendário de
partidas e identificar a equipa favorita com base no maior (max) rating.

7.7.6 Análise de hábitos digitais

Os conjuntos são particularmente eficazes na análise de dados do quotidiano,


sobretudo quando o objetivo é identificar padrões de repetição ou medir
diversidade. Um caso típico é o estudo dos hábitos digitais, em que se
pretende descobrir quais aplicações são usadas de forma recorrente e quantas
diferentes aparecem ao longo de um período.
No exemplo seguinte, conjuntos representam as apps mais utilizadas em cada
dia da semana, permitindo encontrar automaticamente a aplicação mais

190
viciante, ou seja, que está presente em todos os dias, e calcular a diversidade
total de apps utilizadas.

7.7.7 Sistemas de Recompensa

As listas, combinadas com técnicas como list comprehensions e a


função sorted(), organizam e classificam informações rapidamente. Esse
recurso é bom em ranking ou classificações, onde os participantes são
ordenados pelo desempenho. Neste exemplo, uma lista de dicionários guarda
os desafios concluídos e o programa gera o top 3 participantes. Exibe os
nomes e os respetivos número de desafios completados.
Neste exemplo, o método reverse=True indica que a ordenação deve ser
feita em ordem decrescente.

191
7.7.8 Match de Amizade

Os dicionários podem ser combinados com conjuntos para construir sistemas


de comparação de perfis, uma funcionalidade comum em plataformas de
redes sociais ou aplicações de relacionamentos. Cada perfil guarda
informações básicas, como o nome, e um conjunto de interesses do
utilizador. Através das operações de interseção e união de conjuntos, é
possível calcular automaticamente os interesses partilhados e gerar uma taxa
de compatibilidade entre dois perfis.
O exemplo seguinte mostra como esse processo pode ser implementado em
Python.

192
7.8 Exercícios propostos

1) Ler uma lista de e-mails e extrair domínio destes emails (list


comprehension)
− Criar nova lista apenas com domínios (após "@"), sem duplicados,
ordenados.
− Exibir o resultado
− Dica: usar split("@")[1] numa compreensão e depois set()

193
2) Criar uma lista de dicionários que registe utilizadores, em que cada
dicionário tenha as chaves user (string) e desafios (inteiro).
− Ordenar essa lista por desafios em ordem decrescente (em caso de
empate, ordenar alfabeticamente por user para manter resultado
determinístico)
− Exibir os três primeiros elementos numerando a partir de 1 no formato
1º Nome — X desafios
− Dica: usar
sorted(lista, key=lambda d: ( -d["desafios"],
d["user"] )) ou
sorted(lista, key=lambda d: d["desafios"],
reverse=True).

3) Criar uma lista de compras inteligente que permite adicionar produtos,


remover itens duplicados automaticamente e ordenar os produtos
alfabeticamente. A saída do programa deve ter este formato:

194
4) Fazer o código de uma agenda como lista de tuplos (data, hora,
título), conforme o exemplo a seguir.
tuplos = [
("2025-10-28", "09:30", "Consulta Médica de Família"),
("2025-11-27", "14:00", "Dentista"),
("2025-11-21", "10:00", "Consultoria Python"),
]
− Ordenar por data e hora
− Exibir cada compromisso no formato apresentado a seguir, um por linha,
desempacotando os tuplos no ciclo for.
− Dica: for data, hora, titulo in agenda.
Exemplo de saída:

5) Faça um programa para processar dados de sensores IoT com listas


multidimensionais que representam as leituras de temperatura como uma
matriz (lista de listas).
− Guarde as leituras de temperatura, em Celsius, de 3 sensores em 4
momentos numa matriz (lista de listas)
− Calcule a média de temperatura por sensor
− Determine a temperatura máxima global registada
− Converta todas as temperaturas de Celsius para Fahrenheit usando
map() e lambda.

195
6) Fazer um programa que funcione como um gerador de equipas para
jogos online, ou seja, dada uma lista de jogadores com ratings, dividir
em duas equipas balanceadas.
− Usar tuplos para armazenar (nome, rating) e ordenar por rating
− Atribuir jogadores alternadamente às equipas, por exemplo, melhor
jogador à equipa A, segundo melhor à B e assim sucessivamente.

7) Fazer um programa em Python que gere uma grelha 5x5 como lista
multidimensional, iniciando todas as posições com o carácter #.
− Colocar o jogador J na posição (linha, coluna), gerada aleatoriamente
− Marcar 3 obstáculos com o carácter | em posições geradas aleatoriamente

196
− Exibir a grelha linha a linha
− Dicas: usar compreensões aninhadas na criação da matriz e considerar
coordenadas baseadas em 0 (de 0 a 4) e ignorar posições fora da grelha.
− Exemplo de saída:

8) Alterar o programa feito no exercício 7 de forma a:


− Ler a posição do jogador J e o colocar o jogador J na posição
− Ler a posição dos 3 obstáculos com o carácter | nas posições fornecidas
pelo utilizador
− Exibir a grelha linha a linha
− Dicas: garantir que os valores selecionados pelo utilizador estejam
dentro da grelha.

197
9) Implementar um sistema de votação para escolher a "Melhor Série do
Ano". Para resolver o problema, usar um dicionário para contar votos
(série: número de votos).
− Permitir votos múltiplos, mas evitar duplicados por utilizador (usar sets).
− Mostrar o top 3 das séries mais votadas.

10) Analisar sentimentos em comentários usando conjuntos, para tal, criar


uma lista de palavras positivas e negativas, contar quantas vezes
aparecem em comentários.
− Usar operações de conjuntos para encontrar palavras neutras (que não
estão em nenhum dos conjuntos).
− Calcular a percentagem de palavras positivas vs. Negativas
Suponha que os comentários sejam estes.
comentarios = [
"O filme é bom e excelente!",
"Que experiência horrível, péssimo serviço.",
"Adoro este restaurante, maravilhoso!",
"Não gostei, muito chato e ruim.",
"Fantástico! Recomendo a todos."
"Apesar de chato, é um bom filme."
]

198
A saída seria algo como apresentado a seguir.

199
8 MANIPULAÇÃO E FORMATAÇÃO DE STRINGS

As strings em Python são sequências de caracteres. Representam desde


palavras simples até textos completos e estão presentes em praticamente
todos os programas.
Este capítulo está organizado em três grandes blocos:
a) Indexação e fatiamento → como aceder a partes específicas da string
b) Métodos de manipulação → operações prontas que facilitam o trabalho
com textos
c) Formatação moderna (f-strings) → a forma mais prática e
expressiva de montar mensagens.

8.1 Indexação e fatiamento

Uma string em Python é uma sequência de caracteres, semelhante a uma lista


ou a uma tupla. Cada caractere ocupa uma posição definida e pode ser
acedido individualmente. Além disso, podemos extrair partes da string
original, obtendo novas sequências chamadas substrings.
Duas operações estão no centro desse processo:
− Indexação – acesso direto a um único caractere através da sua posição.
− Fatiamento (slicing) – obtenção de uma fatia contínua da string, ou seja,
um segmento delimitado por índices.

200
Estas técnicas permitem desde tarefas simples, como mostrar apenas a
primeira letra de um nome, até manipulações mais elaboradas, como inverter
uma frase inteira.
Dominar indexação e fatiamento torna o trabalho com texto em Python mais
poderoso, flexível e expressivo.

8.1.1 Indexação

Os índices em Python começam em 0 e também podem assumir valores


negativos, tal como acontece nas listas e nos tuplos. Dessa forma, o primeiro
elemento de uma string está sempre na posição 0, enquanto o último pode
ser acedido pela posição -1. A utilização de índices negativos facilita o
acesso rápido aos elementos finais da sequência, sem necessidade de calcular
previamente o seu comprimento, conforme no exemplo a seguir.

String P y t h o n
Índice 0 1 2 3 4 5
Índice- -6 -5 -4 -3 -2 -1

Um pequeno exemplo é apresentado na sequência:

201
8.1.2 Fatiamento (slicing)

O fatiamento (slicing) permite extrair uma parte de uma string, criando uma
nova sequência sem alterar a original.
Essa técnica é extremamente útil para manipular texto de forma flexível, seja
para obter palavras, inverter frases ou selecionar apenas os caracteres
desejados.
A sintaxe geral é:
string[início:fim:passo]
onde:
− início → índice onde a extração começa (inclusivo). Se omitido, assume
o início da string (0)
− fim → índice onde a extração termina (exclusivo). Se omitido, assume o
fim da string
− passo → salto entre os índices. Se omitido, assume o valor 1

Dessa forma, é possível controlar não apenas o trecho selecionado, mas


também a direção e o ritmo da leitura da string.
Depois de compreender a sintaxe geral, é importante observar como o
fatiamento se comporta em diferentes situações. A seguir, apresenta-se
exemplos práticos que cobrem os casos mais comuns e também alguns
menos intuitivos.
Cada linha mostra a expressão aplicada sobre a string texto =
"Aprender Python" e o respetivo resultado.

202
8.2 Métodos de manipulação

As strings em Python dispõem de um vasto conjunto de métodos internos,


isto é, funções que já vêm associadas ao próprio objeto string e que podem
ser invocadas diretamente. Esses métodos facilitam tarefas como alterar
maiúsculas e minúsculas, remover espaços, procurar ou substituir trechos de
texto, dividir em listas ou voltar a juntar partes.
Um ponto essencial a compreender é que as strings em Python são imutáveis,
isto é, após criadas, o seu conteúdo não pode ser modificado. Por essa razão,
cada método aplicado a uma string devolve sempre uma nova string,
deixando a original inalterada.
Este comportamento garante maior segurança e previsibilidade no
tratamento de dados textuais.
A seguir apresenta-se, em 4 tabelas distintas, alguns dos métodos mais
usados em strings. Estes métodos são suficientes para a maioria das tarefas
do dia a dia, mas Python oferece muitos outros, que podem ser explorados
na documentação oficial. Cada entrada da tabela mostra o nome do método,

203
um exemplo de utilização e o respetivo resultado, permitindo compreender
de imediato a sua aplicação prática.
A Tabela 18 apresenta os métodos mais comuns para converter o texto entre
maiúsculas e minúsculas. Estes métodos permitem padronizar a escrita ou
ajustar a forma de apresentação dos dados.

Tabela 18 - Alteração de maiúsculas e minúsculas

Método Exemplo Resultado Observação

.upper() "python".upper() 'PYTHON' Caracteres em


maiúsculas

.lower() "PyThOn".lower() 'python' Caracteres em


minúsculas
'Joana Primeira letra
"joana da
.title() Da de cada palavra
silva".title()
Silva' em maiúscula

"python é 'Python Apenas a 1ª


.capitalize()
bom".capitalize() é bom' letra da string
em maiúscula
Inverte
.swapcase() "Python".swapcase() 'pYTHON' maiúsculas e
minúsculas

Na Tabela 19 encontram-se métodos usados para remover espaços em branco


ou caracteres indesejados no início e no fim de uma string. São úteis para
limpar dados lidos do teclado ou de ficheiros.

204
Tabela 19 - Remoção de espaços e caracteres

Método Exemplo Resultado Remove


Espaço branco
.strip() " Olá ".strip() 'Olá' (esquerda e
direita)

.lstrip() " Olá ".lstrip() 'Olá ' Apenas à


esquerda.

.rstrip() " Olá ".rstrip() ' Olá' Apenas à


direita.
Caracteres
.strip("o") "ooPythonoo".strip("o") 'Python' indicados no
início e fim

A Tabela 20 reúne métodos e operadores destinados a substituir partes do


texto, localizar substrings e contar ocorrências. Estes recursos tornam mais
simples analisar e manipular grandes volumes de texto.

Tabela 20 - Substituição e procura

Método Exemplo Resultado Observação


"gato preto".replace 'cão Substitui todas
.replace(a,b)
("gato","cão") preto' as ocorrências
Devolve a
.find(x) "banana".find("na") 2 posição da 1ª
ocorrência

.rfind(x) "banana".rfind("na") 4 Busca a última


ocorrência

.count(x) "banana".count("na") 2 Conta quantas


vezes ocorre.

in "py" in "python" True Testa se há


substring.
"java" not in Testa se não há
not in True
"python" substring.

205
A Tabela 21apresenta métodos que permitem dividir uma string em várias
partes e, no sentido inverso, unir elementos de uma lista numa única string.
São ferramentas essenciais para o processamento de dados estruturados.

Tabela 21 - Divisão e junção

Método Exemplo Resultado Observação

['aa','bb', Divide por


.split() "aa bb cc".split() espaços em
'cc']
branco

.split("," "aa,bb,cc".split("," ['aa','bb', Divide pelo


) ) 'cc'] separador
indicado
Junta
.join(list "-".join elementos de
'aa-bb-cc'
a) (['aa','bb','cc']) uma lista com
separador.

8.3 Formatação moderna (f-strings)

Introduzidas no Python 3.6, as f-strings (formatted string literals)


revolucionaram a formatação de strings em Python, pois estas são não apenas
mais legíveis e concisas, mas também mais performáticas que os métodos
anteriores (% e .format()).
Embora já tenham sido introduzidas no Tópico 4.5.3, no capítulo dedicado à
entrada e saída de dados, as f-strings merecem aqui uma análise mais
aprofundada. A sua versatilidade torna-as indispensáveis para a construção
de mensagens dinâmicas e para a formatação elegante de valores em Python.

206
A sintaxe é simples, pois basta prefixar a string com f ou F e usar chavetas
{} para inserir expressões.

Dentro das chavetas, pode-se incluir qualquer expressão Python válida, como
operações matemáticas, chamadas de métodos, condicionais até aceder a
elementos em listas e dicionários, como no exemplo a seguir.

As f-strings não se limitam a inserir variáveis diretamente em mensagens.


Um dos seus maiores pontos fortes é o controlo rigoroso da formatação
numérica, permitindo exibir valores inteiros, decimais e percentagens de
formas diferentes e consistentes. Esse recurso é particularmente útil em
relatórios, cálculos científicos e aplicações financeiras, onde a apresentação
dos números deve obedecer a regras específicas.

207
A seguir apresenta-se um exemplo que ilustra diferentes possibilidades de
formatação numérica com f-strings:

As f-strings permitem ainda definir a largura do campo, o alinhamento do


conteúdo e até o caractere de preenchimento. Isso é útil para criar saídas
organizadas, como tabelas e relatórios.
Na sintaxe {valor:formato}, a largura do campo é indicada por um
número, e o alinhamento pode ser definido por:
− < → alinhamento à esquerda
− > → alinhamento à direita
− ^ → alinhamento ao centro

208
Opcionalmente, é possível indicar um caractere de preenchimento antes do
especificador de alinhamento.
A seguir apresenta-se um exemplo com diferentes combinações.

As f-strings também suportam a formatação de datas e horas, bastando usar


códigos especiais após os dois pontos (:). Isso torna simples exibir datas
em diferentes estilos, conforme apresentado no exemplo a seguir.

209
A Tabela 22 seguinte reúne os códigos de formatação mais utilizados em
Python.
Tabela 22 - Códigos de formatação de datas e horas

Exemplo
Código Significado
(para 15/01/2024 10:30:45)
%d 15 Dia do mês (2 dígitos)
%m 01 Mês (2 dígitos)
%Y 2024 Ano com 4 dígitos
%y 24 Ano com 2 dígitos
%H 10 Hora no formato 24h (00–23)
%M 30 Minutos (00–59)
%S 45 Segundos (00–59)
%A Monday Nome completo do dia da semana
%a Mon Nome abreviado do dia da semana
%B January Nome completo do mês
%b Jan Nome abreviado do mês

Até aqui apresentou-se exemplos simples de f-strings. Contudo, este


recurso é suficientemente poderoso para lidar com situações mais
complexas, como a definição dinâmica da formatação e a construção de
tabelas bem organizadas.
a) Formatação dinâmica de casas decimais
É possível controlar a quantidade de casas decimais em tempo de execução,
definindo o valor da precisão numa variável e utilizando chaves aninhadas,
como no exemplo a seguir.

210
b) Tabelas formatadas
Outro uso frequente das f-strings é a criação de tabelas alinhadas, úteis
para relatórios ou inventários. No exemplo seguinte, cada coluna é ajustada
com largura e alinhamento específicos.

Cuja saída é a seguinte:

211
No exemplo anterior, observa-se que a instrução print() original foi
adaptada. A linha ficava demasiado extensa para ser escrita de uma só vez,
pelo que foi dividida em várias partes para facilitar a leitura e a manutenção
do código.
Portanto, quando um print() fica muito comprido, há algumas formas de
quebrar a linha no código sem afetar a saída.
A primeira técnica (com parênteses) é como no exemplo anterior, onde se
quebra as linhas dentro dos parênteses.
A segunda maneira é usar a barra invertida \. A \ indica que a instrução
continua na linha seguinte, conforme este exemplo:

Uma terceira forma consiste na concatenação implícita de strings. Para isso,


basta colocar as strings lado a lado entre aspas, que o Python as junta
automaticamente. Esta técnica é semelhante à primeira técnica apresentada,
mas apresenta menos clareza visual, já que o texto fica imediatamente ao
lado do parêntese, como no exemplo seguinte:

Das três técnicas apresentadas, a mais recomendada hoje em dia é a opção 1


(com parênteses), porque fica limpa e clara, sem precisar de \.

212
8.4 Casos Resolvidos e Explicados

É importante apresentar a resolução de alguns problemas comuns


envolvendo strings. Cada caso será apresentado com o enunciado, a proposta
de resolução comentada, bem como uma breve explicação das técnicas
aplicadas. O objetivo é mostrar o raciocínio por trás do código.

8.4.1 Normalização de Nomes de Utilizador

Um programa que solicita um nome de utilizador. Este nome deve estar em


letras minúsculas, sem espaços no início ou fim e que quaisquer espaços
internos sejam substituídos por underscores (_). Por exemplo:
“ Fernando Pessoa “ deve tornar-se “fernando_pessoa”.

Métodos utilizados:
1. .strip() → Remover espaços em branco do início e do fim da string.
2. .lower()→ Converter os caracteres alfabéticos para minúsculas
3. replace(" ", "_") → Substituir cada ocorrência de um espaço
por um underscore
O ponto chave é a imutabilidade das strings que permite encadear métodos.
Cada método retorna nova string, que é usada como entrada para o próximo.

213
8.4.2 Extração de Informação de um E-mail

Dado um endereço de e-mail, extrair o nome de utilizador (parte antes do @)


e o domínio (parte depois do @). Ocorre erro se não houver @ no e-mail.

Os métodos utilizados são:


1. .find('@') → Localiza o índice de um caractere específico dentro da
string

214
2. string[:indice] → Fatiamento para obter uma substring do início
até (mas não incluindo) o índice
3. string[indice+1:] → Fatiamento para obter uma substring a
partir do índice seguinte até o final.

O fatiamento é uma ferramenta poderosa para extrair secções de uma string


quando se conhece as posições relativas dos elementos.

8.4.3 Estatísticas de Jogadores - Tabela de Classificação

Neste exemplo se cria uma tabela de classificação para um jogo online que
mostra o nome do jogador, sua pontuação (K/D - Eliminações/Mortes) e o
nível. O nome deve ter 20 caracteres alinhados à esquerda, a pontuação K/D
deve ter 8 caracteres centralizados com 2 casas decimais e o nível deve ter 6
caracteres alinhados à direita.
A saída do programa deve ser a seguinte:

215
O código é apresentado a seguir.

216
Alguns exemplos das técnicas aplicadas:
1. f"{nome:<15}" → Alinhado à esquerda e 15 caracteres de largura
2. f"{kd:>6.2}" → K/D alinhado à direita com 6 caracteres e 2 casas
decimais
Para além destas técnicas, há neste exemplo:
217
− Cálculo de K/D com proteção contra divisão por zero
kd = elim / mortes if mortes > 0 else elim
− Extração sequencial de dados
for nome, elim, mortes, nivel in jogadores:
− Acumulação progressiva de totais
total_eliminacoes += jogador[1]
− Uso de desempacotamento de tuplos para aceder direto às variáveis
− Cálculo condicional seguro para evitar erros de divisão por zero
− Formatação tabular profissional com alinhamento consistente
− Separadores visuais com | para organizar colunas.

8.5 Exercícios Propostos

1) Escreva um programa para ler repetidamente um nome completo (sem


abreviar) e imprima as iniciais em maiúsculas, separadas por pontos,
ignorando as partículas de, da, das, do, dos e a conjunção e.
− A leitura termina quando o utilizador carregar só em Enter (linha vazia).
− Deve também considerar nomes hifenizados, como por exemplo,
“Maria-Clara Pereira” → “M.C.P.”)
− Dicas:
⋅ Separar por espaços: tokens = [Link]().split()
⋅ Dividir também por hífen:
partes = []
for t in tokens: partes+= [p for p in [Link]('-') if p]
⋅ Conjunto de partículas a ignorar:

218
PARTICULAS = {"de","da","das","do","dos","e"}
⋅ Filtrar partículas (sem diferenciar maiúsculas/minúsculas):
partes_validas = [p for p in partes if [Link]() not in
PARTICULAS]
⋅ Obter a primeira letra alfabética de cada parte:
letra = next((ch for ch in p if [Link]()), "")
⋅ Construir as iniciais:
inic = '.'.join([Link]() for letra in letras if
letra) + '.'
⋅ Terminar a leitura quando a linha estiver vazia depois de strip():
if not [Link](): break
Exemplo de saída:

2) Implemente um tradutor bidirecional que converte texto para código


Morse e vice-versa. O programa deve ter as seguintes funcionalidades:
− Detetar automaticamente se o input é texto ou Morse
− Suportar letras A-Z, números 0-9 e espaço
− Usar '/' para separar palavras em Morse
− Ignorar caracteres não suportados
Esta é a tabela de código morse:

219
morse_code = {
'A': '.-', 'B': '-...', 'C': '-.-.', 'D': '-..', 'E': '.',
'F': '..-.', 'G': '--.', 'H': '....', 'I': '..', 'J': '.---',
'K': '-.-', 'L': '.-..', 'M': '--', 'N': '-.', 'O': '---',
'P': '.--.', 'Q': '--.-', 'R': '.-.', 'S': '...', 'T': '-',
'U': '..-', 'V': '...-', 'W': '.--', 'X': '-..-', 'Y': '-.--',
'Z': '--..', ' ': '/', '1': '.----', '2': '..---', '3': '...--',
'4': '....-', '5': '.....', '6': '-....', '7': '--...',
'8': '---..', '9': '----.', '0': '-----'
}
Estas são as funções sugeridas:
def texto_para_morse(texto):
# TODO: Converter cada caractere para Morse
# TODO: Juntar códigos com espaço entre letras
# TODO: Usar '/' entre palavras
pass
def morse_para_texto(codigo_morse):
# TODO: Criar dicionário reverso
# TODO: Separar palavras por ' / '
# TODO: Separar letras por espaços
# TODO: Traduzir cada código Morse
pass
def main():
# TODO: Menu com opções
# TODO: Input do usuário
# TODO: Detectar direção automaticamente
# TODO: Mostrar resultados formatados
pass

220
# Adicionar estes testes:
# print(texto_para_morse("PYTHON"))
# Deve retornar: ".--. -.-- - .... --- -."
# print(morse_para_texto(".--. -.-- - .... --- -."))
# Deve retornar: "PYTHON"

Exemplo de saída:

221
3) Desenvolva um programa que receba um texto do utilizador e conte o
número de palavras, de frases (terminadas com .!?) e parágrafos
(separados por \n). Identificar a palavra mais longa e calcular o
comprimento médio das palavras
Este é um exemplo de saída:

222
9 FICHEIROS EM PYTHON

Trabalhar com ficheiros é essencial para armazenar e recuperar dados de


forma persistente. Sempre que o programa termina, as variáveis desaparecem
da memória, mas os ficheiros permitem guardar resultados para serem
utilizados mais tarde.
Um simples bloco de notas pode transformar-se numa base de dados de
contactos, num registo de transações ou num diário digital.

9.1 Abertura de ficheiros com open()

Para manipular um ficheiro é necessário iniciar por abri-lo. Isso faz-se


através da função open(), que recebe o nome do ficheiro e o modo de
abertura e, além destes parâmetros, aceita importantes parâmetros opcionais:
− encoding → define a codificação do ficheiro, como por exemplo,
utf-8, latin-1
− errors → controla como lidar com erros de codificação (strict,
ignore, replace)
− newline → ajusta a forma como quebras de linha são tratadas entre
sistemas operativos
− buffering → controla o uso de memória intermédia para
leitura/escrita
− x → cria um novo ficheiro e falha se já existir, evitando sobre gravação
acidental.

223
9.1.1 Nome e extensão de ficheiros

O nome pode ser apenas o do ficheiro, desde que esteja na mesma pasta do
programa, ou o caminho completo. O modo define se o ficheiro será lido,
escrito ou acrescentado.
Um ficheiro normalmente possui uma extensão, que ajuda a identificar o tipo
de conteúdo, como por exemplo:
− [Link] → ficheiro de texto simples
− [Link] → texto em colunas separadas por vírgulas
− [Link] → ficheiro binário de imagem
− [Link] → ficheiro binário genérico
A extensão não altera a forma como Python lê o ficheiro, mas serve como
convenção para o utilizador e para o sistema operativo reconhecer o tipo de
dados.
Uma vez aberto, o ficheiro comporta-se como um objeto em memória, que
pode ser manipulado com métodos de leitura e escrita. É responsabilidade do
programador fechar o ficheiro depois de o utilizar.

Além do nome e da extensão, é importante compreender como Python trata


com caminhos de ficheiros. O diretório de trabalho atual (cwd – current
working directory) é o local padrão onde Python procura os ficheiros, caso
seja indicado apenas o nome.
− Caminho relativo: refere-se à posição em relação ao diretório atual.
− Caminho absoluto: indica o percurso completo no sistema de ficheiros.
Existem duas abordagens principais para trabalhar com caminhos de
ficheiros em Python.
224
A mais tradicional é o módulo [Link], que faz parte da biblioteca padrão
desde versões antigas da linguagem e utiliza funções avulsas para
manipulação de caminhos. Já a mais moderna e recomendada é a classe
[Link], que oferece uma API orientada a objetos, mais legível e
intuitiva, permitindo usar operadores e métodos diretamente sobre os
caminhos.
A forma mais tradicional de manipular caminhos em Python é através do
módulo os, que faz parte da biblioteca padrão desde as primeiras versões da
linguagem. Dentro há a sub-biblioteca [Link] fornece funções avulsas
para trabalhar com caminhos de ficheiros, utilizando cadeias de caracteres
(str). Essa abordagem continua funcional e é amplamente encontrada em
programas mais antigos.
Neste exemplo, a seguir, utiliza-se o módulo [Link] para realizar três
operações básicas. A função abspath() devolve o caminho absoluto do
ficheiro, ou seja, o percurso completo desde a raiz do sistema até ao ficheiro
em questão. A função dirname() devolve o diretório onde o ficheiro está
localizado, permitindo saber em que pasta ele se encontra. Já a função
exists() verifica se o ficheiro realmente existe no disco, devolvendo
True ou False conforme o caso.

225
Este estilo exige chamadas independentes para cada operação, o que pode
tornar o código mais verboso. Ainda assim, é bastante útil quando se mantém
compatibilidade com versões antigas de Python ou quando se trabalha em
projetos que já utilizam extensivamente o módulo os.
A alternativa mais moderna é a biblioteca pathlib, introduzida
oficialmente no Python 3.4 e hoje considerada a abordagem recomendada.
Diferente do [Link], a classe Path trata cada caminho como um objeto,
oferecendo uma API orientada a objetos, clara e intuitiva. Uma das vantagens
é que o operador / pode ser usado para unir diretórios e ficheiros de forma
natural.
No exemplo a seguir, repete-se as mesmas operações, mas com pathlib.
O método resolve() devolve o caminho absoluto, tal como abspath()
no caso anterior. A propriedade parent indica a pasta mãe do ficheiro,
equivalente a dirname().

226
9.1.2 Texto vs. Binário

Python permite abrir ficheiros tanto em modo texto como em modo binário.
No modo texto, a leitura e a escrita trabalham com cadeias de caracteres,
ideais para ficheiros como .txt ou .csv. Já no modo binário, os dados são
tratados exatamente como estão gravados, sem conversões, o que é essencial
para imagens, áudio, ficheiros comprimidos ou outros formatos não textuais.
A distinção é feita através da presença da letra b no modo de abertura. Se o
modo inclui a letra b, o ficheiro é tratado como binário (rb, wb, ab,
r+b, w+b, a+b).

Se não for indicada a forma de abertura do ficheiro, assume-se por defeito


que se trata de um ficheiro de texto, utilizando os modos r, w, a, r+.
A Tabela 23 apresenta de forma comparativa as principais formas de abertura
de um ficheiro em Python, distinguindo entre texto e binário.

227
Tabela 23 - Modos principais de abertura de ficheiros em Python

Modo Modo
Código Descrição
texto binário

open("[Link]", "r") r rb Lê apenas. O ficheiro deve


existir

open("[Link]", "w") w wb Escreve. Cria novo ficheiro


ou apaga o existente
Escreve. Acrescenta no final
open("[Link]", "a") a ab do ficheiro, sem apagar o
conteúdo anterior

open("[Link]","r+") r+ r+b Lê e escreve. O ficheiro


deve existir

open("[Link]","w+") w+ w+b Lê e escreve. Cria ficheiro


ou apaga o existente

open("[Link]","a+") a+ a+b Lê e escreve. Mantém o


conteúdo e escreve no final

Nos modos a+ e r+, o ponteiro de leitura/escrita pode não estar onde o


utilizador espera. Por exemplo, em a+ o ponteiro fica no fim do ficheiro. Se
desejar reler desde o início após escrever, deve usar seek(0). Este
comando será devidamente explicado do tópico 9.7.

Os exemplos de abertura de ficheiro que se seguem demonstram, de forma


prática, como aplicar estes modos de abertura em situações comuns.

228
Quando um ficheiro é aberto em modo texto, o Python normaliza o carácter
de nova linha \n de acordo com o sistema operativo. No Windows, esse
carácter é convertido para \r\n, enquanto em Linux e macOS permanece
como \n. Essa adaptação é conveniente na maioria dos casos, mas pode
causar problemas em formatos estruturados, como os ficheiros CSV.
É comum que, no Windows, apareçam linhas em branco adicionais quando
um CSV é escrito sem indicar o parâmetro newline. Para evitar esse
problema, o ficheiro deve ser aberto com newline="", o que impede o
Python de alterar os caracteres de quebra de linha, conforme exemplo a
seguir.

O objeto de ficheiro disponibiliza diversos atributos úteis, como name


(nome do ficheiro), mode (modo de abertura), encoding (codificação de
caracteres), errors, newline e closed (estado).
Além disso, é também possível verificar as funcionalidades, ou seja, se o
ficheiro permite leitura, escrita ou reposicionamento, com os métodos
[Link](), [Link]() e [Link](), respetivamente,
o que ajuda a prevenir erros antes de executar operações.

229
9.1.3 Fechar ficheiros com close()

Sempre que um ficheiro é aberto, o sistema operativo reserva recursos para


o manter disponível. Se o ficheiro não for fechado, esses recursos
permanecem ocupados desnecessariamente, podendo causar falhas quando
muitos ficheiros ficam abertos em simultâneo. Além disso, o fecho garante
que toda a informação escrita em memória temporária seja gravada
corretamente no disco.
O fecho pode ser feito manualmente com o método close(), como mostra
o exemplo que se segue:

O problema do close() é ser um fecho manual e o programador incorre


no risco de esquecer de fechar o ficheiro.

9.1.4 O uso do with

A instrução with, fecha o ficheiro automaticamente ao fim do bloco, mesmo


se ocorrer algum erro durante a execução. Por isso, este estilo é considerado
a melhor prática e está a ser apresentada nos exemplos a seguir.

230
Esta abordagem é considerada uma boa prática e deve ser preferida em
praticamente todos os casos.

9.1.5 Buffering e desempenho

O Python utiliza buffering para otimizar a escrita em disco. Os dados são


primeiro guardados em memória e só depois transferidos para o ficheiro, o
que reduz o número de acessos ao disco.

231
Em situações como registos de log ou sistemas de alta frequência, pode ser
útil controlar o tamanho do buffer ou forçar a escrita imediata com
flush(), conforme apresentado no exemplo a seguir.

Usar flush() em cada linha garante segurança, mas pode prejudicar o


desempenho se for usado em excesso.

9.2 Métodos de leitura de ficheiros

Após um ficheiro ser aberto, existem diferentes formas de aceder ao seu


conteúdo. A escolha do método depende da dimensão do ficheiro e do que
se pretende fazer com a informação.

9.2.1 Ler todo o conteúdo (read())

O método read() devolve o conteúdo completo do ficheiro como uma


única string. É prático quando o ficheiro é pequeno e pode ser carregado
integralmente em memória.

232
O resultado corresponde exatamente ao que está gravado no ficheiro,
incluindo quebras de linha.

9.2.2 Ler linha a linha (readline())

O método readline() devolve apenas uma linha de cada vez. Cada


chamada lê a linha seguinte, até chegar ao fim do ficheiro.

Este método é útil quando apenas algumas linhas iniciais interessam, sem
necessidade de percorrer o ficheiro completo.

9.2.3 Ler todas as linhas (readlines())

O método readlines() lê todas as linhas de uma só vez e devolve uma


lista, em que cada elemento corresponde a uma linha.

Com esta abordagem, o acesso às linhas torna-se direto através de índices da


lista, mas exige mais memória caso o ficheiro seja grande.

233
9.2.4 Iterar diretamente sobre o ficheiro

Um ficheiro também pode ser percorrido diretamente com um ciclo for. Em


cada iteração, é lida apenas uma linha, de forma sequencial.

Este método é eficiente para ficheiros grandes, porque processa uma linha de
cada vez, sem carregar tudo para a memória.
Entretanto, se estás a trabalhar em ficheiros muito grandes, é ainda mais
eficiente processar o conteúdo em partes.
− O método [Link](n) lê até n bytes ou caracteres por vez. Deve ser
colocado dentro de um ciclo para se processar o ficheiro bloco a bloco
− O padrão iter(partial([Link], n), ""), do módulo
functools, cria automaticamente um ciclo que chama [Link](n)
repetidamente até devolver uma string vazia (""), sinal de fim de
ficheiro. Desta forma, o programa percorre o ficheiro em blocos, sem
ocupar memória excessiva e sem necessidade de controlar manualmente
a condição de paragem.

Este exemplo a seguir mostra o caso de ficheiros muito grandes, que é


ineficiente se carregar tudo em memória, sendo, portanto, uma solução
processar o conteúdo em blocos, utilizando iter(partial(...)). De
forma que o programa processe partes do ficheiro sem risco de esgotar a
memória.

234
9.3 Métodos de escrita em ficheiros

Tal como na leitura, também existem diferentes formas de gravar informação


em ficheiros. O Python permite escrever dados de modo simples, acrescentar
novos conteúdos sem apagar os anteriores ou até atualizar partes já
existentes. A escolha do modo de abertura (w, a, r+, entre outros) define o
comportamento da gravação e influencia o resultado final no ficheiro.

9.3.1 Escrever conteúdo (write())

O método write() escreve texto diretamente no ficheiro. Cada chamada


acrescenta exatamente o que for passado como argumento. Para inserir
quebras de linha, é necessário incluir \n no final da string.

Neste exemplo, um novo ficheiro é criado ou, se já existir, o conteúdo


anterior é apagado antes da escrita.

235
9.3.2 Escrever múltiplas linhas (writelines())

Quando se pretende escrever várias linhas de uma só vez, pode-se utilizar o


método writelines(), que recebe uma lista de strings.

Este método não acrescenta quebras de linha automaticamente. Cabe ao


programador garantir que cada string, se necessário, contenha o seu \n.
A Tabela 24 compara os principais métodos de entrada e saída em ficheiros.
Cada método possui características próprias, com vantagens e limitações que
influenciam a escolha conforme o tamanho do ficheiro, a necessidade de
acesso sequencial ou direto e a forma como os dados serão manipulados.
Tabela 24 - Comparativo das entradas e saídas em ficheiros

Método Tipo Vantagens Desvantagens

read() Simples, devolve todo o Consome muita memória


Leitura
conteúdo de uma só vez em ficheiros grandes

readline() Permite ler linha a linha É necessário chamar várias


Leitura
de forma controlada vezes para ler todo ficheiro

readlines() Devolve lista de linhas, Carrega todas as linhas


Leitura
facilita acesso por índice para memória
Eficiente em ficheiros Não permite aceder direto
Iteração for Leitura grandes, lê uma linha de por índice, apenas
cada vez percorrer sequencial
Flexível, escreve qualquer
Exige inserir manualmente
write() Escrita string, incluindo variáveis
\n para criar novas linhas
formatadas
Ideal para listas de linhas, Não adiciona quebras de
writelines() Escrita escreve várias de uma só linha automaticamente,
vez precisa estar incluído

236
9.4 Exemplo prático com ficheiros de texto

Após conhecer os métodos de leitura e escrita, é útil observar um exemplo


completo que mostra como estas técnicas se aplicam em situações cotidianas.
Este exemplo mostra todas as etapas básicas, ou seja, como criar um ficheiro,
escrever algumas linhas, voltar a abri-lo em modo de leitura e numerar o
conteúdo linha a linha.

Após executar este código, na mesma pasta do ficheiro, é criado


“[Link]”, com este conteúdo a seguir:

E, na sequência o conteúdo do ficheiro “[Link]”, é transcrito para


o ecrã desta maneira:

237
9.5 Tratamento de exceções

Ao trabalhar com ficheiros, nem sempre o resultado corresponde ao


esperado. O ficheiro pode ter sido apagado, pode estar protegido contra
escrita ou o utilizador pode ter digitado o nome errado. Nessas situações,
Python interrompe a execução normal do programa e gera uma exceção, que
é um tipo especial de erro.
Uma exceção funciona como um sinal enviado ao programa para indicar que
algo correu mal. Se não for tratada, o programa termina imediatamente e
apresenta uma mensagem de erro ao utilizador. Para evitar esse
comportamento abrupto, é possível antecipar esses problemas e lidar com
eles através do bloco try/except.
Como já visto anteriormente, o bloco try contém o código que pode falhar,
enquanto o bloco except define a reação em caso de erro. Esta estrutura
permite ao programa continuar a execução sem se encerrar de forma
inesperada. No contexto de ficheiros, algumas exceções são particularmente
frequentes:
− FileNotFoundError → este erro ocorre quando se tenta abrir um
ficheiro que não existe
− PermissionError → aparece quando não há permissões para aceder
ao ficheiro
− IOError → indica problemas gerais de entrada/saída, como falhas no
disco ou que está corrompido
− Exception → representa qualquer outro erro não previsto.

238
Neste próximo exemplo vê-se como tratar estes casos de forma simples.
Dessa forma, mesmo que um erro ocorra, o programa informa o utilizador e
continua a correr, em vez de terminar abruptamente.

Além do bloco try/except, Python permite utilizar else e finally


em operações com ficheiros. O bloco else executa-se apenas se não ocorrer
erro, enquanto finally é sempre executado, servindo para libertar
recursos, conforme este exemplo:

239
Na prática, prefere-se o uso do with, mas este exemplo ilustra todas as
cláusulas possíveis num bloco de tratamento de erros.

9.5.1 Exemplos práticos: leitura e cópia de ficheiros texto

Para consolidar este conteúdo, seguem três pequenos programas que


exploram a leitura e a escrita em ficheiros de texto. Cada exercício inclui um
enunciado simples, um exemplo de código e um breve comentário.

Antes de prosseguir, convém destacar um ponto técnico essencial: a


codificação de caracteres. Esse mecanismo define como letras, acentos e
símbolos são representados internamente em bytes. A utilização de diferentes
padrões de codificação pode originar erros na leitura ou escrita de ficheiros,
sobretudo quando os dados foram produzidos em sistemas distintos.
O padrão atual mais utilizado é o UTF-8, adotado em aplicações modernas
e compatível com praticamente todas as línguas. No entanto, muitos ficheiros
antigos ou gravados em editores do Windows utilizam outras codificações,
como Latin-1 (ISO-8859-1) ou Windows-1252 (cp1252).
Nestes casos, ao tentar abrir o ficheiro com encoding="utf-8", podem
surgir erros como este exemplo:
Error: 'utf-8' codec can't decode byte 0xe9 in
position 7: invalid continuation byte

240
Esse erro indica que o ficheiro não está em UTF-8, mas numa codificação
diferente. Por exemplo, a letra “é” corresponde ao byte 0xe9 em Latin-
1/Windows-1252, mas não é válido em UTF-8.

Para resolver, basta indicar explicitamente a codificação correta ao abrir o


ficheiro. Se for um ficheiro criado num editor simples do Windows, como o
Bloco de Notas, é comum resultar melhor com encoding="latin-1"
ou encoding="cp1252".
Ou seja, a resolução passa por apenas indicar a codificação correta no
momento da abertura.
A Tabela 25 mostra as opções mais utilizadas.

Tabela 25 - Codificações comuns em ficheiros de texto

Codificação Uso típico Quando usar

utf-8 Sempre que possível, pois suporta


Padrão universal moderno
todos os idiomas
utf-8- Ficheiros com BOM (Byte Quando o ficheiro tem marca BOM
sig Order Mark) (comum em Windows)

latin-1 Alternativa para ficheiros Quando UTF-8 falha e o ficheiro


antigos contém acentos

cp1252 Variante Windows do Ficheiros específicos do Windows


Latin-1 com problemas

Com este cuidado, evita-se a maioria dos erros relacionados com leitura de
acentos e caracteres especiais.

Nos exemplos a seguir, todos os ficheiros são abertos no padrão UTF-8.

241
 Exemplo 1: Contador de palavras

O objetivo é abrir um ficheiro de texto, ler o conteúdo e contar quantas


palavras existem.

Neste programa, o método split() divide o texto em palavras separadas


por espaços, devolvendo uma lista. O comprimento da lista corresponde ao
número total de palavras. O bloco try/except garante que, caso o ficheiro
não exista, uma mensagem clara seja apresentada ao utilizador.

 Exemplo 2: Copiador de ficheiros

Neste exemplo o objetivo é criar uma cópia exata de um ficheiro de texto,


linha por linha.

242
Este programa abre o ficheiro de origem em modo leitura e o ficheiro de
destino em modo escrita. Cada linha lida é imediatamente gravada no novo
ficheiro. O tratamento de erros assegura que, caso o ficheiro original não
exista, o utilizador seja informado.

 Exemplo 3: Filtro de linhas

Neste exemplo se deve copiar apenas as linhas que contêm uma palavra
específica em um ficheiro e, depois, criar um novo ficheiro filtrado.

Neste caso, cada linha é testada com a expressão if palavra_chave


in linha. Apenas as linhas que satisfazem esta condição são gravadas no
ficheiro de saída.
Para além, o programa inclui tratamento de erros para lidar com a ausência
do ficheiro de entrada.

243
9.6 Manipular ficheiros CSV e JSON

Ao trabalhar com dados estruturados, raramente se utilizam apenas ficheiros


de texto simples. Em muitos casos, é necessário guardar informação em
formatos que preservam a organização dos dados, facilitando a sua partilha
entre programas e sistemas. Dois desses formatos dominam o cenário atual:
CSV e JSON.
− csv (Comma-Separated Values) é amplamente usado em folhas de
cálculo, exportações de bases de dados e relatórios, pela sua simplicidade
e compatibilidade
− json (JavaScript Object Notation) tornou-se o padrão universal de
comunicação entre aplicações modernas, em particular em APIs web e
aplicações que lidam com dados hierárquicos.
Python inclui módulos nativos para trabalhar com ambos, permitindo ler,
manipular e escrever dados nestes formatos de forma natural.

9.6.1 Ficheiros CSV (Comma-Separated Values)

O módulo csv está incluído na biblioteca padrão de Python e oferece


ferramentas para leitura e escrita de tabelas representadas em texto. Cada
linha do ficheiro corresponde a um registo e cada valor é separado por um
delimitador, normalmente a vírgula. Basta importar o módulo:
import csv
Existem duas abordagens principais para leitura. A primeira maneira é
transformar cada linha numa lista e o segundo método é mapear os dados
diretamente para um dicionário.

244
a) Método 1 – Leitura como listas
Cada linha é interpretada como uma lista, em que a posição de cada elemento
representa a coluna correspondente, conforme o exemplo a seguir.

b) Método 2 – Leitura como dicionários (recomendado)


Quando o ficheiro contém cabeçalho, o DictReader converte cada linha
num dicionário, usando os nomes das colunas como chaves. Isso torna o
código mais legível, conforme o exemplo a seguir.

Assim como na leitura, a escrita em ficheiros pode ser feita a partir de listas
ou de dicionários. A seguir se apresentam exemplos de diferentes métodos
de escrever em ficheiros .csv.

a) Método 1 – Escrita com listas


Este método organiza cada registo como uma lista de valores. A ordem dos
elementos deve coincidir com a ordem das colunas no ficheiro. É a forma
mais simples de gerar um .csv quando não há necessidade de identificar
explicitamente os campos, conforme o exemplo a seguir.

245
Em cada sublista corresponde a uma linha no ficheiro. O argumento
newline="" evita linhas em branco adicionais no Windows, e
encoding="utf-8" garante suporte a acentos.
O método writerows() recebe uma lista de listas e grava todas de uma
só vez no ficheiro CSV. Cada sublista dentro de dados torna-se uma linha no
ficheiro.
Ressalta-se que se fosse utilizado o método writerow() (no singular),
seria necessário chamar o método repetidamente para cada linha.

b) Método 2 – Escrita com dicionários


Neste caso, cada registo é representado por um dicionário, associando cada
valor diretamente ao nome da coluna. Esta abordagem, apresentada no
exemplo a seguir, aumenta a legibilidade e reduz o risco de erros quando o
número de campos é elevado.

246
O método DictWriter usa a lista fieldnames para definir a ordem das
colunas. O método writeheader() escreve automaticamente o
cabeçalho antes dos dados.
A lista fieldnames de strings define os nomes das colunas e a ordem em
que aparecem no ficheiro CSV e, mesmo que o dicionário dos dados tenha
as chaves numa ordem diferente, o DictWriter vai respeitar a ordem
indicada em fieldnames.
Vale ressaltar que se faltar uma chave no dicionário, a coluna correspondente
fica em branco e se aparecer uma chave que não está em fieldnames,
ocorre erro (a menos que seja tratado com extrasaction='ignore').

c) Personalização do CSV
Nem sempre o separador padrão (vírgula) é o mais adequado. Em Portugal e
no Brasil, por exemplo, o ponto e vírgula (;) é muito utilizado em ficheiros
CSV. O módulo csv permite personalizar o delimitador de forma simples,
como no exemplo a seguir.

247
O argumento delimiter=';' altera o separador de colunas, permitindo
ler ficheiros que não utilizam a vírgula.

O módulo csv também inclui a classe Sniffer, que tenta identificar


automaticamente o delimitador (vírgula, ponto e vírgula, tabulação etc.) e o
caractere de aspas usado no ficheiro. Esta funcionalidade é especialmente
útil quando não se conhece previamente o formato exato do ficheiro, como
no exemplo a seguir.

Além disso, em alguns contextos é importante controlar quando os campos


de um ficheiro CSV devem ser colocados entre aspas.
O módulo csv oferece o parâmetro quoting, que define esta regra de
forma explícita.
A Tabela 26 resume as principais opções disponíveis, seus comportamentos
e em que situações são mais indicadas.

248
Tabela 26 - Opções de quoting no módulo csv

Valor Comportamento Quando usar


Obter máxima
Todas as células entre
csv.QUOTE_ALL segurança e
aspas, mesmo que não seja
compatibilidade com
necessário
outros programas
Aspas apenas quando
csv.QUOTE_MINIMAL Uso mais comum,
necessário (exemplo:
(padrão) gera ficheiros mais
campos com vírgula, aspas
leves.
ou quebras de linha)
Aspas em todos campos Quando se quer
não numéricos. E converte distinguir, com
csv.QUOTE_NONNUMERIC
os valores não citados em clareza, números de
números (int/float). texto
Nunca usa aspas. Nesse
caso, é obrigatório definir Quando se precisa
csv.QUOTE_NONE um carácter de escape para de ficheiros sem
lidar com vírgulas e outros aspas, em sistemas
símbolos dentro dos muito específicos
campos

O uso de quoting no módulo CSV está a ser exemplificado a seguir.

9.6.2 Ficheiros JSON (JavaScript Object Notation)

O formato JSON representa dados estruturados de forma leve e legível, e


combina listas e dicionários aninhados. É usado para guardar configurações,
transferir dados entre aplicações e comunicar com serviços web.
249
O módulo json, incluído na biblioteca padrão de Python, oferece funções
para converter dados Python em JSON e vice-versa. Para importar basta
incluir a seguinte linha de código.
import json

a) Leitura de JSON
O formato JSON é frequentemente usado para guardar dados estruturados
em ficheiros. O Python permite abrir o ficheiro e convertê-lo diretamente
para um dicionário.
Segue um exemplo elucidativo.

O método [Link]() lê o ficheiro e converte-o para estruturas Python


(dicionários e listas). Assim, é possível aceder a valores internos usando
chaves como ['pessoa']['nome'].

250
b) Escrita em JSON
Também é possível guardar dados Python diretamente em ficheiros JSON,
garantindo que estruturas como listas e dicionários sejam preservadas,
conforme o próximo exemplo.

O método [Link]() grava os dados no ficheiro. O argumento


indent=4 torna o ficheiro mais legível, adicionando recuos, enquanto
ensure_ascii=False garante que acentos e caracteres especiais sejam
guardados corretamente.
Para grandes volumes de dados, é comum o uso do formato JSON Lines
(NDJSON), que é um formato de dados em que cada linha do ficheiro
contém um objeto JSON independente. Esse formato é ideal para grandes
volumes de dados, pois permite a leitura e processamento linha por linha,
sem a necessidade de carregar o ficheiro inteiro na memória. O formato é
amplamente utilizado em logs, datasets grandes e transmissão de dados em
tempo real, pois facilita o processamento incremental e a escala de dados.
Cada linha no NDJSON pode ser lida e analisada de forma independente,
tornando-o muito eficiente para sistemas que precisam processar dados de
forma contínua e sem sobrecarregar a memória.

251
Considere este exemplo de JSON Lines (NDJSON) a seguir, onde cada
linha contém um objeto JSON válido.

A seguir, mostra-se como processar um ficheiro NDJSON linha a linha,


convertendo cada linha de JSON para um objeto Python (dict), de forma a
processar com mais eficiência em situações de grande volume de dados.

c) Conversão de strings JSON


Além dos ficheiros, também é comum receber dados JSON como strings, por
exemplo em respostas de APIs. O Python permite converter essas strings
para dicionários ou, inversamente, transformar dicionários em strings JSON.
A seguir um exemplo desta conversão.

252
O método [Link]() converte uma string JSON para estruturas
Python, enquanto [Link]() faz o contrário, gerando uma string
JSON a partir de dicionários ou listas.

9.7 Posicionamento e escoamento de dados

Em alguns cenários é útil controlar a posição de leitura/escrita dentro do


ficheiro e forçar a gravação imediata de dados que ainda estão em memória
intermédia (buffer). Estes três métodos são importantes em ficheiros:
− [Link]() devolve a posição atual (em bytes) no ficheiro
− [Link](offset, whence=0) reposiciona o cursor. Tem-se como
valores possíveis para whence:
⋅ os.SEEK_SET (0) → posição relativa ao início
⋅ os.SEEK_CUR (1) → posição relativa à atual
⋅ os.SEEK_END (2) → posição relativa ao final
− [Link]() força a escrita no disco do que está em buffer, útil em
registos críticos (logs).

Estes conceitos estão exemplificados a seguir.

253
É importante perceber que em ficheiros de texto, seek() pode não apontar
exatamente para “caracteres” devido a codificações; em binário, a posição
corresponde diretamente a bytes.

9.8 Codificação de caracteres: estratégia e gestão de erros

A codificação de caracteres estabelece a correspondência entre os símbolos


exibidos no ecrã e a sequência de bytes que os representa num ficheiro.
Quando diferentes padrões de codificação são utilizados, podem surgir erros
de leitura ou escrita, tornando essencial escolher a estratégia adequada e
prever mecanismos de gestão de incompatibilidades.

9.8.1 Erros

Quando a codificação efetiva do ficheiro não é conhecida, os erros mais


comuns são basicamente de dois tipos:
1. Erros de acesso

254
− O ficheiro não pode ser aberto porque não está disponível ou o
sistema não permite a operação
− Exceções típicas: FileNotFoundError (ficheiro não existe),
PermissionError (sem permissões), caminho inválido ou
ficheiro bloqueado por outro processo
2. Erros de conteúdo
− O ficheiro é aberto, mas os bytes não correspondem à codificação ou
ao formato esperado, como ao ler texto com acentos em ficheiro não
UTF-8 e ao tentar gravar caracteres especiais em ficheiros
configurados com codificação limitada
− Exceções típicas: UnicodeDecodeError (leitura de texto com
codificação errada), UnicodeEncodeError (escrita em texto
com caracteres não suportados).

9.8.2 Estratégia recomendada

O tratamento adequado passa pela adoção duma sequência lógica bem


definida, onde cada etapa deve ser cumprida de forma ordenada, de modo a
assegurar clareza no processo, evitar falhas desnecessárias e permitir um
controlo eficaz do resultado.
Desta forma, sugere-se os passos a seguir.

1) Verificar acesso
− Confirmar se o ficheiro existe e se há permissões de leitura/escrita
(FileNotFoundError, PermissionError)

255
− Em caso de incerteza sobre o tipo de conteúdo, recomenda-se abrir
o ficheiro em modo binário numa primeira etapa, realizando apenas
uma inspeção mínima, como a verificação do tamanho ou da
eventual presença de um BOM.
⋅ BOM (Byte Order Mark): alguns ficheiros UTF-8 começam
com três bytes (EF BB BF). Se o ficheiro for aberto com
encoding="utf-8", essa marca pode aparecer como
carácter estranho. Com encoding="utf-8-sig", o BOM
é ignorado automaticamente.

2) Selecionar codificação
− Usar UTF-8 como padrão universal
− Se houver BOM, usar UTF-8-sig
− Em ficheiros antigos, recorrer a Latin-1 ou CP1252 se UTF-8 falhar

3) Tratar erros de conteúdo


− Se ocorrer UnicodeDecodeError, recomenda-se tentar codificar na
seguinte ordem: UTF-8 → UTF-8-sig → Latin-1 → CP1252
− Quando não for possível garantir fidelidade absoluta, usar
errors="replace" ou errors="ignore", avisando o utilizador
da possível perda de caracteres

4) Validar o resultado
− Verificar se aparecem muitos símbolos , sinal de erro de codificação

256
− Confirmar se os dados mantêm sentido: número de colunas em CSV, ou
leitura correta em JSON.

Um fato importante sobre Latin-1 é que esta codificação nunca falha a


descodificar, pois aceita todos os 256 valores de byte. Contudo, pode
mascarar erros, substituindo caracteres por símbolos incorretos (aspas
curvas, travessões, €). Deve ser usada apenas como alternativa consciente
para ficheiros antigos em ANSI/Windows.
Uma regra/sugestão importante é, em novos projetos, normalizar sempre
para UTF-8. Em dados legados, aplicar a estratégia de tentativa ordenada e
reportar ao utilizador quando for necessário recorrer a leitura tolerante.

A Figura 9-1 resume, em forma de fluxograma, a estratégia recomendada para


lidar com problemas de codificação.
Cada passo segue a ordem lógica de tentativas, começando pela verificação
de acesso e avançando até ao recurso a alternativas tolerantes. Esta
representação facilita a compreensão do processo sem exigir a leitura integral
da explicação teórica.

257
Se ainda
Se falhar falhar
Abrir Tentar → Se nada resultar →

ficheiro UTF-8 UTF-8- leitura tolerante
sig Latin-1 /
CP1252

Se falhar Usar
→ Útil Alternativa errors= Informar o
FileNotFou quando para utilizador
Padrão "replace"
ndError existe BOM ficheiros da possível
universal ou
ou (marca no antigos ou perda de
início) vindos do errors= caracteres
Permission
Error Windows "ignore"

Figura 9-1 - Fluxo de decisão (resumo em 5 passos)

Em cenários críticos, como registos de logs ou gravação de dados


financeiros, recomenda-se o uso de escrita atómica.
A escrita atómica refere-se a um padrão de manipulação de ficheiros que
garante que as operações de escrita sejam seguras e completas, mesmo em
situações onde o sistema possa falhar inesperadamente (como um corte de
energia ou uma falha no programa). Quando uma operação de escrita é
atómica, ela é realizada de maneira que, em caso de falha, o ficheiro original
não fica corrompido e o estado final é sempre válido.
A escrita atómica funciona desta forma:
− Gravar primeiro em ficheiro temporário → Em vez de escrever
diretamente no ficheiro original, grava-se num ficheiro temporário, ou

258
de backup. Isso assegura que, mesmo se ocorrer uma falha durante a
escrita, o ficheiro original permanecerá intacto
− Usar flush() e fsync() → O método flush() garante que os
dados sejam enviados da memória para o disco, enquanto fsync()
força a gravação real no disco, garantindo que a operação seja
completada corretamente
− Substituir o ficheiro original → Após garantir que a escrita no ficheiro
temporário foi concluída com sucesso, o próximo passo é substituir o
ficheiro original pelo temporário utilizando [Link](). A
operação replace() é atómica e garante que, se o sistema falhar, o
ficheiro original não seja corrompido, já que a substituição só acontece
após a conclusão da escrita.

No exemplo a seguir, o conteúdo é primeiro gravado num ficheiro


temporário ([Link]) para evitar corromper o original.
Em seguida, flush() e fsync() garantem que os dados sejam gravados
no disco.
E por fim, [Link]() substitui o ficheiro original pelo temporário
de forma atómica, assegurando que a troca só ocorra quando a escrita for
concluída corretamente, mesmo em caso de falha.

259
Uma forma prática de aplicar este conceito consiste em usar a biblioteca
tempfile para gerar automaticamente um ficheiro temporário no mesmo
diretório, garantindo nomes seguros e evitando colisões. No passo final, a
substituição do original é feita com [Link](), o que assegura que a
troca só ocorre depois de a escrita estar concluída com sucesso. O exemplo
a seguir mostra essa implementação.

260
9.9 Aplicações de Ficheiros Texto

Os ficheiros de texto são a base para integrar sistemas, gerar relatórios e


guardar configurações de aplicações. Nesta secção, verás exemplos práticos
de leitura e escrita em formatos comuns (CSV e JSON) em exercícios que
simulam cenários reais de trabalho.

9.9.1 Migrar base de dados de clientes para API web

A migração de dados é frequente, como num CRM (Customer Relationship


Management) que exporta CSV e uma API espera JSON. O exemplo mostra
como ler registos com [Link], como transformar campos e
gravar um ficheiro JSON adequado para consumo por serviços web.

261
9.9.2 Relatório diário de vendas da loja

Relatórios diários dependem de cálculos consistentes. Com pandas, é simples


ler um CSV, calcular totais e métricas (média, produto mais vendido, melhor
vendedor) e imprimir um resumo legível.
Conforme neste exemplo.

262
9.9.3 Fechamento diário de caixa

Terminais de pagamento exportam ficheiros simples (CSV). Este exemplo


simula registos de venda, calcula o total geral e os totais e exibe um relatório
com os valores agregados.

263
9.9.4 Sistema de configuração de aplicação web

Guardar a configuração em JSON desacopla o código da parametrização


(BD, APIs e flags), tornando a aplicação mais portátil entre ambientes
(desenvolvimento, testes e produção). O exemplo grava config_app.json e
mostra como o carregar no arranque da aplicação.

264
9.9.5 Conversor de Formatos (CSV ⇄ JSON)

Integrações exigem conversões fiáveis entre formatos. Neste exemplo faz-se


a transformação de CSV para JSON (para APIs) e o inverso (para Excel).

265
Inicialmente cria-se o ficheiro [Link] a partir da string fornecida;
em seguida, depois converte o CSV em JSON e confirma a criação de
produtos_api.json com a chave de lista definida; por fim, executa
gera o ficheiro produtos_export.csv e verifica que os campos e o
cabeçalho foram preservados corretamente.

266
9.10 Ficheiros Binários

Ficheiros binários são aqueles que armazenam dados exatamente como estão
gravados, sem qualquer tipo de conversão ou processamento de texto. Isso é
crucial para quando se trabalha com imagens, áudio, vídeos, ficheiros
comprimidos ou qualquer outro formato que não seja baseado em texto.
Diferente dos ficheiros de texto, que armazenam dados como cadeias de

267
caracteres, os ficheiros binários mantêm os dados tal como estão no disco,
byte a byte.

9.10.1 Modo de Abertura de Ficheiros Binários

Os modos de abertura para ficheiros binários são bastante semelhantes aos


de ficheiros de texto, mas com a adição do b. Os principais modos de
abertura incluem:
− rb: Leitura de ficheiro binário. O ficheiro deve existir
− wb: Escrita em ficheiro binário. Cria novo ficheiro ou apaga o existente
− ab: Escrita em ficheiro binário. Acrescenta dados ao final do ficheiro
sem apagar o conteúdo existente
− r+b: Leitura e escrita em ficheiro binário. O ficheiro deve existir
− w+b: Leitura e escrita em ficheiro binário. Cria novo ficheiro ou apaga
o existente
− a+b: Leitura e escrita em ficheiro binário. Mantém o conteúdo do
ficheiro e escreve no final
A principal diferença em relação aos modos de texto é a presença da letra b
no modo de abertura, o que instrui Python a tratar os dados como bytes.

9.10.2 Leitura de Ficheiros Binários

Ao abrir um ficheiro binário em modo de leitura (rb), os dados são lidos


como uma sequência de bytes e não como texto. Isso é útil quando se está a
tratar com imagens, vídeos ou qualquer tipo de ficheiro não textual onde a
precisão dos dados é crítica.
268
Por exemplo, ao abrir uma imagem, os bytes podem ser lidos diretamente
sem qualquer conversão de caracteres, o que preserva a integridade do
ficheiro, conforme o exemplo a seguir.

Neste exemplo, data conterá a sequência exata de bytes que foram gravados
no ficheiro e pode ser processada ou manipulada conforme necessário.

9.10.3 Escrita em Ficheiros Binários

A escrita em ficheiros binários funciona de forma semelhante à leitura, mas


ao invés de ler, está a gravar uma sequência de bytes diretamente no disco.
Isso é importante para formatos de dados como imagens, áudio e vídeos,
onde a precisão dos dados é fundamental.
A seguir há um exemplo de como escrever uma sequência de bytes num
ficheiro binário:

Neste exemplo, o conteúdo que está a ser escrito no ficheiro é uma sequência
de bytes (b'\x89PNG\r\n\x1a\n...'), que corresponde ao início de
um ficheiro PNG. Ao escrever os dados diretamente em formato binário,
garante-se que a estrutura do ficheiro será preservada sem qualquer alteração.

269
9.10.4 Manipulação de Dados Binários

Trabalhar com ficheiros binários também pode envolver manipulação de


bytes. Isso é particularmente útil quando está a manipular formatos de
ficheiros específicos, como ficheiros comprimidos ou dados codificados.
Por exemplo, ao trabalhar com dados binários, pode ser necessário manipular
cabeçalhos de ficheiros, verificar a presença de certos valores específicos nos
dados ou realizar operações como compressão ou encriptação.
Ao ler dados binários para, por exemplo, manipular a header de um
ficheiro de imagem, pode-se fazer algo como o seguinte:

Neste exemplo, lê-se os primeiros 8 bytes do ficheiro, que geralmente


contêm informações de cabeçalho (como a assinatura de um formato de
imagem). O Python, felizmente, permite que você manipule esses dados
diretamente, sem qualquer tipo de conversão.

9.10.5 Ficheiros Binários e struct

Embora o exemplo anterior mostre como tratar bytes diretamente, em alguns


casos é necessário interpretar os dados binários em formatos mais
específicos, como inteiros, floats ou strings. Para isso, pode-se usar o módulo

270
struct, que permite converter entre tipos de dados Python e suas
representações binárias.
Aqui está um exemplo de como usar o struct para trabalhar com dados
binários mais complexos.

Neste exemplo, [Link]() converte os valores para uma sequência


binária, que é escrita no ficheiro. Mais tarde, [Link]() pode ser
usado para converter os dados binários de volta para os tipos Python
originais. Isso é útil quando se trabalha com formatos binários estruturados.

9.10.6 Assinaturas binárias (magic numbers)

Muitos formatos de ficheiros possuem no início uma sequência fixa de bytes


que funciona como uma assinatura. Essa marca, conhecida como magic
number, permite identificar rapidamente o tipo de ficheiro sem depender da
sua extensão. Esse método é amplamente usado em sistemas operativos e
programas de análise de ficheiros para verificar se o conteúdo corresponde
ao formato declarado.
271
No exemplo a seguir, o programa abre um ficheiro em modo binário, lê os
primeiros bytes e compara-os com algumas assinaturas conhecidas. Dessa
forma é possível reconhecer de imediato se se trata, por exemplo, de uma
imagem PNG, um documento PDF ou um ficheiro ZIP.

A Tabela 27 - Exemplos comuns de assinaturas binárias:apresenta alguns


exemplos comuns de assinaturas binárias.

Tabela 27 - Exemplos comuns de assinaturas binárias:

Formato Assinatura (hex) Observação


PNG 89 50 4E 47 0D 0A 1A 0A 8 bytes fixos
JPEG FF D8 FF Início da imagem (SOI)
PDF 25 50 44 46 ("%PDF") Primeiros 4 bytes
ZIP 50 4B 03 04 ("PK..") Base de ZIP (DOCX, XLSX, APK)

Esta técnica é especialmente útil quando se recebem ficheiros sem extensão


ou com extensões alteradas, pois a assinatura interna é mais fiável do que o
nome do ficheiro para determinar o seu verdadeiro formato.

272
9.11 Exemplos práticos

Os exemplos práticos que se seguem ilustram de forma aplicada como


utilizar as técnicas apresentadas, mostrando situações comuns em que a
leitura e escrita de ficheiros se tornam essenciais.

9.11.1 Analisador de Logs para Deteção de Intrusões

Este exemplo simula um Sistema de Deteção de Intrusões (IDS) básico que


analisa ficheiros de log para identificar atividades maliciosas. Em ambientes
corporativos, os servidores geram logs para registar todas as atividades de
logins, acessos a ficheiros, tentativas falhadas. Analisar estes logs
manualmente é impossível, daí a necessidade de automatização.
O objetivo deste exemplo é detetar automaticamente as tentativas de força
bruta (múltiplos logins falhados), de acessos não autorizados a ficheiros
confidenciais e de padrões de comportamento suspeitos.

273
A biblioteca re (expressões regulares) é uma ferramenta poderosa para o
processamento de texto e de padrões. Ao importar, é possível:
− Buscar padrões específicos em texto
− Validar formatos, como emails, URLs e telefones
− Extrair informações estruturadas de texto não estruturado
274
− Substituir partes do texto baseado em padrões
É essencial em cibersegurança para analisar logs e em Inteligência Artificial
para pré-processar dados de texto.

9.11.2 Sistema de Treino de Modelo IA com Persistência de Dados

Este exemplo demonstra como ficheiros JSON são usados para persistir
dados em sistemas de IA. Na prática, modelos de Machine Learning devem
guardar dados de treino, guardar os parâmetros do modelo para implantação,
guardar as métricas de performance para avaliação e manter o histórico de
versões para controlo de qualidade. Neste contexto, o objetivo deste exemplo
é apresentar um sistema em IA simples, apenas com o intuito de criar um
sistema que guarda tais dados.

275
Este é o relatório de saída deste sistema:

Neste exemplo o comando [Link]() lê um ficheiro JSON e converte-


o para um dicionário. Já [Link]() faz o contrário: grava um
dicionário num ficheiro JSON. Assim, garante-se a persistência dos dados,
que continuam disponíveis entre execuções do programa. Também trata
possíveis erros, como quando o ficheiro não existe.

276
9.11.3 Sistema de Proteção de Ficheiros Simples

Este exemplo mostra como criar um sistema básico de proteção de ficheiros


usando apenas operações simples com ficheiros. Em vez de criptografia
complexa, este exemplo apenas utiliza técnicas básicas para ofuscar o
conteúdo, tornando-o menos legível para olhares indiscretos.
Neste contexto, este código busca “proteger" ficheiros tornando o conteúdo
menos legível, manter registo dos ficheiros protegidos e mostrar princípios
básicos de segurança de dados. Por questões didáticas, este exemplo está
separado em 3 pequenos exemplos simples:
a. Ofuscação básica (inverter texto)
b. Verificação simples de passwords
c. Registo de acessos

277
Com a seguinte saída no ecrã:

278
Neste segundo exemplo, pertinente ao mesmo contexto, verifica uma
password, de forma bem básica, inicialmente guarda a palavra-passe num
ficheiro e depois a compara com o que o utilizador digita.

Este é o relatório de saída:

279
E no exemplo a seguir, há um sistema de registo de acessos, onde dados são
acrescentados ao ficheiro e guarda a data/hora e o nome do utilizador.

280
9.12 Exercícios Propostos

1) Implementar um detetor de phishing em emails. O programa deve


analisar ficheiros de emails (em formato .txt ou .csv) com o objetivo
de identificar potenciais tentativas de phishing. O sistema deve
reconhecer padrões suspeitos, como URLs maliciosas ou pedidos de
dados pessoais, e gerar um relatório estruturado com a classificação de
risco de cada mensagem. O programa deve:
− Ler ficheiros contendo emails suspeitos
− Identificar indicadores comuns de phishing
− Produzir relatório em formato legível com classificações de risco
− Guardar os resultados finais em JSON

2) Criar uma aplicação que analise ficheiros de configuração (.conf, .xml,


.json) em busca de vulnerabilidades frequentes, como palavras-passe
guardadas em claro ou permissões demasiado permissivas. O sistema
deve comparar os resultados com uma base de dados de vulnerabilidades
conhecidas e exportar um relatório detalhado em formato HTML. O
programa deve:
− Ler ficheiros de configuração em diferentes formatos
− Detetar padrões de risco (passwords, permissões fracas)
− Validar com base de vulnerabilidades predefinida
− Exportar relatório estruturado em HTML

281
10 PROGRAMAÇÃO ORIENTADA A OBJETOS

A Programação Orientada a Objetos (POO) é um paradigma em que


modelamos problemas na forma de objetos que possuem propriedades
(atributos) e comportamentos (métodos). Em contraste, a programação
imperativa descreve passo a passo o que o computador deve fazer,
manipulando dados com funções soltas. Foca-se em objetos, reutilização,
modularidade, promove a coesão (dados + comportamentos no mesmo
módulo) e a separação de responsabilidades (cada classe resolve um
subproblema bem definido). Em sistemas de maior escala, isto reduz
acoplamento, facilita testes e evolução incremental do software.

10.1 Classes e objetos

Enquanto a programação imperativa pode ser mais direta em problemas mais


simples, a POO é essencial em sistemas maiores, ao agrupar dados e
comportamento de forma coesa. Quais os conceitos principais de POO?
− Classe – o molde que descreve atributos e métodos comuns a um
conjunto de objetos.
− Objeto – uma instância de uma classe (um exemplar concreto).
− Atributos – as características do objeto (guardadas como variáveis).
− Métodos – as funcionalidades do objeto (funções definidas na classe).

Pense numa analogia simples, uma classe é como uma receita: lá estão as
instruções e as características do que se quer preparar (por exemplo, um bolo
de chocolate). Ler a receita não produz o bolo, é preciso executá-la. Quando

282
“executa-se a receita” de uma classe em Python, cria-se um objeto: o bolo
feito. As características do bolo (cor, tamanho, cobertura) correspondem aos
atributos do objeto, as ações (misturar, verter, cozer) correspondem aos
métodos definidos na classe.
Resumindo, a classe define, o objeto concretiza, os atributos descrevem o
estado, métodos definem o que o objeto sabe fazer. Para inicializar os
atributos de um objeto de uma classe, é necessário definir o método
construtor dentro da classe. Em Python, o método construtor é designado de
__init__. O construtor recebe como parâmetro a própria instância do
objeto (self) e quaisquer outros parâmetros que sejam necessários para
inicializar os atributos do objeto.
Dentro do construtor, os atributos do objeto são definidos usando a sintaxe
self.nome_do_atributo = valor. Em Python, define-se uma
classe com a palavra-reservada class, seguida do nome (por convenção,
em CamelCase):

Neste exemplo, definiu-se uma classe com três atributos definidos dentro do
método construtor (__init__). Esta classe, também tem um método
próprio designado de ligar_motor. A criação de objetos (instanciação)

283
reserva memória, executa o construtor para inicializar o estado e devolve
uma referência. Guardar essa referência numa variável não “copia” o objeto,
apenas aponta para o mesmo exemplar (semântica de referência).

Como instanciar o objeto é apresentado a seguir:

Neste exemplo, Automovel("BMW", "AA-00-AA") chama o


construtor da classe (__init__(self, marca, matricula)),
criando um objeto do tipo Automovel. Dentro do __init__, os valores
passados são guardados nos atributos de instância:
[Link] = "BMW", [Link] = "AA-00-AA", e
self.motor_ligado = False (estado inicial).
O resultado dessa chamada é a referência ao objeto criado, essa referência é
guardada na variável auto_bmw. Ou seja, auto_bmw passa a “apontar” para
o objeto. auto_bmw.ligar_motor() usa o operador ponto para invocar
um método de instância. Dentro do método, o objeto é passado como
primeiro argumento (self), e o estado muda para motor_ligado = True.

Notas úteis
Pode-se também usar argumentos nomeados:
Automovel(marca="BMW", matricula="AA-00-AA")
Para confirmar o tipo da variável pode-se utilizar a função type:
type(auto_bmw) que devolve Automovel. Se o número/ordem dos
argumentos não corresponder ao __init__, o Python lança um

284
TypeError (assinatura inválida). A função integrada
isinstance(obj, Classe) verifica se um objeto é instância de uma
classe (ou suas subclasses). Já issubclass(Sub, Super) verifica
relações na hierarquia de herança.

10.2 Atributos e métodos

Os atributos representam as características próprias de cada objeto e


constituem o seu estado interno. Distingue-se entre atributos de instância,
que pertencem a cada objeto em particular e são acedidos nos métodos
através de self., e atributos estáticos (da classe), que pertencem à própria
classe e são partilhados por todas as suas instâncias. Neste exemplo,
categoria é estático (fica na classe e é igual para todos os objetos),
enquanto marca, matricula e motor_ligado são de instância
(vivem dentro de cada objeto e podem assumir valores diferentes em cada
caso).

Em relação à chamada dos atributos de instância, como é possível verificar


no exemplo seguinte, o acesso faz-se pelo operador ponto sobre o objeto:
[Link].

285
O valor pertence apenas àquela instância e pode ser lido/alterado sem afetar
as restantes. Já, a chamada dos atributos estaticos é recomendado que seja
através do nome da classe: [Link]. O valor pertence
à classe e é comum a todas as instâncias. Pode ser lido a partir da instância
([Link]), mas continua a pertencer à classe. Se for atribuída
um novo valor através da instância, cria-se um atributo de instância com o
mesmo nome (sombreamento), sem alterar o valor partilhado da classe.
Neste exemplo a seguir, cria-se um objeto da classe Automovel
armazenado na variável auto_bmw. Para invocar um atributo de instancia
tem-se de indicar a variável auto_bmw seguido do atributo. Já para os
atributos estáticos pode-se indicar a classe Automovel.

Os métodos de instância são funções definidas dentro da classe e invocadas


sobre o objeto. Recebem, implicitamente, o próprio objeto como primeiro
parâmetro (self) e podem ler e alterar os atributos dessa instância. self é
a referência à própria instância dentro dos métodos da classe, permitindo
aceder e modificar os seus atributos.

286
A chamada típica é

Os métodos estáticos estão associados à classe e não recebem self.


Utilizam-se quando a operação não depende do estado de um objeto concreto
e, por isso, podem ser chamados sem instanciar. A chamada faz-se, por
convenção, através do nome da classe.

A chamada típica é:

287
10.3 Encapsulamento

Quanto à visibilidade e encapsulamento, em Python os nomes são públicos


por omissão, mas existem convenções para desencorajar o acesso direto a
detalhes internos. O prefixo _nome indica ocultação fraca (uso interno), que
funciona como aviso de que o atributo não deve ser manipulado a partir do
exterior e o prefixo __nome indica ocultação forte. O prefixo __nome ativa
o name mangling, fazendo com que o interpretador transforme o
identificador e, assim, desencoraje o acesso externo direto e reduza colisões
de nomes em hierarquias de herança. Este mecanismo favorece o
encapsulamento e ajuda a proteger os detalhes de implementação. No name
mangling, o interpretador acrescenta _NomeDaClasse ao início do
identificador quando este começa por __, tornando-o menos acessível de
fora. Embora seja tecnicamente possível aceder ao nome transformado, essa
prática não é recomendada, pois fragiliza a manutenção e quebra a intenção
de ocultação.
Para acesso controlado ao estado, recomenda-se definir métodos de acesso:
get_* para leitura e set_* para escrita. Estes métodos permitem validar
valores, manter invariantes internas e evitar dependências do código cliente
em detalhes de armazenamento, preservando a clareza da interface pública
da classe.

288
Neste exemplo, apresenta-se uma classe Automovel que ilustra, de forma
integrada, o encapsulamento: usa _nome para ocultação fraca, __nome para
ocultação forte com name mangling, e providencia getters/setters para
acesso controlado, evidenciando a separação entre interface pública e
detalhes internos.
No exemplo seguinte cria-se uma instância de Automovel e demonstra-se,
o acesso a um atributo público, a um atributo com ocultação fraca (_) e, por
fim, o acesso controlado ao atributo com ocultação forte (__) através do
respetivo getter.

289
10.4 Herança

A herança é o mecanismo que permite definir novas classes a partir de classes


existentes. A classe de base chama-se superclasse; a classe derivada chama-
se subclasse. A subclasse recebe (herda) os atributos e métodos da
superclasse e pode:
1. reutilizar tal como estão;
2. adicionar novos atributos e métodos;
3. sobrescrever (redefinir) os herdados quando precisa de
comportamento diferente.
O propósito é evitar duplicação de código, organizar conceitos por afinidade
(relações “é-um”) e facilitar a evolução: melhorias feitas na superclasse
propagam-se, quando apropriado, às subclasses. A herança concentra o
comum na superclasse e deixa o específico para as subclasses. Neste
exemplo é apresentada a estrutura base de herança entre uma superclasse
(Animal) e duas subclasses (Cao e Gato):

Mesmo sem acrescentar código nas subclasses, Cao e Gato já possuem tudo
o que Animal expõe (por exemplo, fazer_som). Isto ilustra a reutilização

290
direta de métodos e atributos herdados. Assim, atributos como nome e vivo
e métodos utilitários como info() vivem uma única vez em Animal e são
herdados por quem deles precisa.
Quando for necessário que as subclasses tenham atributos e métodos
específicos da classe, define-se um __init__ na subclasse e usa-se
super().__init__(atributo) para reaproveitar a parte comum da
classe. Depois, acrescenta-se o que é específico. Quando um método é
invocado numa subclasse, o Python procura a implementação desse método
dentro da própria classe e, caso não encontre, procura na classe superclasse.
Ao utilizar super(), a implementação de um método é delegada
explicitamente para a superclasse, permitindo que a subclasse modifique ou
estenda o comportamento definido na superclasse. Conforme apresentado
neste exemplo da subclasse Cao:

O exemplo define a subclasse Cao que, ao herdar de Animal, chama


super().__init__(nome) para reutilizar a inicialização do nome,
acrescenta o atributo específico raca e disponibiliza o método próprio
abanar_cauda().

291
10.4.1 Herança múltipla

Em Python, é possível que uma classe herde de mais do que uma classe ao
mesmo tempo. A isto chama-se herança múltipla. A ideia é permitir que uma
classe final combine comportamentos vindos de várias origens diferentes,
sem que seja necessário escrever tudo de raiz em cada classe. Muitas vezes,
essas classes intermediárias não representam propriamente um tipo “do
mundo real”, mas sim uma capacidade adicional: por exemplo, algo que sabe
registar operações de log, algo que sabe guardar a data de criação, algo que
sabe ser exportado etc. A estas classes centradas numa capacidade em vez de
numa identidade chama-se frequentemente mixins. Em vez de dizer “um
objeto é um Logavel”, diz-se antes “este objeto tem a capacidade de registar
log porque herda de Logavel”. Em termos práticos, herança múltipla permite
construir tipos finais que agregam várias capacidades sem duplicar código.
Considere-se o seguinte exemplo da classe Animal definida anteriormente:

Agora introduz-se uma segunda classe independente, Nadador. Esta classe


não herda de Animal. Não descreve um “tipo de animal” completo, mas
sim uma capacidade concreta: nadar. Esta classe tem o seu próprio atributo
(velocidade) e sabe responder à pergunta "como se move?" através do
método mover().

292
Até aqui tem-se duas classes que não se sobrepõem:
− Animal sabe o nome e sabe apresentar-se com info().
− Nadador sabe guardar uma velocidade de nado e sabe dizer que se
move “a nadar”.
A seguir cria-se uma classe Pato que herda das duas ao mesmo tempo:
herda de Animal e herda de Nadador. Ou seja, Pato passa a ser ao
mesmo tempo “um Animal com nome” e “algo que nada”. Isto já é herança
múltipla.

A declaração class Pato(Nadador, Animal): indica que Pato herda


simultaneamente de Nadador e de Animal, e a ordem em que estas classes
aparecem é relevante porque, sempre que se chama um método num objeto
Pato, o Python procura primeiro esse método em Pato, depois em
Nadador, depois em Animal, e por fim nas classes base internas da
linguagem, seguindo uma sequência conhecida como MRO (Method
Resolution Order); no construtor __init__ de Pato, é recebido nome e
velocidade e chama explicitamente Animal.__init__(self,

293
nome) para inicializar a parte que vem de Animal (isto é, dar um nome ao
pato) e Nadador.__init__(self, velocidade) para inicializar
a parte que vem de Nadador (isto é, a velocidade de nado), o que significa
que se está a construir manualmente as duas dimensões do objeto: a
identidade (nome) e a capacidade funcional (velocidade). Por fim,
define-se em Pato um método próprio mover() que, mesmo existindo já
em Nadador, é reescrito de forma mais específica para este tipo concreto,
combinando dados herdados das duas superclasses (o nome vindo de
Animal e a velocidade de nado vinda de Nadador), o que exemplifica
como uma subclasse pode personalizar e especializar o comportamento
herdado.
Agora, veja isto a funcionar na prática:

A chamada [Link]() funciona porque Pato herda de Animal, e Animal


define o método info(). Não é preciso reescrever info() em Pato.
A chamada [Link]() vai usar o método definido em Pato, que constrói uma
frase completa combinando informação vinda das duas “linhas” de herança. Ele
consegue usar [Link], que vem do Animal.__init__, e
[Link], que vem do Nadador.__init__.

294
10.5 Sobrescrita de métodos

Como visto anteriormente, a herança permite construir novas classes a partir


de classes existentes. A classe de base é a superclasse: a derivada é a
subclasse. A subclasse recebe atributos e métodos da superclasse e pode
adicionar funcionalidades ou sobrescrever comportamentos para os adaptar
ao seu contexto. O polimorfismo decorre daí: o mesmo nome de método pode
produzir comportamentos diferentes consoante a classe real do objeto,
permitindo tratar vários tipos de forma uniforme. Sobrescrever é redefinir na
subclasse um método que existe na superclasse, mantendo a mesma
assinatura (nome e parâmetros relevantes). O nome do método mantém-se;
o comportamento adapta-se ao tipo concreto.
Um exemplo de utilização está apresentado a seguir.

A saída será:

295
Como é possível observar no exemplo anterior, a subclasse herda
fazer_som de Animal, mas redefine o método com um comportamento
específico. Neste exemplo, é possivel verificar que o mesmo nome de
método provoca ações distintas consoante a classe real do objeto.

10.6 Exercícios Propostos

1) Defina a classe Estudante com atributo privado __nome.


Implemente os métodos:
− o método get_nome() que devolve o nome atual;
− o método set_nome(novo_nome) que atualiza o nome, validando
que não é vazio nem só espaços;
− o método imprimir_nome() que apresenta no ecrã o nome do
estudante no formato Estudante: <nome>.
Crie uma instância de Estudante("Guido van Rossum"). Invoque
o método imprimir_nome().
Este é um exemplo de saída:

2) Defina a classe Colaborador com os atributos privados __nome,


__nif (exactamente 9 dígitos), __salario_base (valor > 0) e
__departamento, o atributo estático empresa = "Tech &
Data, SA" e a lista __avaliacoes (notas de desempenho de 1 a
5). Implemente os seguintes métodos:

296
− get_*/set_* com validação (nome não vazio, NIF com 9 dígitos
numéricos, salário positivo, departamento não vazio);
− o método transferir_para(novo_departamento) que altera
o departamento com validação;
− o método registar_desempenho(nota) que diciona uma nota
entre 1 e 5 à lista de avaliações do utilizador.
Crie uma instância Colaborador ("guido van rossum",
"123456789", 1600, "Engenharia"). Imprima, por esta ordem,
o nome, o NIF, o salario base, o departamento e o nome da
empresa. Atualize depois o nome para "Guido van Rossum", o
NIF para "987654321" e o salario base para 1700.0; transfira o
colaborador para "I&D". Por fim, imprima numa única linha o nome, NIF,
salario base, departamento e get_avaliacoes() (que deverá
estar vazio).
Este é um exemplo de saída:

3) Considerando a classe Colaborador do exercício anterior, defina a


subclasse Estagiario(Colaborador) com o atributo privado
__bolsa e métodos:

297
− get_*/set_* com validação (bolsa deve ser numérica maior do que
0, nome tem de ser uma string, NIF tem de ser string com exatamente 9
dígitos, salário base numérico tem de ser maior do que 0, o
departamento tem de ser uma string não vazia;
− redefina salario_mensal() para devolver o montante da bolsa;
− o método elegivel_efetivacao() que devolve True se
existirem pelo menos 3 avaliações e se a média das avaliações for igual
ou superior a 4 (use as avaliações herdadas de Colaborador).
Crie uma instância de Estagiario (com nome, NIF, salario base,
departamento e bolsa válidos), registe três avaliações e apresente: o
salário mensal (bolsa) e o resultado de elegibilidade à efetivação.

Este é um exemplo de saída:

298
11 BIBLIOTECAS ÚTEIS EM PYTHON

11.1 Módulos e pacotes

Ao importar um módulo em Python, o interpretador segue uma sequência de


diretórios definida em [Link] para localizar o ficheiro correspondente.
Esta lista inclui o diretório do script atual, os caminhos da biblioteca padrão
e os diretórios de pacotes de terceiros instalados (site-packages).
Um aspeto importante é a distinção entre pacotes clássicos e namespace
packages. Os pacotes clássicos são diretórios que contêm um ficheiro
__init__.py, enquanto os namespace packages (introduzidos na
PEP 420) dispensam este ficheiro e permitem que um mesmo pacote seja
distribuído por múltiplos diretórios, sendo especialmente úteis em projetos
grandes ou com arquitetura modular.
Ao importar módulos, o Python permite duas abordagens principais:
importação absoluta e importação relativa.
A importação absoluta indica o caminho completo até ao módulo, a partir da
raiz do projeto ou de um pacote principal. É a forma mais recomendada, por
ser mais clara e facilitar a leitura do código:
from meu_pacote.utils import texto
Neste exemplo, o Python procura o módulo texto dentro do pacote utils,
que por sua vez pertence a meu_pacote.
A importação relativa, por outro lado, é usada dentro de um mesmo pacote e
recorre a pontos para indicar a posição do módulo em relação ao ficheiro

299
atual. Apesar de ser prática em alguns casos, pode tornar o código mais difícil
de compreender por quem não conhece a estrutura do projeto:
from .utils import texto # um nível acima
from ..comum import ajuda # dois níveis acima
Internamente, o Python mantém um cache de módulos em [Link],
carregando cada módulo apenas uma vez durante a execução. Isto torna
reimportações subsequentes instantâneas, pois o módulo é obtido
diretamente da memória em vez de ser relido do ficheiro. No entanto, durante
o desenvolvimento ou em modo interativo (REPL), este comportamento
pode gerar confusão: se o ficheiro do módulo for alterado após o primeiro
import, o Python não aplica automaticamente as alterações.
Para resolver isso sem reiniciar o interpretador, pode-se usar a função
[Link](nome_do_módulo). Essa função força o Python
a ler novamente o ficheiro original e a atualizar o módulo em memória,
garantindo que as modificações feitas no código passem a ter efeito imediato.
Este recurso é especialmente útil durante testes e sessões de desenvolvimento
interativo, permitindo iterar rapidamente sobre o código sem precisar
reiniciar o ambiente.
Um cuidado adicional deve ser tomado com os ciclos de importação, que
ocorrem quando o módulo A importa o módulo B, e B tenta importar
novamente A. Essa situação cria uma dependência circular e normalmente
resulta num AttributeError. Para evitar o problema, pode-se atrasar o
import para dentro de uma função, mover partes comuns para um terceiro
módulo ou reorganizar a estrutura do código de modo a manter dependências
unidirecionais e claras.

300
11.1.1 Criar módulos e pacotes próprios

Projetos Python modernos costumam adoptar uma estrutura padronizada que


facilita testes, empacotamento e manutenção. Um formato comum inclui um
diretório src/ contendo o código-fonte principal, um diretório tests/
com os testes automatizados e um ficheiro [Link] com as
configurações e metadados do projeto. Essa organização ajuda a isolar o
código do pacote, evita importações acidentais e segue as boas práticas
recomendadas pela comunidade.
Dentro do pacote, o ficheiro __init__.py pode ser usado para definir a
interface pública do módulo. Isso é feito através da variável __all__, que
lista os identificadores (funções, classes, constantes etc.) a serem exportados
quando se usa from pacote import *. Essa abordagem mantém a
API controlada e facilita a compreensão do que é realmente parte do pacote
público.
Além disso, é comum incluir metadados como __version__ (versão do
pacote) e __author__ (autor ou equipa responsável). Esses campos
ajudam na documentação e rastreabilidade das versões do projeto.
Para que um módulo possa ser executado como um script independente,
utiliza-se a sintaxe:
python -m nome_do_modulo
Dentro do ficheiro, deve-se incluir a condição:
if __name__ == "__main__":
# código executado apenas quando o módulo
# é executado diretamente

301
Esse padrão é amplamente usado para criar módulos que funcionam tanto
como bibliotecas quanto como programas executáveis.
Entre as boas práticas de design de código em Python destacam-se:
− manter módulos coesos, com uma responsabilidade bem definida
− utilizar tipagem estática (PEP 484) para tornar o código mais seguro e
legível
− incluir um ficheiro [Link] quando o pacote for distribuído com type
hints
− escrever docstrings claras e concisas, preferencialmente com
exemplos de uso que possam ser testados automaticamente com o
módulo doctest.
Essas práticas tornam o código mais modular, previsível e fácil de integrar
em ambientes de produção ou de investigação científica.

11.2 Biblioteca padrão – tópicos avançados e práticos

11.2.1 math: precisão, combinatória e utilidades

O módulo math amplia as operações matemáticas básicas com funções de


alta precisão e desempenho. Para cálculos combinatórios, as funções
[Link]() e [Link]() permitem calcular combinações e
permutações com inteiros de precisão arbitrária, evitando problemas de
overflow típicos de implementações baseadas em ponto flutuante.
Em questões de precisão numérica, [Link]() é superior à função
sum() integrada da linguagem, pois utiliza um algoritmo que compensa os
erros de arredondamento acumulados. Da mesma forma,
302
[Link]() oferece uma comparação segura entre números de
ponto flutuante, permitindo definir tolerâncias relativas e absolutas.
Para tratamento de valores especiais, o módulo inclui funções como
[Link](), [Link]() e [Link](), que
identificam números infinitos e valores indefinidos (NaN). Em contextos
geométricos, [Link]() calcula hipotenusas de maneira
numericamente estável e [Link]() generaliza esse conceito para
distâncias em múltiplas dimensões.
Outras funções úteis incluem [Link](), que calcula o produto de uma
sequência (semelhante ao sum(), mas para multiplicação) e
[Link](), que retorna a distância entre um número e o seu vizinho
mais próximo na representação de ponto flutuante (IEEE 754).

11.2.2 random: objetos geradores, pesos e amostragem

O módulo random é amplamente usado para simulações, jogos e testes


estatísticos. Contudo, não deve ser utilizado para fins criptográficos, pois
nesses casos, recomenda-se o módulo secrets.
Uma prática moderna é criar instâncias próprias da classe Random, em vez
de usar diretamente as funções globais do módulo. Isso evita interferências
no estado global do gerador e permite reproduzir resultados com facilidade
ao definir uma seed específica:

303
Para amostragem probabilística, [Link]() realiza seleções
com reposição e suporta pesos, enquanto [Link]() faz
amostragem sem reposição. A função [Link]() embaralha
listas in-place, sendo eficiente, mas modificando o conteúdo original.
Em grandes conjuntos de dados, recomenda-se gerar valores sob demanda
através de iteradores, economizando memória e tornando o processamento
escalável.

11.2.3 datetime: fusos horários e horário de verão com zoneinfo

A partir do Python 3.9, o módulo zoneinfo passou a fazer parte da


biblioteca padrão, substituindo a dependência externa pytz e oferecendo
suporte completo a fusos horários.
A regra de ouro ao trabalhar com datas e horas é armazenar tudo em UTC
e converter para o horário local apenas quando for necessário apresentar os
dados ao utilizador. Objetos datetime com informação de fuso horário
(chamados aware) devem ser preferidos aos que não têm (os naive), pois
garantem cálculos corretos mesmo durante transições de horário de verão.
Funções como [Link]() e isoformat()
simplificam a conversão de datas no formato ISO 8601. Ao calcular
diferenças de tempo que cruzam alterações de DST (Daylight Saving Time),
o uso de datetimes aware assegura resultados consistentes.
Para medições de duração e desempenho, deve-se usar
time.perf_counter(), que é monotónico e não sofre interferência de
ajustes no relógio do sistema.

304
11.3 Introdução a NumPy (arrays, vectorização e broadcasting)

O NumPy é a base da computação científica em Python. Esta biblioteca


introduz o tipo de dado ndarray, um array multidimensional que armazena
elementos homogéneos em blocos contíguos de memória, permitindo
operações matemáticas altamente otimizadas.
Diferentemente das listas nativas do Python, os arrays do NumPy são
implementados em C, o que garante grande eficiência e aproveitamento de
memória.
Cada ndarray possui dois atributos fundamentais:
− shape, que indica as suas dimensões (número de linhas, colunas etc.)
− dtype, que representa o tipo de dados armazenado (por exemplo,
int32, float64, bool)

11.3.1 Conceitos-chave

Um dos maiores benefícios do NumPy é a vectorização. Isso significa que


operações matemáticas são aplicadas a todos os elementos de um array de
uma só vez, sem necessidade de loops explícitos em Python. Essa abordagem
resulta em código mais conciso e, sobretudo, muito mais rápido, pois as
operações são executadas em baixo nível.
Outro conceito fundamental é o broadcasting: quando dois arrays de
tamanhos diferentes são usados numa operação, o NumPy tenta
automaticamente “expandir” o menor para que ambos tenham dimensões
compatíveis. Isso evita cópias desnecessárias e facilita expressões
matemáticas elegantes.

305
A criação de arrays pode ser feita de diversas formas. A mais simples é a
partir de uma lista Python, como no exemplo a seguir.

Além disso, existem funções utilitárias muito usadas, tais como as


apresentadas a seguir:

O gerador moderno [Link].default_rng() substitui a API antiga


de aleatoriedade ([Link] e afins). Este gerador fornece
números aleatórios de alta qualidade e uma interface mais previsível, como
no exemplo a seguir que cria um gerador independente de números aleatórios
(rng) com seed fixa (42), garantindo reprodutibilidade.
A função normal() gera uma matriz 3×3 com valores distribuídos
segundo a distribuição normal padrão (média 0, desvio 1).
O uso de default_rng() é a forma atual e recomendada de gerar
números aleatórios em NumPy, substituindo a API antiga baseada em
[Link]().

306
As chamadas funções universais (ou ufuncs), como [Link](),
[Link]() e [Link](), aplicam-se a todos os elementos do array de
forma vetorizada.
Reduções ao longo de eixos específicos são feitas com métodos como
mean(), sum(), min(), max() e std(), usando o parâmetro
axis para definir a direção da operação, como no próximo exemplo:

As máscaras booleanas são outro recurso poderoso, pois permitem filtrar


dados sem loops. Basta criar uma máscara com uma condição e ela atua como
um filtro lógico, como por eexmplo
positivos = matriz[matriz > 0]
O resultado é um novo array contendo apenas os valores positivos.

11.3.2 Views vs. cópias

Um aspeto essencial para compreender o comportamento do NumPy é a


diferença entre views (vistas) e cópias.
Quando se realiza um fatiamento de um array, o NumPy normalmente cria
apenas uma view, isto é, uma nova “janela” para os mesmos dados em
memória. Qualquer modificação feita nessa view reflete-se no array original,
como no exemplo a seguir.

307
Esse mecanismo economiza memória e tempo, mas pode causar surpresas se
o programador não estiver atento.
Para criar uma cópia independente dos dados, deve-se usar o método
copy(), como a seguir:
y = x[2:7].copy()
Além disso, é importante saber que operações de indexação avançada (com
listas, arrays booleanos ou múltiplas dimensões) criam sempre cópias, nunca
views.
Saber essa diferença é crucial para otimizar o desempenho e evitar bugs sutis
em código científico.

11.3.3 Entrada/Saída e tipos de dados

O NumPy oferece métodos eficientes para leitura e escrita de arrays.


Para armazenamento binário, usa-se:

Esses formatos são extremamente rápidos e preservam o tipo de dados


(dtype) com precisão.
Também é possível exportar e importar arrays em texto ([Link] e
[Link]), embora com menor eficiência e precisão.
A conversão entre tipos é feita com o método astype(), que gera sempre
uma cópia:
308
matriz_int = [Link](np.int32)
A escolha correta do dtype influencia diretamente o consumo de memória
e a precisão dos resultados, especialmente em cálculos de larga escala.

11.3.4 Estatística e aleatoriedade

O NumPy inclui um gerador de números aleatórios avançado, capaz de


produzir amostras de diversas distribuições estatísticas: normal, gama,
binomial, exponencial, dentre outras.
Essas funções são essenciais para simulações, modelagem estatística e testes
de hipóteses, como no exemplo a seguir que gera-se uma amostra de 10 000
valores seguindo a distribuição gama com parâmetros shape = 2.0 e
scale = 2.0.
O objeto rng é o gerador moderno de números aleatórios do NumPy, e o
método gamma() produz dados conforme essa distribuição.
As funções chamadas mean() e std() calculam a média e o desvio-
padrão da amostra, que devem aproximar-se dos valores teóricos esperados
(média ≈ 4 e desvio ≈ 2,83) devido à lei dos grandes números.

Ressalta-se em relação ao exemplo anterior, que em Python, o uso de


sublinhados em números, como 10_000, é apenas um recurso de
legibilidade introduzido no Python 3.6. Ou seja, estas três formas são
idênticas:

309
10_000 == 10000 == 0b10011100010000

Todas representam exatamente o mesmo valor inteiro (dez mil).


A vantagem de usar o sublinhado (_) é deixar números longos mais fáceis
de ler, da mesma forma que se usa separadores de milhar em escrita comum
(exemplo: 10 000 ou 1.000.000).

Além das funções estatísticas mean(), std(), as demais funções, como,


var() e corrcoef() são implementadas em C e podem operar sobre
eixos específicos de grandes matrizes com enorme eficiência.
Assim, o NumPy oferece as bases para cálculos numéricos de alto
desempenho, servindo de fundação para bibliotecas mais avançadas como
Pandas, SciPy e scikit-learn.

11.4 Introdução a Matplotlib (API Orientada a Objetos)

A visualização de dados é uma das etapas mais importantes na análise e


comunicação de resultados científicos. Em Python, a biblioteca
Matplotlib é o padrão de facto para esse fim. Criada por John D. Hunter
em 2003, ela foi inspirada na linguagem MATLAB e tornou-se a base sobre
a qual muitas outras ferramentas gráficas foram construídas, como
Seaborn e Pandas Plot. Sua principal vantagem é a flexibilidade, pois
com Matplotlib, é possível produzir desde gráficos simples de linha até
figuras complexas com múltiplos eixos, anotações e exportação em
diferentes formatos.

310
11.4.1 Estrutura geral e filosofia da biblioteca

A arquitetura do Matplotlib é organizada considerando três diferentes


níveis principais:
− Figure  representa toda a figura ou página de desenho. Pode conter
um ou vários gráficos
− Axes  é a área de plotagem propriamente dita, onde os dados são
representados. Cada figura pode ter vários objetos Axes.
− Artists  são os elementos visuais, como linhas, textos, marcadores,
eixos e legendas, que compõem o gráfico.
A criação de um gráfico segue normalmente o modelo conforme o do
exemplo a seguir, onde a função [Link]() cria simultaneamente
uma Figure (fig) e um Axes (ax). Todas as operações subsequentes
são aplicadas ao objeto ax, o que reflete o estilo orientado a objetos preferido
em aplicações profissionais.

311
A saída deste código, que traça a função seno, está apresentada a seguir.

11.4.2 Tipos fundamentais de gráficos

O Matplotlib oferece uma grande variedade de representações gráficas,


capazes de traduzir conjuntos de dados numéricos ou categóricos em formas
visuais de fácil interpretação. Essas representações são amplamente
empregadas em relatórios científicos, dashboards e análises exploratórias,
permitindo identificar padrões, tendências e relações de forma imediata.
Entre as mais usadas, destacam-se:

− Gráficos de linha  para mostrar evolução contínua ou séries temporais


− Gráficos de dispersão (scatter)  ideais para relações entre variáveis
− Histogramas  utilizados para representar distribuições de frequência.
− Gráficos de barras  adequados para dados categóricos.

312
A seguir, apresenta-se um exemplo contendo estes tipos de gráficos.

a) Exemplo de Gráfico de Linha

A saída está apresentada a seguir.

313
b) Exemplo de Gráfico de Dispersão

Cuja saída está a ser apresentada no gráfico a seguir.

314
c) Exemplo de Histograma

Após executar o código, esta é a saída no ecrã:

315
d) Exemplo de Gráfico de Barras

Após executar o código, eis a saída no ecrã

Essas são formas básicas, mas servem como ponto de partida para
composições mais ricas e relatórios visuais complexos.

316
11.4.3 Personalização e estilo

A principal força do Matplotlib é a sua capacidade de personalização. É


possível alterar cores, espessuras de linha, tamanhos de fonte, temas e estilos
globais.
Portanto, antes de criar os gráficos, pode-se ajustar parâmetros globais de
estilo para que todas as figuras sigam o mesmo padrão visual. Essa
configuração é feita através do dicionário rcParams, que controla
propriedades como resolução, tamanho de texto, grelhas e formatação dos
eixos. O exemplo a seguir mostra como definir um conjunto de preferências
gerais, garantindo consistência e legibilidade em todas as visualizações
geradas durante a sessão.

Também existem estilos predefinidos, aplicáveis com


[Link]('ggplot'), [Link]('seaborn-
v0_8'), entre outros. Em relatórios científicos, recomenda-se adotar
esquemas de cores acessíveis, como as paletas “tab10” e “tab20”, que
preservam o contraste mesmo para leitores com daltonismo.
Anotações, linhas de referência e textos adicionais são criados, como neste
exemplo:
317
Esses recursos enriquecem o gráfico e destacam informações relevantes sem
sobrecarregar a visualização.

11.5 Exercícios propostos

1. Estrutura modular e empacotamento em Python


Crie um projeto organizado em pacotes e subpacotes que represente uma
aplicação de loja virtual. Implemente módulos separados para produtos,
clientes e vendas, cuidando para evitar ciclos de importação entre eles.
No ficheiro __init__.py do pacote principal, defina uma API
pública controlada através da variável __all__.
Teste a execução direta de um dos módulos com if __name__ ==
"__main__": e explore o uso de [Link]() durante
o desenvolvimento.

2. Precisão numérica e desempenho com o módulo math


Implemente dois scripts para somar uma grande sequência de números
de ponto flutuante (por exemplo, 10⁶ valores aleatórios). Compare os
resultados obtidos com sum() e [Link](), medindo tanto o
tempo de execução quanto o erro acumulado em relação à soma exata.
Explique por que [Link]() apresenta maior precisão e em que
contextos essa diferença se torna relevante.

318
3. Simulação probabilística com o módulo random
Crie um programa que simule um sorteio ponderado de itens com
diferentes probabilidades, utilizando [Link]() com
pesos. Execute a simulação milhares de vezes e compare as frequências
observadas com as probabilidades teóricas esperadas, ilustrando
graficamente o resultado com Matplotlib.

4. Fuso horário e horário de verão com datetime e zoneinfo


Escreva um programa que calcule a diferença de tempo entre duas datas
em cidades de fusos horários distintos (exemplo: Lisboa e Rio de
Janeiro). Inclua no teste uma data que atravesse o início ou fim do
horário de verão. Analise e explique o resultado obtido, destacando o
comportamento de horas "repetidas" ou "inexistentes" e a importância
de usar objetos aware.

5. Broadcasting e vistas em NumPy


Crie uma matriz NumPy de valores aleatórios e implemente uma
normalização estatística (subtraindo a média e dividindo pelo desvio-
padrão) utilizando broadcasting. Verifique se a operação gerou views (e
não cópias) analisando o atributo .base dos arrays resultantes. Discuta
como esse comportamento afeta o desempenho e o uso de memória em
aplicações científicas.

6. Visualização de dados com Matplotlib

319
Desenvolva um relatório visual que apresente, numa única figura,
múltiplos tipos de gráficos:
− uma curva temporal (linha),
− um histograma de distribuição,
− e um gráfico de dispersão entre variáveis.
Utilize o modelo orientado a objetos (fig, ax =
[Link]()) e organize os gráficos em subplots bem rotulados.
Aplique um estilo consistente com rcParams e exporte o resultado em
pelo menos três formatos: PNG, SVG e PDF. Explique como a
personalização visual contribui para a clareza e a comunicação científica.

 Sugestão opcional (para alunos mais avançados)


Implemente um pequeno pipeline de análise de dados:
1. Gere dados simulados com NumPy e random
2. Analise estatisticamente com funções de math
3. Visualize resultados com Matplotlib.
O objetivo é consolidar o uso integrado das bibliotecas abordadas
neste capítulo.

320
12 DESENVOLVENDO INTERFACES GRÁFICAS COM TKINTER

A construção de interfaces gráficas de utilizador (GUI) é um dos elementos


mais visíveis e gratificantes no desenvolvimento de software. Uma aplicação
gráfica oferece interação direta, visibilidade imediata do estado do programa
e uma experiência mais intuitiva para o utilizador final.
No Python, a biblioteca Tkinter ocupa uma posição singular, pois é a
interface oficial do Python para o Tk GUI Toolkit, integrando-se de forma
nativa ao interpretador e sem a necessidade de qualquer instalação adicional.
Apesar de sua longevidade, o Tkinter mantém-se atual pela sua simplicidade,
robustez e ampla compatibilidade com diferentes sistemas operativos.

12.1 Introdução ao Tkinter

Tkinter fornece uma camada de abstração em torno da biblioteca Tk,


permitindo criar janelas, botões, caixas de texto, menus, quadros, tabelas e
diversos outros componentes gráficos. O paradigma de programação
subjacente é o event-driven, ou seja, o fluxo do programa é controlado por
eventos, isto é, cliques, movimentos do rato, pressionamentos de tecla,
seleções dentre outros.
Cada elemento visual (ou widget) é um objeto que responde a esses eventos
através de funções ou métodos associados, chamados callbacks. Assim, o
papel do programador passa a ser o de definir o comportamento da interface
em resposta a cada ação do utilizador.

321
O Tkinter é frequentemente a primeira escolha para quem deseja
desenvolver aplicações desktop em Python por diversas razões:
− Integração nativa: é distribuído com o Python, não exigindo
dependências externas
− Multiplataforma: o mesmo código executa-se, sem alterações, em
Windows, macOS e Linux
− Simplicidade conceitual: os widgets seguem uma hierarquia lógica e
coerente e o modelo de eventos é intuitivo
− Documentação extensa e estabilidade da API: o Tkinter é amplamente
documentado, o que o torna adequado tanto para iniciantes como para
profissionais que necessitam de uma base sólida para aplicações desktop.
Apesar de existirem alternativas mais modernas, como PyQt, PySide ou
Kivy, o Tkinter continua a ser o ponto de partida ideal para compreender
os fundamentos de programação de interfaces em Python.

12.2 Configuração Inicial

Antes de iniciar o desenvolvimento, basta importar os módulos essenciais:

A convenção import tkinter as tk simplifica a escrita e evita


colisões de nomes. O submódulo ttk fornece widgets com aspeto nativo
do sistema operativo (como botões e menus estilizados), enquanto
322
messagebox oferece janelas de diálogo padrão para notificações, alertas e
mensagens de confirmação.
Todo o código Tkinter deve ser executado no thread principal, dado que
o Tk não é thread-safe. Quando for necessário executar operações
demoradas (por exemplo, leitura de ficheiros volumosos ou pedidos de rede),
deve-se recorrer a threads auxiliares e comunicar os resultados de volta à
GUI através de mecanismos como after() ou queue.

12.3 Estrutura Básica de uma Aplicação Tkinter

A estrutura fundamental de uma aplicação Tkinter consiste em três etapas:


a) Criação da janela principal → instanciar o objeto Tk()
b) Execução do ciclo de eventos → com o método mainloop(), que
mantém a aplicação ativa enquanto espera interações do utilizador.

O exemplo a seguir apresenta a forma mais limpa e modular de estruturar


uma aplicação. Neste exemplo, a função mainloop() inicia o ciclo interno
do Tk, responsável por processar continuamente todos os eventos de entrada,
como cliques de rato, pressionamento de teclas e redimensionamentos da
janela.
Enquanto esse ciclo estiver ativo, a aplicação permanece em execução,
reagindo às ações do utilizador.
É nesse momento que a interface se torna interativa e funcional, encerrando-
se apenas quando a janela principal é fechada.

323
A saída deste código está apresentada a seguir:

324
Ao clicar no botão Sair, a seguinte messagebox aparece no ecrã:

12.4 Widgets Fundamentais

Os widgets são os componentes visuais de uma GUI. O Tkinter oferece


uma grande variedade e os mais utilizados podem ser agrupados em três
categorias: exibir texto ou imagem, entrada de dados e interação por
comando.

12.4.1 Label (Rótulos)

O widget Label é usado para apresentar textos, ícones ou imagens estáticas.


Pode ser configurado quanto a tipo de letra, cor, espaçamento e até incluir
imagens externas.

325
A saída deste exemplo é apresentada a seguir.

326
Um erro comum é perder a referência à imagem carregada. Quando o objeto
PhotoImage é recolhido pelo garbage collector, o rótulo deixa de exibir a
imagem. A atribuição label_imagem.image = foto garante que a
imagem permaneça acessível enquanto o label existir.

12.4.2 Button (Botões)

Os botões são elementos de ação. O Tkinter permite definir o


comportamento de um botão através do parâmetro command, que referencia
uma função de retorno (callback).
O comando deve ser passado sem parênteses, pois a função será executada
apenas quando o utilizador clicar no botão.
É possível também associar eventos específicos com bind(), como entrada
e saída do rato (<Enter>, <Leave>), cliques com diferentes botões
(<Button-1>, <Button-3>) e combinações de teclas.

12.4.3 Entry (Campos de Entrada)

O widget Entry permite capturar texto de uma única linha, sendo


amplamente usado em formulários e autenticações.
No exemplo a seguir, cria-se uma pequena janela com um campo Nome: e
um botão Enviar. O utilizador pode digitar um nome e carregar em Enter
ou clicar no botão. A seguir exibe uma caixa de mensagem com a saudação
correspondente ou, se o campo estiver vazio, apresenta um aviso.

327
A saída é uma caixa para entrada do nome, conforme a imagem a seguir.

328
Se um nome for digitado, como por exemplo, o nome Maria José, uma caixa
de mensagens é então exibida, após pressionar o botão enviar, conforme esta
imagem:

Se nenhum nome for digitado, a seguinte messagebox aparece:

Além do texto simples, Entry suporta validações de dados, substituição de


caracteres (para senhas) e manipulação dinâmica por eventos de foco
(<FocusIn>, <FocusOut>).
É também possível associar variáveis de controlo (StringVar, IntVar)
que refletem automaticamente as alterações entre a interface e o código.

329
12.4.4 Text (Área de Texto)

Para entrada e edição de texto multilinha, utiliza-se o widget Text,


frequentemente acompanhado de scrollbars verticais e horizontais.
Este próximo exemplo apresenta de forma simples e completa, como utilizar
o widget Text para criar uma área de edição multilinha acompanhada por
uma scrollbar vertical funcional. A implementação inclui ainda operações
fundamentais de manipulação de texto, como a inserção automática de
conteúdo, a limpeza integral da área e a recuperação do texto introduzido
pelo utilizador, exibido posteriormente através de uma caixa de mensagem.
Trata-se de uma demonstração prática que mantém a coerência com o estilo
adotado ao longo do livro, ilustrando de forma direta como construir
interfaces funcionais e intuitivas com Tkinter.

330
A saída deste exemplo corresponde ao comportamento esperado de uma área
de texto interativa. Após a executar o programa, surge uma janela contendo
o campo multilinha com scrollbar associada, permitindo deslocar o conteúdo
quando o texto excede o espaço visível. Os botões colocados abaixo da área
possibilitam testar imediatamente todas as operações implementadas, ou
seja, se pressionar “Inserir Texto”, acrescenta-se uma linha de exemplo. Ao
selecionar “Limpar”, o conteúdo é removido integralmente, e através do
botão “Obter Texto”, o utilizador pode visualizar o texto escrito numa caixa
de mensagem, conforme imagens ilustrativas a seguir.

331
Ao clicar em “Inserir Texto”:

332
Também é possível que o próprio utilizador introduza texto na área, como se
observa na imagem seguinte. Como o conteúdo excede o espaço disponível,
o scrollbar vertical (eixo y) torna-se visível para permitir a navegação pelas
várias linhas.

O botão “Limpar” remove todo o conteúdo da área de texto, enquanto o botão


“Obter Texto” copia o texto presente para a área de transferência e apresenta,
em seguida, a seguinte mensagem:

333
Assim, é possível verificar de forma clara como cada funcionalidade atua
sobre o widget Text, e observar que tanto a inserção como a remoção e a
leitura do conteúdo funcionam conforme o esperado, tornando o exemplo
particularmente eficaz para demonstrar o uso deste componente em
Tkinter.
O Text é extremamente versátil, pois pode ser usado para construção de
editores, blocos de notas, consolas de saída ou até mesmo renderização de
texto formatado.

12.5 Gerenciadores de Layout

Um dos aspetos mais importantes numa interface gráfica é a forma como os


elementos são posicionados dentro da janela. Para isso, o Tkinter
disponibiliza três gerenciadores de geometria, pack, grid e place,
que determinam como os widgets são organizados no ecrã.
Cada gerenciador segue uma lógica própria e, por isso, cada Frame deve
utilizar apenas um deles. Misturá-los no mesmo container é uma das causas
mais frequentes de comportamento inesperado no layout.
a) pack
O gerenciador pack organiza os widgets por empilhamento, sendo ideal para
layouts simples onde basta posicionar elementos no topo, na base ou nos
lados da janela.
Conforme este exemplo a seguir com a sua respetiva saída.

334
Neste exemplo os botões são empilhados verticalmente de acordo com a ordem em
que foram declarados.

b) grid
O gerenciador grid distribui os widgets numa matriz de linhas e colunas,
sendo especialmente útil para formulários e interfaces onde é importante
alinhar rótulos, entradas e botões. Um simples exemplo de uso com grid
está a ser aperesentado a seguir, com a respetiva saída.

335
Cada widget é posicionado numa célula da grelha, facilitando alinhamentos
precisos.

c) place
O gerenciador place posiciona widgets em coordenadas absolutas (x,y)
ou relativas (proporções da janela). Deve ser usado com cuidado,
principalmente quando é necessário que a interface se adapte ao
redimensionamento da janela. A seguir, há um exemplo do uso do place.

Exemplo de uso com place:


Neste caso, o primeiro botão é colocado em coordenadas fixas, enquanto o
segundo mantém-se centrado, independentemente do tamanho da janela.

A saída deste código é a seguinte:

336
É importante ressaltar que o pack é a escolha natural para interfaces
simples. O grid é indicado para formulários estruturados e a maioria dos
layouts organizados. Já o place deve ser usado apenas quando é necessária
precisão absoluta na posição dos elementos.

12.6 Widgets Avançados

Além dos widgets fundamentais, o Tkinter disponibiliza componentes


mais avançados que permitem construir interfaces ricas e organizadas. Entre
os mais utilizados encontram-se o Listbox, o Combobox (do módulo
ttk) e o Treeview, cada um adequado a diferentes situações de
apresentação e seleção de dados.
O Listbox apresenta uma lista vertical de itens, permitindo seleção
simples ou múltipla. É útil para menus de escolha, listas de elementos e
navegação básica, conforme o trecho de código do exemplo com a sua
respetiva saída.

337
O Combobox, disponível através do ttk, fornece uma lista suspensa com
valores pré-definidos, sendo ideal para opções controladas, como seleção de
categorias ou configurações. O trecho de código deste exemplo com a sua
respetiva saída, sendo a primeira imagem com a lista suspensa fechada e a
segunda com a lista suspensa após ser clicada.

O Treeview é um dos widgets mais poderosos do Tkinter. Permite


apresentar informação de forma tabular ou hierárquica, semelhante a uma
tabela ou explorador de ficheiros, possibilitando a visualização estruturada
de dados.
Este exemplo a seguir apresenta um Treeview inicializado a partir de um
dicionário de Python, onde cada chave corresponde ao nome de um animal e
o respetivo valor representa o número de patas.
A estrutura tabular facilita a visualização destes dados, permitindo comparar
rapidamente diferentes espécies.
Após o código, a saída é então mostrada para se perceber como a tabela se
apresenta no ecrã.

338
Estes widgets suportam eventos específicos: <<ListboxSelect>>,
<<ComboboxSelected>> e <<TreeviewSelect>>, que permitem
reagir quando o utilizador altera uma seleção. Dessa forma, é possível criar
interfaces de navegação, consulta e gestão de dados com poucas linhas de
código e grande flexibilidade.

339
Neste exemplo, ao clicar em uma das opções, esta fica marcada em azul. A
saída apresentada mostra a seleção da Opção B.

12.7 Menus e Caixas de Diálogo

O Tkinter disponibiliza o widget Menu, permitindo construir menus


tradicionais de aplicação, com barras principais, submenus e atalhos de
teclado (aceleradores). Os menus acrescentam estrutura e profissionalismo à
interface, facilitando o acesso a funcionalidades como Abrir, Guardar,
Editar ou Sair.

340
Um exemplo simples é o Listbox, onde o evento <<ListboxSelect>>
permite detetar imediatamente quando o utilizador escolhe um item. No
trecho anterior, sempre que ocorre uma seleção, o programa obtém o valor
correspondente e apresenta-o no terminal. Este é o caso mais compacto para
ilustrar como Tkinter reage a eventos de seleção com poucas linhas de
código. Neste exemplo, exibe-se um Menu denominado Ficheiro, que ao
ser clicado, abre as opções de Abrir e Sair.

Cuja saída é:

Para complementar os widgets apresentados, o Tkinter disponibiliza um


conjunto de caixas de diálogo padrão que facilitam tarefas frequentes nas
aplicações gráficas. Estas caixas permitem ao utilizador interagir com o
sistema operativo de forma natural, sem que o programador precise de
implementar esse comportamento manualmente. O módulo messagebox,
que já foi utilizada nos exemplos anteriores, oferece janelas para apresentar

341
avisos, erros, mensagens informativas ou pedidos de confirmação, sendo
uma forma rápida de comunicar o estado de uma operação.
O filedialog permite selecionar ficheiros ou escolher um local para
guardar informação, recorrendo à janela nativa do sistema operativo, o que
garante familiaridade e consistência visual.
Por fim, o colorchooser disponibiliza um seletor de cores intuitivo, útil
em programas que necessitam de personalização visual. Estas caixas de
diálogo tornam a interface mais completa e reduzem significativamente o
esforço de desenvolvimento, ao disponibilizar soluções prontas para algumas
das interações mais comuns numa aplicação.
Estas caixas de diálogo encapsulam funcionalidades complexas e oferecem
um comportamento consistente (visualmente e funcionalmente) em
diferentes sistemas operativos, poupando tempo ao programador e reduzindo
a probabilidade de erros. São componentes fundamentais para criar
aplicações que interajam de forma natural com o utilizador.

12.8 Aplicação: Mini Editor de Texto

Este pequeno exemplo mostra como combinar diferentes widgets do


Tkinter numa aplicação funcional. A interface inclui uma área de texto
com scrollbar e um menu simples com as opções “Abrir” e “Guardar
Como”, suficientes para criar um editor básico. Apesar de reduzido,
demonstra claramente como estruturar uma aplicação completa, servindo
como ponto de partida para projetos mais elaborados.

342
Este exemplo mostra como os principais elementos do Tkinter podem ser
combinados numa aplicação funcional. A área de texto, acompanhada por
uma scrollbar, permite editar conteúdo de forma cómoda, enquanto o menu
“Ficheiro” reúne as opções essenciais de abertura e gravação de
documentos. As funções associadas a cada ação demonstram a leitura e
escrita de ficheiros, além do uso das caixas de diálogo nativas para selecionar
o destino do conteúdo. Apesar de simples, o exemplo ilustra claramente

343
como estruturar uma aplicação completa e serve como base para versões
mais avançadas, propostas nos exercícios.

12.9 Boas Práticas de Desenvolvimento

O desenvolvimento com Tkinter pode escalar desde pequenos utilitários


até sistemas completos. Para garantir qualidade e manutenibilidade, devem
ser seguidas algumas diretrizes fundamentais:
− Separar lógica e interface → a camada de GUI deve apenas invocar
métodos da lógica central
− Evitar bloqueios → operações demoradas devem ser executadas em
threads ou agendadas com after()
− Utilizar exceções conscientemente → mensagens ao utilizador devem
ser claras, mas o log deve manter detalhes técnicos
− Internacionalização → textos e mensagens devem ser centralizados em
dicionários ou ficheiros de tradução
− Encerramento limpo → liberte recursos, cancele timers e finalize threads
antes de fechar a janela.

12.10 Conclusão

Tkinter permanece uma das bibliotecas mais equilibradas para


desenvolvimento de aplicações gráficas em Python. A sua simplicidade,
aliada à flexibilidade dos widgets ttk e à maturidade da biblioteca, torna-o
uma ferramenta didática e prática desde a prototipagem até aplicações reais.
344
Ao longo deste capítulo, explorou-se desde os fundamentos, como a criação
de janelas, widgets básicos e layouts, até à construção de uma aplicação com
menus, caixas de diálogo e manipulação de ficheiros.
Com esses conceitos dominados, o leitor está preparado para avançar para
tópicos mais complexos, como:
− personalização de estilos e temas com [Link]
− integração com bibliotecas externas (como Pillow e Matplotlib)
− desenho e animação com o widget Canvas
− uso de threads e comunicação assíncrona em aplicações Tkinter.
A partir desta breve apresentação da Tkinter, a criatividade torna-se o
limite, onde cada novo projeto pode ganhar vida através de interfaces
intuitivas, elegantes e totalmente desenvolvidas em Python.

12.11 Exercício Proposto

1) Desenvolver uma versão personalizada do mini editor de texto


apresentado no item 13.8, aplicando de forma integrada os conceitos
estudados sobre o Tkinter. O objetivo principal é criar uma aplicação
gráfica completa, organizada e funcional, capaz de manipular ficheiros
de texto e oferecer um conjunto mínimo de operações de edição.
A aplicação deve incluir:
a) Janela principal - Uma janela de tamanho adequado, com título e
possibilidade de redimensionamento.

345
b) Área de edição - Um widget Text com suporte a scrollbars vertical e
horizontal, configurado para permitir edição fluida de texto, incluindo as
funcionalidades de undo e redo
c) Menus - Uma barra de menus contendo, no mínimo, os seguintes grupos:
− Ficheiro: opções de Novo, Abrir, Guardar, Guardar Como e Sair
− Editar: funcionalidades de Cortar, Copiar, Colar, Desfazer e
Refazer.
d) Gestão de ficheiros - Implementação da abertura e gravação de ficheiros
através do filedialog, garantindo tratamento apropriado de
exceções e preservação do caminho do ficheiro atual.
e) Atalhos de teclado - As ações mais importantes devem ser acessíveis por
atalhos (por exemplo, Ctrl+N, Ctrl+O, Ctrl+S, Ctrl+Z,
Ctrl+Y, entre outros).
f) Barra de estado - Um rótulo na parte inferior da janela que apresente
mensagens contextuais ao utilizador, tais como operações concluídas,
ficheiros carregados, ou erros encontrados.
g) Boas práticas de organização - O programa deve apresentar uma
estrutura clara, com funções bem definidas para cada tarefa, facilitando
a leitura e manutenção do código.
Acrescente funcionalidades adicionais, caso deseje, como contagem de
palavras, destaque de linha atual, alteração do tamanho do tipo de letra, ou
confirmação de saída quando existir texto não guardado. O importante é
demonstrar domínio dos conceitos fundamentais do Tkinter, bem como
capacidade de estruturar uma aplicação completa e coerente.

346
13 DO DATA MINING AO DASHBOARD

Este capítulo acompanha, passo a passo, o ciclo completo de trabalho com


dados: desde a recolha inicial até a construção de um painel interativo
(dashboard) pronto a ser utilizado por um decisor de negócio. A ideia central
é mostrar que a programação em Python não serve apenas para executar
cálculos isolados, mas sim para construir uma linha de produção de
informação: importar dados, compreender a sua estrutura, limpar problemas,
analisar relações, visualizar padrões e finalmente comunicar os resultados de
forma clara e acessível.
Para tornar esta jornada concreta, assume-se um cenário comum no contexto
empresarial: existe um conjunto de vendas registadas num ficheiro e se quer
responder a perguntas práticas, como qual o centro comercial com maior
faturação, que categoria de produtos gera mais lucro ou que tipo de cliente
gasta mais. Ao longo das próximas secções serão introduzidas classes Python
para organizar o projeto, como DataLoader, Eda e DataVisualization,
técnicas de análise exploratória de dados, regras de limpeza e, por fim, a
criação de um dashboard interativo com Streamlit que permitirá
consultar métricas e gráficos em tempo real.
Antes de aprofundar cada etapa, vale a pena compreender a lógica que
sustenta todo este processo.
Transformar dados brutos em respostas úteis segue sempre uma sequência
lógica. Primeiro obtêm‑se os dados (por exemplo, lendo um ficheiro .csv
ou chamando uma API externa). Depois realiza-se uma Análise Exploratória

347
de Dados (EDA), examinando estrutura, valores, padrões e possíveis
problemas do conjunto de dados. A seguir corrigem‑se erros e incoerências,
como valores em falta, duplicados, categorias escritas de forma inconsistente
ou tipos incorretos. Só então é sensato calcular indicadores, métricas e
estatísticas de negócio. Finalmente, apresenta‑se tudo de forma visual e
interativa num dashboard para apoio à decisão.
Esta abordagem passo a passo não é apenas uma opção, mas uma forma de
garantir qualidade. Sem compreender o estado real dos dados não é possível
confiar nas conclusões. Sem limpar adequadamente os dados, as métricas
podem ficar enviesadas (distorcidas) por erros simples. Sem visualização,
padrões importantes passam despercebidos. E sem comunicar resultados de
forma clara, mesmo a melhor análise perde valor. Ao final deste capítulo
espera-se que sejas capaz de percorrer todo este processo com autonomia e
rigor técnico.

13.1 Importação de dados

Antes de se pensar em gráficos elegantes é preciso trazer os dados para


dentro do programa. Em Python, o processo de importação pode ter várias
origens: ficheiros locais (como .csv), bases de dados internas, APIs
fornecidas por serviços externos e até páginas web. Em todos os casos, o
objetivo é o mesmo: transformar a fonte de dados num objeto estruturado
que permite manipular com segurança e consistência. Em projetos de análise,
esse objeto é quase sempre um DataFrame, da biblioteca pandas, pois o
DataFrame representa uma tabela com linhas e colunas, ideal para
operações como filtragem, agregação e cálculo estatístico.

348
Uma boa prática recorrente é encapsular a lógica de importação numa classe.
Por exemplo, uma classe DataLoader pode receber o caminho do ficheiro
e expor um método que devolve o DataFrame carregado.
O mesmo princípio aplica‑se para dados vindos de uma API: cria‑se uma
classe que sabe construir o pedido, autenticar‑se, receber a resposta em
formato JSON e convertê‑la para DataFrame. Esta organização dá‑nos
reutilização e clareza. Ao longo das secções seguintes apresenta-se ambos os
casos.

13.1.1 Padrão .csv

O formato .csv (Comma-Separated Values) é o mais comum para partilha


de dados tabulares. Cada linha representa um registo e cada coluna
representa um atributo. Em Python, a biblioteca pandas disponibiliza a
função read_csv(), que lê o ficheiro e cria automaticamente um
DataFrame. Depois de carregado, pode-se inspecionar dimensões, tipos de
dados, colunas numéricas e colunas categóricas. É também nesta fase é
confirmados se as datas foram reconhecidas como datas ou se ficaram
guardadas como texto, e se existem valores em branco onde deveria existir
um número.
Um padrão típico consiste em criar uma classe simples responsável por
carregar o ficheiro. O objetivo é separar claramente o ato de obter dados do
ato de analisar dados.
Um esboço possível está ilustrado a seguir. A variável data_frame passa
a ser utilizada nas fases seguintes do projeto, nomeadamente pela fase de
análise exploratória e pela fase de visualização.

349
Neste exemplo define-se uma classe chamada DataLoader que recebe o
caminho de um ficheiro CSV quando é criada, guarda esse caminho
internamente e disponibiliza um método carregar_csv() que usa
pandas para ler o ficheiro (pd.read_csv), devolver os dados sob a
forma de um DataFrame e armazenar esse DataFrame no próprio objeto
para reutilização.

Para criar um objeto desta classe:

Neste exemplo, o ficheiro "[Link]" é carregado para um


DataFrame através do objeto DataLoader e, de seguida, são mostradas
as primeiras linhas desse DataFrame com [Link]().

13.1.2 APIs: Conceito e Acesso

Uma API (Application Programming Interface) é uma forma normalizada de


dois sistemas comunicarem entre si. Em vez de descarregar ficheiros
manualmente, pode-se perguntar diretamente a um serviço remoto por
350
informação já estruturada e atualizada. Muitos serviços modernos, como por
exemplo, bases de filmes ou catálogos de produtos, exigem a criação de uma
conta e fornecem uma chave de acesso (API key). Esta chave é enviada em
cada pedido para provar que há autorização para consultar os dados. O
resultado habitual de uma chamada a uma API é um bloco de texto em
formato JSON, que possui uma estrutura parecida a dicionários Python, que
depois pode ser convertido em DataFrame.
Um cenário comum é pedir a uma API uma lista dos itens mais populares de
um determinado tipo, como por exemplo filmes mais vistos no momento. O
processo segue sempre a mesma lógica: definir a URL base do serviço,
construir a rota específica (endpoint) correspondente à informação desejada,
preparar os parâmetros do pedido (incluindo a chave de acesso e a língua dos
resultados), enviar o pedido HTTP e capturar a resposta. Esta resposta é, em
seguida, transformada num DataFrame do pandas para análise posterior.
Para manter o código organizado, pode-se escrever uma classe dedicada a
recolher dados de um serviço remoto. Essa classe armazena a chave da API,
conhece a rota base e disponibiliza métodos temáticos, por exemplo
importar_filmes_populares(). Cada método prepara os
parâmetros necessários, envia o pedido via biblioteca requests e converte
o resultado para DataFrame. Caso a resposta venha vazia ou com erro, o
método devolve um DataFrame vazio em vez de provocar a interrupção
total do programa. Esta abordagem torna mais simples acrescentar,
futuramente, outros métodos para diferentes tipos de consulta.
Neste exemplo a seguir, assume-se que está a consultar a API pública do The
Movie Database (TMDb), disponível em [Link] , para

351
obter a lista de filmes populares e convertê-la diretamente para um
DataFrame do pandas.

352
Neste exemplo criou-se um objeto DataLoaderAPI utilizando
“CHAVE_API” (que deve ser substituída pela sua própria chave gerada após
criar conta e pedir uma API key), pediu-se à API a lista de filmes populares
em inglês (lingua="en-US"), extraiu-se o título (title) e o ano
(release_date para construir um DataFrame apenas com "Title" e
"Year". Por fim, mostra-se as primeiras linhas com head().
Um exemplo de saída está apresentado a seguir:

Note que esta classe pode facilmente ganhar novas funcionalidades, como
um método que filtra filmes por género, por ano de lançamento ou por
avaliação média. A ideia é que o programador não precise reescrever o
acesso à API sempre que surja uma nova pergunta. Basta criar um novo
método dentro desta classe e reutilizar a mesma infraestrutura de
autenticação e conversão em DataFrame.
Há situações em que a informação necessária ainda não está disponível em
formato de ficheiro ou API. Nesses casos, recorre‑se frequentemente a
técnicas de extração automática de páginas web, conhecidas como web
scraping. A ideia é descarregar o conteúdo HTML de uma página e procurar
dentro desse conteúdo os elementos que interessam (tabelas, títulos, preços
etc.).

353
Em Python, bibliotecas como requests (para obter o HTML) e
BeautifulSoup ou Scrapy (para analisar e extrair partes específicas)
são tipicamente utilizadas.
Apesar de muito útil, o scraping exige responsabilidade legal e ética. É
essencial confirmar se o sítio autoriza a recolha automática de dados e
respeitar limites de acesso. Além disso, os dados extraídos devem ser
tratados com o mesmo cuidado aplicado aos restantes métodos de
importação: precisam de ser limpos, interpretados e convertidos para uma
estrutura tabular consistente, de preferência um DataFrame do pandas.
Neste capítulo, o foco recai apenas sobre fontes que disponibilizam dados de
forma explícita (ficheiros, APIs), mas é importante saber que o scraping
representa outra porta de entrada muito comum no trabalho com dados.

13.1.3 Organização do Projeto em Módulos

À medida que o projeto cresce, separar responsabilidades torna‑se essencial.


Uma prática muito útil é dividir o código em módulos (ficheiros .py)
com papéis bem definidos. Por exemplo: DataLoader trata apenas de
trazer os dados, Eda analisa a qualidade e a estrutura,
DataVisualization cria métricas e gráficos, enquanto um ficheiro
principal atua como orquestrador, chamando métodos das classes e
mostrando os resultados ao utilizador. Desta forma, cada parte do sistema
pode evoluir de forma independente sem comprometer o funcionamento das
restantes. Esta separação clara é também crucial quando se passa de

354
experiências isoladas para aplicações que precisam de ser mantidas ao longo
do tempo.
Outro benefício desta organização modular é a reutilização. Caso se pretenda
analisar um dataset diferente (por exemplo, outro conjunto de vendas ou
dados de clientes), grande parte do código de Eda e
DataVisualization permanece válida e funcional. Basta fornecer um
novo DataFrame carregado por DataLoader e executar novamente os
métodos. Esta forma de estruturar o projeto reduz esforço repetitivo e
incentiva boas práticas de engenharia de software.

13.2 EDA: Análise Exploratória de Dados

A Análise Exploratória de Dados (EDA, do inglês Exploratory Data


Analysis) reúne um conjunto de técnicas iniciais utilizadas para inspecionar
e compreender os dados antes de avançar para modelos complexos ou
conclusões de negócio.
Em termos práticos, a EDA costuma incluir: analisar dimensões do dataset,
verificar tipos de cada coluna, procurar valores em falta, procurar linhas
duplicadas, calcular estatísticas descritivas básicas, localizar valores
extremos (outliers) e estudar a relação entre variáveis.
Uma forma de manter este processo organizado é criar uma classe Eda
dedicada apenas a estas tarefas. Em vez de espalhar chamadas aleatórias a
métodos pandas por todo o código, empacota‑se esse conhecimento em
métodos bem nomeados, como analisar_estrutura(),
identificar_correlacao() e assim por diante. Ao fazer isto,

355
garante-se duas coisas: primeiro, a análise exploratória fica repetível,
segundo, que o relatório inicial de qualidade dos dados pode ser mostrado
rapidamente a outras pessoas da equipa sem exigir que elas leiam todo o
código.

13.2.1 Análise do conjunto de dados

O primeiro passo da EDA é puramente descritivo, pois é necessário saber


com o que se está a lidar. Algumas perguntas essenciais são: Quantas linhas
e colunas existem? Quais são os nomes das colunas? Que tipo de dado
(numérico, texto, data, categórico) cada coluna representa? Existem valores
ausentes (nulos) em alguma coluna? Existem linhas duplicadas? Estas
perguntas parecem simples, mas ignorá‑las leva facilmente a erros graves na
etapa de análise, porque pode-se estar a fazer cálculos sobre dados
incompletos ou inválidos.
Em pandas, métodos como .shape, .dtypes, .isna().sum() e
.duplicated().sum() são aliados imediatos. Eles revelam
dimensões, tipos de dados, número de valores ausentes por coluna e quantas
linhas repetidas existem. É comum capturar estes resultados e exibi‑los num
relatório inicial, de forma amigável. Algumas implementações chegam a
formatar este relatório com um aspeto mais limpo para apresentar ao
utilizador final.
Vale ressaltar que a biblioteca pandas é realmente uma ferramenta
fundamental para análise de dados em Python. O objetivo principal desta
biblioteca é fornecer estruturas de dados tabulares fáceis de manipular — em

356
especial o DataFrame, que representa uma tabela com linhas e colunas
(semelhante a uma folha de cálculo), mas com capacidade de filtragem,
agregação, ordenação, limpeza e cálculo estatístico de forma programática e
repetível.
A Tabela 28 resume os principais métodos e atributos da biblioteca pandas.

Tabela 28 – Métodos e atributos da biblioteca pandas

Método / Atributo O que faz

[Link]() Mostra as primeiras linhas do


DataFrame (por omissão, 5)

[Link]() Mostra as últimas linhas do


DataFrame
Devolve um tuplo (n_linhas,
[Link] n_colunas) com o tamanho do
DataFrame

[Link] Devolve a lista com os nomes de todas


as colunas
Mostra o tipo de dados de cada coluna
[Link] (por exemplo int64, float64,
object, datetime64)

[Link]().sum() Conta quantos valores em falta (nulos)


existem em cada coluna

[Link]().sum() Indica quantas linhas aparecem


marcadas como duplicadas
Calcula estatísticas descritivas básicas
[Link]() das colunas numéricas (média, min,
max, desvio padrão…)
Conta a frequência de cada categoria.
df.value_counts()
Exemplo:
(numa série)
df["Loja"].value_counts().

[Link](numeric_only=True) Calcula a correlação entre colunas


numéricas

357
Método / Atributo O que faz
Mostra um resumo técnico: tipos de
[Link]() coluna, nº de valores não nulos e uso
de memória

df.sort_values("coluna") Ordena as linhas do DataFrame com


base na coluna indicada
df.drop_duplicates() Remove linhas duplicadas
Converte uma coluna de texto em
pd.to_datetime(df["coluna"])
datas reais (datetime)

[Link]() Mostra as primeiras linhas do


DataFrame (por omissão, 5)

[Link]() Mostra as últimas linhas do


DataFrame
Devolve um tuplo (n_linhas,
[Link] n_colunas) com o tamanho do
DataFrame

[Link] Devolve a lista com os nomes de todas


as colunas
Mostra o tipo de dados de cada coluna
[Link] (por exemplo int64, float64,
object, datetime64).

[Link]().sum() Conta quantos valores em falta (nulos)


existem em cada coluna

[Link]().sum() Indica quantas linhas aparecem


marcadas como duplicadas
Calcula estatísticas descritivas básicas
[Link]() das colunas numéricas (média, min,
max, desvio padrão…)
Conta a frequência de cada categoria.
df.value_counts() Exemplo:
df["Loja"].value_counts().

[Link](numeric_only=True) Calcula a correlação entre colunas


numéricas

358
Método / Atributo O que faz
Mostra um resumo técnico: tipos de
[Link]() coluna, nº de valores não nulos e uso
de memória

df.sort_values("coluna") Ordena as linhas do DataFrame com


base na coluna indicada
df.drop_duplicates() Remove linhas duplicadas
Converte uma coluna de texto em
pd.to_datetime(df["coluna"])
datas reais (datetime)

Segue um exemplo de aplicação prática de alguns dos métodos apresentados


anteriormente em pandas, reunidos numa classe Eda. Esta classe
centraliza a extração de informações descritivas do dataset (dimensões,
colunas, valores ausentes, duplicados, tipos de dados e estatísticas
descritivas) e devolve um relatório inicial de EDA pronto a ser consultado.

359
Neste exemplo definiu-se uma classe chamada Eda, cujo objetivo é gerar
um relatório inicial de Análise Exploratória de Dados (EDA) a partir de um
DataFrame do pandas.
O construtor __init__ recebe o DataFrame original (data_frame) e
guarda-o internamente em self.__df. O método
analisar_estrutura() recolhe um conjunto de indicadores
descritivos sobre esse DataFrame e devolve tudo num dicionário chamado
relatorio.
Esse dicionário inclui: as dimensões do dataset (linhas e colunas:
self.__df.shape), as primeiras linhas (self.__df.head()), a lista
dos nomes das colunas (list(self.__df.columns)), o número de
valores ausentes por coluna (self.__df.isnull().sum()), o tipo de
dados de cada coluna (self.__df.dtypes), o número de registos
duplicados (self.__df.duplicated().sum(), convertido em
inteiro) e as estatísticas descritivas das colunas numéricas
(self.__df.describe()).
Métricas como média, mediana, mínimo, máximo e desvio padrão ajudam a
perceber o comportamento típico e a variabilidade dos dados.
O próprio pandas oferece muitas dessas medidas automaticamente através
do método .describe(). Ao observar estes valores pode-se rapidamente
detetar situações anómalas, tais como números impossíveis (por exemplo,
preços negativos) ou amplitudes excessivamente grandes que sugerem erros
de registo.

360
Estas estatísticas são também cruciais para preparar a fase de visualização.
Se uma variável tem valores muito concentrados num intervalo pequeno e
alguns valores extraordinariamente altos, talvez seja necessário utilizar
escalas logarítmicas ou destacar explicitamente os casos extremos. Se duas
colunas numéricas têm médias e desvios parecidos, pode haver uma relação
interessante para explorar. O segredo está em usar estas medidas como um
primeiro radar que orienta as perguntas seguintes.
No final, analisar_estrutura() devolve este dicionário
relatorio, que pode depois ser usado para gerar um relatório formatado
ou para alimentar outras partes da aplicação. Para utilizar a classe Eda, é
preciso primeiro importar os dados. Neste exemplo, isso é feito através de
um objeto da classe DataLoader, definida anteriormente.

Após a criação de um objeto da classe DataLoader foi instanciado um


objeto da classe Eda:

A seguir, percorre-se o dicionário relatório, devolvido pela classe Eda,


e imprime organizadamente e legível no ecrã, um resumo completo do
dataset: número de linhas e colunas, primeiras linhas, nomes das colunas,
tipo de dados de cada coluna, valores ausentes, número de registos
duplicados e as estatísticas descritivas, como a seguir.

361
A seguir é apresentado o relatório gerado a partir do dicionário devolvido pela classe
Eda, utilizando um ficheiro CSV ([Link]) previamente carregado através do
DataLoader. Este ficheiro encontra-se na mesma pasta dos restantes ficheiros do
projeto, mas poderia ser substituído por qualquer outro CSV com a mesma lógica.

362
13.2.2 Deteção de Outliers

Outliers são valores que se afastam fortemente do padrão geral dos dados.
Entretanto, outliers podem ser registos legítimos, como por exemplo, uma
venda excecionalmente alta em época festiva, ou podem resultar de erros de
introdução, como por exemplo, escrever 10000 em vez de 100.
Identificar outliers é importante pois eles podem distorcer médias, gerar
gráficos enganosos e levar a conclusões incorretas.

363
Na prática, a identificação pode ser feita com boxplots, análise de quartis ou
regras como o IQR (Interquartile Range).
Encontrar um outlier não significa eliminá‑lo automaticamente. É preciso
interpretar o contexto de negócio, pois um valor extremo pode representar
uma venda VIP que interessa manter. Por outro lado, pode ser apenas ruído
que deve ser corrigido ou removido para não contaminar as métricas. O mais
importante é registar claramente as decisões tomadas. A limpeza de dados
não é só técnica, é também documental, pois afeta diretamente a confiança
que os outros terão nos resultados apresentados.

No exemplo a seguir, a classe Eda foi ampliada com o método


detetar_outliers, usado para identificar valores atípicos numa
coluna numérica. O método recebe o nome da coluna e aplica a regra do
intervalo interquartil (IQR): calcula o primeiro quartil (Q1), o terceiro quartil
(Q3) e o intervalo interquartil (IQR = Q3 − Q1).
A partir desses valores, define limites inferior (Q1 − 1.5 × IQR) e superior
(Q3 + 1.5 × IQR) considerados normais. Os registos fora desses limites são
classificados como outliers.
O método devolve um dicionário contendo os limites calculados, o total de
outliers encontrados e um pequeno exemplo desses registos. Essa abordagem
simples e estatisticamente sólida é muito útil para detetar valores extremos
antes da análise ou visualização dos dados.

364
365
13.2.3 Correlação entre variáveis

A correlação mede até que ponto duas variáveis variam em conjunto. Se


quando uma aumenta a outra também tende a aumentar, diz-se que existe
correlação positiva. Se quando uma aumenta a outra tende a diminuir, ocorre
a correlação negativa.
Se não há uma relação consistente entre as variáveis, considera-se que a
correlação é fraca ou inexistente.
O coeficiente de correlação varia tipicamente entre -1 e 1: valores próximos
de 1 indicam associação positiva forte; próximos de -1 indicam associação
negativa forte; valores próximos de 0 sugerem ausência de relação linear.
Existem diferentes tipos de correlação, e escolher o método correto é
importante.
− A Correlação de Pearson mede relações lineares entre variáveis
numéricas e funciona melhor quando não há valores extremos (outliers).
− A Correlação de Spearman baseia‑se na ordem (ranking) dos valores e
é mais robusta perante valores extremos ou relações não lineares, desde
que monotónicas.
− A Correlação de Kendall é frequentemente usada em conjuntos
pequenos e também trabalha com rankings, sendo mais precisa quando
se pretende comparar posições relativas, como por exemplo, “qual filme
ficou melhor classificado numa época em relação a outra”.

Na análise de correlação entre variáveis, a classe Eda foi ampliada com o


método identificar_correlacao, que calcula a matriz de correlação
entre todas as colunas numéricas do DataFrame usando o método de
366
Pearson. Nesta implementação optou-se por Pearson porque mede a força
da relação linear entre variáveis numéricas contínuas e é a abordagem mais
comum em análises iniciais.
No entanto, também é possível usar outros métodos, como Spearman,
baseado em rankings, mais robusto a outliers e relações não lineares, mas
monotônicas, e o método Kendall, que é adequado para amostras menores e
comparação de ordem relativa entre observações.
A correlação é obtida aplicando ao DataFrame o comando:
self.__df.corr(method="pearson",numeric_only=True)
que devolve diretamente uma matriz com os coeficientes calculados.

367
13.3 Limpeza de Dados

A limpeza de dados é a etapa que prepara o dataset para análise fiável e


consistente. Um dos primeiros desafios é tratar valores ausentes (ou missing
values ou nulos). Há várias abordagens possíveis. A mais direta é remover
registos incompletos, mas isso nem sempre é aceitável, sobretudo se os dados
forem escassos ou se a ausência de valores não for aleatória. Outra estratégia
comum é preencher os valores em falta com alguma estimativa, por exemplo
a média, a mediana ou a moda da coluna, consoante o tipo de dado.
A decisão de remoção ou preenchimento deve considerar o impacto no
significado dos dados. Por exemplo, substituir um valor de faturação em falta
pela média de faturação pode ser aceitável num relatório macro, mas pode
ser perigoso se estiver a analisar clientes individualmente.
Independentemente da abordagem escolhida, deve‑se registar claramente
como os valores ausentes foram tratados, para que qualquer leitor posterior
compreenda a origem dos números apresentados.

368
É também essencial a limpeza é tratar registos duplicados. Linhas repetidas
podem inflacionar totais e enviesar estatísticas, especialmente em métricas
de volume de vendas ou número de pedidos. No pandas, métodos como
.duplicated() e .drop_duplicates() permitem identificar e
remover linhas iguais. Antes de apagar, confirme se a duplicação é realmente
erro e não apenas o reflexo de duas transações legítimas muito semelhantes.
Também é comum encontrar inconsistências de escrita e de formatação. Um
mesmo centro comercial pode aparecer registado como "Shopping Norte" e
"shopping norte"; um método de pagamento pode surgir como "Cartão" num
registo e "cartao" noutro. Estas diferenças impedem que o agrupamento dos
dados funcione corretamente, porque o computador entende cada variação
como uma categoria diferente. Parte do processo de limpeza consiste em
normalizar estas categorias, padronizando maiúsculas/minúsculas,
corrigindo erros ortográficos óbvios e unificando abreviações.
Para além de valores ausentes e duplicados, é crucial garantir que cada
coluna tem o tipo correto. Datas devem ser interpretadas como datas,
números como números, e assim sucessivamente. Uma coluna que
aparentemente contém preços pode ter sido lida como texto devido ao uso de
vírgulas ou símbolos monetários. Converter explicitamente o tipo (por
exemplo usando pandas.to_datetime para datas ou
pandas.to_numeric para valores numéricos) evita surpresas ao calcular
estatísticas ou ao gerar gráficos cronológicos.
Em certos casos, especialmente quando se pretende treinar modelos
estatísticos ou comparar variáveis em escalas diferentes, aplica‑se
normalização ou padronização. Normalizar significa redimensionar os

369
valores para uma faixa comum (por exemplo, entre 0 e 1); padronizar
significa ajustar uma variável para ter média 0 e desvio padrão 1. Estas
transformações reduzem o impacto de variáveis que, apenas por terem
unidades grandes, dominariam visualizações ou cálculos automáticos.
Após limpar os dados, começa a fase de fazer perguntas úteis. Em contexto
de vendas ao público, algumas perguntas clássicas são: Qual o centro
comercial com maior faturação total? Que categoria de produto gera mais
receita? Clientes do género masculino ou feminino gastam mais por compra?
Estas perguntas transformam o DataFrame num relatório de gestão.
Note que cada pergunta corresponde, a uma agregação: agrupar por loja e
somar total de vendas, agrupar por categoria e contar número de itens
vendidos, agrupar por género do cliente e calcular o ticket médio. É
exatamente este tipo de raciocínio que um dashboard precisa apresentar de
forma imediata, sem obrigar o utilizador final a escrever código. Ao desenhar
o painel deve-se ter a certeza de que estas perguntas aparecem respondidas
logo nos primeiros elementos visuais.
No final desta fase, a limpeza dos dados pode (e deve) ser formalizada numa
classe própria, por exemplo DataCleaner, com todas as operações de
preparação do dataset antes da análise. A ideia é separar
responsabilidades: enquanto a classe DataLoader importa os dados e a
classe Eda descreve e avalia a qualidade desses dados (dimensão, nulos,
duplicados, outliers, correlação), a classe DataCleaner é a responsável
por aplicar as correções necessárias de forma consistente e repetível.

370
371
13.4 Visualização de Dados e Tomada de Decisão

Visualizar dados não é só decorar relatórios com gráficos bonitos. É uma


etapa estratégica onde padrões, tendências e anomalias saltam aos olhos e se
tornam argumentos para decisão. Por exemplo, um gráfico de barras
comparando faturação por centro comercial deixa imediatamente claro qual
deles está a gerar mais receita. Um gráfico de linhas com a evolução temporal
das vendas mostra sazonalidade e permite antecipar períodos de pico. Estes
elementos visuais ajudam a otimizar processos, detetar oportunidades de
mercado e melhorar o desempenho global da organização.
Outra vantagem da visualização é a comunicação entre equipas. Enquanto
tabelas extensas podem ser intimidantes para quem não tem formação
técnica, gráficos bem escolhidos contam uma história quase imediata. Um
bom dashboard aproxima áreas distintas tais como gestão, marketing,
operações, análise porque fornece um ponto comum de leitura e discussão.
Para apoiar a criação de gráficos e indicadores reutilizáveis, é frequente
definir uma classe DataVisualization. Esta classe recebe um
DataFrame (normalmente já limpo) e disponibiliza métodos que calculam
métricas de negócio e que constroem visualizações específicas. A mesma
classe pode ainda manter uma versão filtrada dos dados para refletir escolhas
feitas pelo utilizador, como intervalo temporal, centro comercial selecionado
ou método de pagamento preferido.
Para transformar a análise de dados em algo interativo e útil para decisão, o
projeto recorre ao Streamlit. O Streamlit é uma biblioteca Python
que permite construir dashboards rapidamente, sem precisar escrever
HTML/CSS/JS. Neste capítulo, ele é usado para: (i) criar a interface visual

372
(títulos, secções, layout em colunas), (ii) disponibilizar controlos de
filtragem ao utilizador (listas, sliders etc.), e (iii) apresentar resultados de
negócio de forma clara (métricas, tabelas e gráficos). A Tabela 29 resume os
métodos mais comuns do Streamlit utilizados neste tipo de dashboard e
o papel de cada um.

Tabela 29 – Métodos da biblioteca Streamlit

Método Streamlit Função principal

st.set_page_config() Define opções globais da página (título do


separador, layout largo, ícone).
Cria títulos e subtítulos visuais para organizar
[Link]() / [Link]()
a aplicação.

[Link] Acede à barra lateral, usada para colocar


filtros e controlos de interação.
Dropdown para o utilizador escolher uma
[Link]() opção (ex.: centro comercial, método de
pagamento).

[Link]() Slider para selecionar intervalos numéricos ou


temporais (datas, faixas de valores, etc.).

[Link]() Mostra indicadores de negócio (KPI) com


valor atual e variação.

[Link]() Apresenta um DataFrame interativo (pode


ordenar, fazer scroll).

st.bar_chart() Cria rapidamente um gráfico de barras a partir


de dados agregados.

st.line_chart() Cria um gráfico de linhas (tendência temporal,


evolução diária/mensal).
Mostra figuras criadas com
[Link]() Matplotlib/Seaborn para visualizações
personalizadas.

[Link]() Função genérica: escreve texto, números,


DataFrames, dicionários, etc.

373
Método Streamlit Função principal

[Link]() Cria layout em várias colunas para colocar


KPIs e gráficos lado a lado.
st.plotly_chart() (se Mostra gráficos Plotly interativos (zoom,
usado) hover), úteis para explorar dados ao detalhe.

st.set_page_config() Define opções globais da página (título do


separador, layout largo, ícone).
Cria títulos e subtítulos visuais para organizar
[Link]() / [Link]()
a aplicação.

Segue um exemplo prático de aplicação direta destes conceitos, onde é


construída uma classe DataVisualization responsável por preparar os
dados para análise visual, como por exemplo, calcular a receita a partir de
quantidade e preço, agrupar valores por categoria de produto, método de
pagamento ou género do cliente, e por disponibilizar resultados já prontos
para serem mostrados em gráficos. A partir desta classe, é criado um pequeno
dashboard interativo em Python que permite filtrar os dados e observar
imediatamente o impacto desses filtros nas distribuições de receita e no
comportamento de compra. Este dashboard é implementado com
Streamlit e foi organizado de forma a correr num único ficheiro: basta
guardar o código e executar esse ficheiro no IDLE. O próprio script trata de
lançar o Streamlit e abrir a interface no navegador, sem ser necessário
escrever comandos de terminal manualmente. Desta forma, a visualização
deixa de ser apenas teórica e passa a ser demonstrada como uma ferramenta
prática de apoio à tomada de decisão baseada em dados. No código a seguir,
é exemplificado a classe DataVisualization aplicado a um ficheiro de
vendas ([Link]).

374
375
376
Este exemplo lê o ficheiro [Link], calcula métricas básicas de
negócio (receita total, número de transações e ticket médio) e mostra essas
métricas juntamente com um gráfico de receita por categoria num pequeno
dashboard feito em Streamlit, que pode ser lançado diretamente em
Python sem usar a linha de comandos, conforme este exemplo de saída:

377
13.5 Exercícios Propostos

3) Implementar um detetor de phishing em emails. O programa deve


analisar ficheiros de emails (em formato .txt ou .csv) com o objetivo
de identificar potenciais tentativas de phishing. O sistema deve
reconhecer padrões suspeitos, como URLs maliciosas ou pedidos de
dados pessoais, e gerar um relatório estruturado com a classificação de
risco de cada mensagem. O programa deve:
− Ler ficheiros contendo emails suspeitos
− Identificar indicadores comuns de phishing
− Produzir relatório em formato legível com classificações de risco
− Guardar os resultados finais em JSON

4) Criar uma aplicação que analise ficheiros de configuração (.conf, .xml,


.json) em busca de vulnerabilidades frequentes, como palavras-passe
guardadas em claro ou permissões demasiado permissivas. O sistema

378
deve comparar os resultados com uma base de dados de vulnerabilidades
conhecidas e exportar um relatório detalhado em formato HTML. O
programa deve:
− Ler ficheiros de configuração em diferentes formatos
− Detetar padrões de risco (passwords, permissões fracas)
− Validar com base de vulnerabilidades predefinida
− Exportar relatório estruturado em HTML

379
14 APLICAÇÕES DE PYTHON EM CIBERSEGURANÇA

• 14.1 Introdução
A cibersegurança tornou-se um dos pilares essenciais da computação
moderna. Com o crescimento exponencial das redes, da Internet das Coisas
e dos serviços digitais, proteger a informação é tão importante quanto
produzi-la. A linguagem Python, pela sua clareza, simplicidade e vasto
ecossistema de bibliotecas, é hoje uma das ferramentas mais utilizadas no
desenvolvimento de soluções de segurança, auditoria, deteção e automação.
O Python é usado tanto em equipas de defesa (blue teams), os
responsáveis por monitorar e proteger, como em equipas de teste e auditoria
(red teams), que simulam ataques de forma controlada e ética, com o
objetivo de avaliar a robustez dos sistemas.
Este capítulo apresenta como a linguagem pode ser aplicada a tarefas
fundamentais de cibersegurança, desde a criação de senhas seguras até à
encriptação de dados e à análise de tráfego de rede.
A abordagem será progressiva. Inicia-se com conceitos básicos de segurança
digital e manipulação segura de dados, avançando depois para técnicas de
criptografia, comunicação segura e análise de rede.
Os exemplos apresentados são ilustrações práticas de defesa e
monitorização. Devem ser utilizados exclusivamente em ambientes
controlados e autorizados.

380
14.1 Fundamentos da Segurança da Informação

A segurança da informação assenta tradicionalmente na chamada tríade CIA,


composta por três princípios fundamentais:
− Confidencialidade – garantir que apenas utilizadores autorizados podem
aceder aos dados.
− Integridade – assegurar que a informação não é alterada indevidamente.
− Disponibilidade – manter os sistemas operacionais e acessíveis aos
legítimos utilizadores.
A estes princípios juntam-se frequentemente outros, como autenticidade
(garantir a identidade de quem envia ou recebe informação) e não repúdio
(impedir que uma ação legítima possa ser negada mais tarde).
O Python fornece ferramentas para atuar em todas estas dimensões.
Bibliotecas como hashlib, cryptography, secrets, socket,
scapy, requests, paramiko e nmap permitem, em conjunto,
construir sistemas que asseguram desde a integridade de ficheiros até à
comunicação encriptada e à análise de vulnerabilidades.

14.2 Geração de Senhas e Chaves Seguras

Um dos aspetos mais críticos da cibersegurança é a criação de credenciais


robustas. O módulo secrets, introduzido na biblioteca padrão do Python,
foi concebido precisamente para gerar valores aleatórios criptograficamente
seguros, substituindo a função random, que não é adequada para uso em
segurança.

381
O código a seguir gera uma senha aleatória, combinando letras, números e
símbolos. O módulo secrets garante aleatoriedade imprevisível,
adequada para gerar senhas, chaves de API, tokens de autenticação e outros
segredos digitais.

A saída é aleatória, entretanto, para fins de exemplo, apresenta-se uma senha


aleatória gerada.

14.3 Hashing e Verificação de Integridade

O hashing é um processo matemático que transforma dados de qualquer


tamanho num valor fixo, o hash.
Hashes são amplamente usados para verificar a integridade de ficheiros e
proteger senhas.
O Python oferece o módulo hashlib, com suporte a algoritmos como
SHA-256, SHA-512 e SHA3.
No exemplo seguinte, apresenta-se uma função simples para calcular o hash
SHA-256 de um ficheiro. O objetivo é verificar a sua integridade, isto é,

382
garantir que o conteúdo não foi alterado. O procedimento consiste em ler o
ficheiro em blocos, atualizar progressivamente o algoritmo de hashing e, por
fim, obter o valor hexadecimal correspondente. Este tipo de abordagem é
essencial quando se trabalham ficheiros grandes, uma vez que evita carregar
todo o conteúdo na memória de uma só vez.

Gerando o seguinte hash para o ficheiro utilizado no exemplo.

Ao recalcular o hash de um ficheiro e compará-lo com o valor original, é


possível detetar qualquer alteração, seja intencional ou acidental, no seu
conteúdo.
Este princípio é usado em sistemas de atualização segura, distribuição de
software e verificação de backups.

14.4 Criptografia Simétrica

A criptografia é um dos pilares fundamentais da segurança da informação,


sendo responsável por garantir a confidencialidade dos dados. Na
criptografia simétrica, a mesma chave é utilizada tanto para encriptar como
383
para desencriptar a informação, o que torna este método eficiente e adequado
a cenários em que a chave pode ser partilhada de forma segura entre as partes
envolvidas.
A biblioteca externa cryptography disponibiliza a classe Fernet, que
implementa um esquema moderno e seguro baseado em AES e HMAC. A
utilização do Fernet assegura que a mensagem não apenas permanece
confidencial, mas também que qualquer alteração indevida pode ser detetada,
graças ao mecanismo de autenticação integrado.
No exemplo seguinte, apresenta-se um pequeno programa que ilustra o
processo completo, desde gerar uma chave secreta, guardá-la num ficheiro,
encriptar uma mensagem e, posteriormente, recuperar o seu conteúdo
original. Este tipo de abordagem é particularmente útil para proteger
ficheiros locais, configurações sensíveis ou pequenos conjuntos de dados que
não devem ser armazenados em texto simples.

A execução deste exemplo produz um valor cifrado composto por uma


sequência longa de bytes codificada em Base64. À primeira vista, esta

384
sequência parece totalmente aleatória, o que é desejável em criptografia
moderna. Após a desencriptação, obtém-se novamente o texto original,
demonstrando a simetria do processo.
É importante notar que a mensagem cifrada será diferente em cada execução,
mesmo que o texto original seja o mesmo. Isto acontece porque o Fernet
incorpora informação aleatória a cada operação de encriptação, contribuindo
para reforçar a segurança.
Este método é ideal para proteger ficheiros locais, configurações internas ou
pequenas bases de dados. Contudo, exige especial cuidado no
armazenamento da chave, já que qualquer pessoa que obtenha acesso a esse
ficheiro poderá desencriptar a informação protegida. Por isso, é
recomendável guardar a chave em diretórios restritos ou utilizar mecanismos
adicionais de proteção.

14.5 Criptografia Assimétrica e Assinaturas Digitais

Na criptografia assimétrica utilizam-se duas chaves diferentes, mas


matematicamente relacionadas. As chaves são denominadas de chave
pública e chave privada. A chave pública pode ser partilhada livremente e é
utilizada para encriptar a informação, enquanto a chave privada deve ser
mantida em segurança e serve para desencriptar os dados ou gerar assinaturas
digitais. Este modelo permite estabelecer comunicações seguras mesmo em
ambientes não confiáveis e constitui a base tecnológica de sistemas
amplamente utilizados, como o protocolo HTTPS e o correio eletrónico
seguro.

385
No exemplo seguinte apresenta-se o processo fundamental da criptografia
assimétrica através da geração de um par de chaves RSA e da subsequente
encriptação e desencriptação de uma mensagem. A chave privada é criada
localmente e, a partir dela, obtém-se a chave pública correspondente. Utiliza-
se então a chave pública para encriptar a mensagem, garantindo que apenas
o detentor da chave privada poderá recuperá-la. Este procedimento ilustra o
princípio essencial da criptografia de chave pública e mostra, de forma
simples, como o Python permite implementar este mecanismo de segurança
de forma robusta.

A mesma biblioteca permite também criar assinaturas digitais, um


mecanismo que assegura tanto a autenticidade como a integridade de uma

386
mensagem. Ao assinar digitalmente um documento com a chave privada, o
emissor garante que o conteúdo não foi alterado e que a sua origem pode ser
verificada através da chave pública correspondente.
Este princípio é utilizado diariamente em sistemas como certificados digitais,
assinaturas eletrónicas e mecanismos de autenticação em dois fatores,
constituindo uma base essencial da segurança moderna.
O exemplo seguinte mostra como se pode gerar uma assinatura digital e a
verificar utilizando RSA, um algoritmo de criptografia assimétrica
apresentado em 1977. O algoritmo baseia-se na dificuldade computacional
de fatorizar números inteiros muito grandes, construídos a partir do produto
de dois números primos.

387
Neste exemplo, a mensagem é assinada com a chave privada e verificada
com a chave pública, demonstrando o funcionamento básico da assinatura
digital.

14.6 Comunicação Segura com HTTPS e SSH

A segurança em ambiente de rede depende simultaneamente de mecanismos


de criptografia, que protegem a confidencialidade dos dados, e de processos
de autenticação, que garantem a identidade das partes envolvidas na
comunicação. Em Python, é possível estabelecer ligações seguras recorrendo
a bibliotecas amplamente utilizadas, como requests, para comunicações
HTTPS, e paramiko, que implementa o protocolo SSH para administração
e automação remota de sistemas.
Os exemplos seguintes ilustram como estas ferramentas podem ser utilizadas
de forma segura.

a) HTTPS com requests


O protocolo HTTPS combina HTTP com criptografia baseada em TLS,
garantindo que os dados transmitidos entre o cliente e o servidor
permanecem protegidos contra escutas e manipulações. O Python facilita
este processo através da biblioteca externa requests, que realiza
automaticamente a negociação criptográfica e permite verificar a
autenticidade do servidor através do certificado digital.
No exemplo seguinte, é realizado um pedido simples a um serviço web.

388
A opção verify=True garante que o certificado SSL apresentado pelo
servidor é validado antes de a ligação ser mantida. Esta verificação impede
ataques de man-in-the-middle, nos quais um atacante tenta interpor-se na
comunicação para capturar ou alterar dados. Caso o certificado não seja
fiável, o pedido é interrompido, preservando a integridade da comunicação.

b) SSH com paramiko


Ao contrário do HTTPS, que é usado para comunicações web, o SSH é um
protocolo concebido para acesso remoto seguro a servidores e equipamentos
de rede. Além de encriptar toda a sessão, o SSH fornece mecanismos de
autenticação forte, como chaves públicas, protegendo eficazmente contra
interceções e acessos não autorizados.
O exemplo seguinte demonstra como o Python pode utilizar o protocolo SSH
para executar comandos num servidor remoto:

389
O paramiko permite estabelecer uma sessão encriptada, autenticar o
utilizador e executar comandos de forma remota, substituindo com segurança
ferramentas antigas como o Telnet, que transmitiam credenciais e dados em
texto simples. Este tipo de automação é especialmente útil em ambientes de
administração de sistemas, permitindo monitorizar servidores, recolher
informações de diagnóstico ou executar atualizações programadas.

14.7 Análise e Varredura de Rede

A análise de rede é essencial da cibersegurança, permitindo compreender o


comportamento dos sistemas, identificar portas e serviços expostos e detetar
padrões de tráfego que possam indicar vulnerabilidades ou atividades
suspeitas. Profissionalmente, estas técnicas enquadram-se em auditorias de
segurança, testes de intrusão autorizados e monitorização preventiva.
O Python possui muitas bibliotecas, sendo adequado para estas tarefas.
Módulos como socket, scapy e ferramentas integradas com nmap
permitem implementar, com poucas linhas de código, operações típicas de
varrimento de portas, inspeção de pacotes ou análise exploratória de tráfego.
Importa, contudo, sublinhar que todas estas práticas devem ser conduzidas
de forma ética, em ambientes controlados ou com autorização explícita, pois
a utilização indevida de técnicas de varredura pode constituir violação legal.

a) Varredura simples com socket


O módulo socket, incluído na biblioteca padrão do Python, permite criar
ligações TCP e UDP e é suficiente para realizar varrimentos básicos de

390
portas. A técnica consiste em tentar estabelecer ligações sucessivas a várias
portas de um determinado host. Se a ligação for aceite, a porta é considerada
aberta.
O exemplo seguinte apresenta um varrimento simples.

Embora muito simples e rudimentar, este método demonstra o princípio


fundamental de um port scanner, já que identifica serviços visíveis numa
máquina remota. Ferramentas profissionais, como o Nmap, utilizam
estratégias mais eficientes, mas assentam na mesma lógica base de
verificação de portas.

b) Utilização de scapy
Para além da varredura de portas, a análise de pacotes é uma técnica essencial
para compreender o comportamento de uma rede. A biblioteca scapy
permite capturar, gerar e analisar pacotes de baixo nível, tornando-se uma
ferramenta muito poderosa para diagnóstico, deteção de intrusões e
investigação em segurança.
O exemplo seguinte captura um pequeno número de pacotes e apresenta um
resumo de cada um, entretanto, antes do exemplo, ressalta-se que a captura

391
de pacotes com o Scapy depende do suporte do sistema operativo para
acesso de baixo nível à interface de rede. Esta funcionalidade está totalmente
disponível em sistemas Linux e macOS, onde a biblioteca opera sem
necessidade de componentes adicionais, permitindo o uso normal da função
sniff() e de outras operações de análise de pacotes.
No Windows, contudo, esse suporte não é nativo. Para que o Scapy consiga
capturar ou enviar pacotes ao nível da camada de enlace (camada 2), é
necessário instalar o componente Npcap, disponível no site oficial do Nmap.
Sem este componente, o Scapy devolve um erro indicando que não é
possível efetuar a captura a nível 2. Alternativamente, é possível recorrer ao
modo de captura ao nível 3 (camada de rede), que funciona sem
dependências externas, mas oferece capacidades reduzidas e não permite
observar todo o tráfego da interface.
Assim, recomenda-se a utilização de Linux ou macOS quando se pretende
explorar todo o potencial do Scapy, como neste exemplo.

Com apenas estas linhas, é possível observar tráfego real circulando pela
interface de rede. Cada pacote capturado é analisado e resumido, permitindo
ao programador identificar padrões, tipos de protocolo ou comportamentos
inesperados. A partir deste ponto, é possível evoluir para funcionalidades
mais complexas, tais como filtragem seletiva, deteção de pacotes suspeitos
ou análise temporal do tráfego.
392
Este tipo de abordagem constitui a base conceptual dos sistemas de deteção
de intrusões (IDS), que dependem de monitorização contínua e análise
criteriosa do fluxo de dados para identificar atividades anómalas.

14.8 Boas Práticas e Ética Profissional

A programação aplicada à cibersegurança exige rigor técnico e, acima de


tudo, responsabilidade ética. Técnicas como varredura de portas, captura de
pacotes ou análise de tráfego devem ser utilizadas exclusivamente em
ambientes próprios para testes ou mediante autorização explícita dos
responsáveis pela infraestrutura. O uso indevido destas ferramentas pode
configurar crime informático e comprometer seriamente a integridade de
sistemas.
Além da vertente ética, a segurança exige também boas práticas de
desenvolvimento, que devem ser seguidas de forma consistente. Entre as
mais importantes destacam-se:
− armazenar senhas e segredos de forma cifrada
− evitar o registo (log) de informação sensível
− utilizar sempre HTTPS ou outros protocolos encriptados nas
comunicações
− restringir permissões de ficheiros, chaves e diretórios críticos
− validar e limpar todas as entradas de utilizador
− manter bibliotecas e dependências atualizadas.
O programador de segurança é, antes de mais, um defensor da integridade
digital, da privacidade e da resiliência dos sistemas. O uso consciente e ético

393
das ferramentas abordadas neste capítulo é um passo essencial para garantir
que o conhecimento técnico é aplicado de forma responsável.

É percetível que o Python se destaca como uma ferramenta versátil e


acessível no domínio da cibersegurança, combinando uma sintaxe simples
com um ecossistema rico em bibliotecas especializadas.

14.9 Exercícios Propostos

1. Verificação de Integridade: Escreva um programa que leia um conjunto


de ficheiros e gere os respetivos hashes SHA-256. O programa deve
permitir comparar os valores atuais com uma lista de referência e indicar
ficheiros alterados.
2. Encriptação de Ficheiros: Utilize a biblioteca cryptography para
criar um pequeno utilitário que encripte e desencripte ficheiros de texto,
guardando a chave num ficheiro separado.
3. Scanner de Rede: Implemente uma ferramenta que receba um endereço
IP e verifique quais as portas abertas entre 1 e 1024. O programa deve
exibir o tempo total de varredura.
4. Monitorização de Tráfego: Usando o scapy, capture pacotes de rede
durante um intervalo de 10 segundos e apresente quantos pertencem a
protocolos TCP, UDP e ICMP.
5. Ligação SSH Segura: Escreva um script que se ligue, via SSH, a um
servidor remoto e execute um comando de diagnóstico, registando o
resultado num ficheiro local.

394
15 RESPOSTAS

15.1 CAPÍTULO 2

1)
a = 10
b = 3.5

# a) a + b
resultado1 = a + b
print("a) a + b =")
print(resultado1)
print("Tipo:")
print(type(resultado1))

# b) a * b
resultado2 = a * b
print("b) a * b =")
print(resultado2)
print("Tipo:")
print(type(resultado2))

# c) a / b
resultado3 = a / b
print("c) a / b =")
print(resultado3)
print("Tipo:")
print(type(resultado3))

2)
nome = "Paulo"
apelido = "Marques"

# Concatenar com espaço


nome_completo = nome + " " + apelido

395
# Mostrar o resultado
print(nome_completo)

3)

numero_str = "15"

# Converter para inteiro e somar 5


numero_int = int(numero_str) + 5

# Converter o resultado para string


resultado_str = str(numero_int)

# Exibir a string final e o respetivo tipo


print(resultado_str)
print(type(resultado_str))

4)
x = 10
y = 20

print(x > y) # a) x é maior que y


print(x != y) # b) x é diferente de y
print(x == y / 2) # c) x é igual a y / 2

5)

TAXA_JUROS = 0.05
valor = 1000
juro = valor * TAXA_JUROS

print(juro)

396
15.2 CAPÍTULO 3

1)
# Cálculo da expressão
resultado = 10 + 3 * 2 2 / 4 - 1
print(resultado)

# Explicação da ordem das operações:


print("Ordem das operações:")
print("1. Exponenciação: 2 2 = 4")
print("2. Multiplicação: 3 * 4 = 12")
print("3. Divisão: 12 / 4 = 3")
print("4. Adição: 10 + 3 = 13")
print("5. Subtração: 13 - 1 = 12")

2)
# Cálculo da expressão
a = 5
b = 10
c = 15

# a) a < b < c
resultado_a = a < b < c
print("a) a < b < c:")
print(resultado_a)

# b) a + b == c
resultado_b = a + b == c
print("b) a + b == c:")
print(resultado_b)

# c) c % a != 0
resultado_c = c % a != 0
print("c) c % a != 0:")
print(resultado_c)

397
3)
idade = 18
tem_cartao = True

print("a) Idade >= 18 E tem cartão:")


print(idade >= 18 and tem_cartao)

print("b) Idade < 18 OU não tem cartão:")


print(idade < 18 or not tem_cartao)

4)
lista1 = ["cpu", "pc", "ram"]
lista2 = ["cpu", "pc", "ram"]

print("a) lista1 == lista2:")


print(lista1 == lista2)

print("b) lista1 is lista2:")


print(lista1 is lista2)

5)
fruta = "banana"

print('a) "a" está em fruta:')


print("a" in fruta)

print('b) "maçã" não está em fruta:')


print("maçã" not in fruta)

print('c) "nana" é substring de fruta:')


print("nana" in fruta)

398
15.3 CAPÍTULO 4

1)
# Solicitar nome e cidade
nome = input("Qual é o teu nome? ")
cidade = input("De que cidade vens? ")

# Mostrar mensagem personalizada


print(f"Olá {nome}! É um prazer saber que vens de
{cidade}. Bem-vindo/a!")

2)
# Ler horas
horas = float(input("Introduzir o número de horas: "))

# Converter
minutos = horas * 60
segundos = horas * 3600
# Mostrar resultados
print(f"{horas} horas = {minutos:.0f} minutos =
{segundos:.0f} segundos")

3)
# Ler dados do produto
produto = input("Nome do produto: ")
preco_unitario = float(input("Preço unitário (€): "))
quantidade = int(input("Quantidade: "))

# Calcular valores
total_sem_iva = preco_unitario * quantidade
iva = total_sem_iva * 0.23
total_com_iva = total_sem_iva + iva

# Mostrar recibo formatado


print(f"\n--- RECIBO ---")
print(f"Produto: {produto}")
print(f"Total: {total_sem_iva:.2f} €")
print(f"IVA (23%): {iva:.2f} €")
print(f"Total com IVA: {total_com_iva:.2f} €")

399
4)
# Ler número de 6 dígitos
numero = int(input("Digitar um número de 6 dígitos: "))
# Diferentes formatações
print(f"Com separadores: {numero:,}")
print(f"Como moeda: {numero:.2f} €")
print(f"Com zeros: {numero:010d}")
print(f"Em hexadecimal: {numero:x}")

5)
# Solicita as três palavras ao usuário
palavra1 = input("Digite a primeira palavra: ").strip()
palavra2 = input("Digite a segunda palavra: ").strip()
palavra3 = input("Digite a terceira palavra: ").strip()

# Formata e exibe o resultado usando f-string


print(f"\nPalavras formatadas: {palavra1}-{palavra2}-
{palavra3}")

6)
# Ler resultados do teste
corretas = int(input("Número de respostas corretas: "))
total = int(input("Total de perguntas: "))

# Calcular percentagem
percentagem = (corretas / total) * 100

# Mostrar resultado
print(f"Acertaste {corretas} de {total} questões
({percentagem:.1f}%)")

400
15.4 CAPÍTULO 5

1)
numero = int(input("Digite um numero: "))

# Método tradicional
if numero % 2 == 0:
tipo = "par"
else:
tipo = "ímpar"
print(f"O número {numero} é {tipo}")

# Método com operador ternário


tipo = "par" if numero % 2 == 0 else "ímpar"
print(f"O número {numero} é {tipo}")

2)

import random
# Gerar número aleatório entre 1 e 100
numero_secreto = [Link](1, 100)
tentativas = 0

print("�
� Bem-vindo ao Jogo de Adivinhação!")
print("Tente adivinhar o número entre 1 e 100")
print("-" * 40)

# Variável para controlar o jogo


acertou = False

while not acertou:


try:
# Pedir palpite ao utilizador
palpite = int(input("Digite o seu palpite: "))
tentativas += 1
# Verificar o palpite e dar dicas
if palpite < numero_secreto:
print("�
� Dica: Tente um número MAIOR!")
elif palpite > numero_secreto:

401
print("�
� Dica: Tente um número MENOR!")
else:
print(f"�
� PARABÉNS! Acertaste em
{tentativas} tentativas!")
acertou = True # Termina o ciclo

except ValueError:
print("� Erro: Digite um número válido!")

print() # Linha em branco para melhor legibilidade

3)
# Pedir texto ao utilizador
texto = input("Introduza uma palavra ou frase: ")

# Converter para minúsculas para facilitar a comparação


texto_minusculo = [Link]()
# Inicializar contador
contador_vogais = 0

# Percorrer cada caractere do texto


for caractere in texto_minusculo:
# Verificar se caractere é uma vogal (inclui acentos)
if caractere == 'a' or caractere == 'á' or caractere
== 'à' or caractere == 'â' or caractere == 'ã':
contador_vogais += 1
elif caractere == 'e' or caractere == 'é' or
caractere == 'è' or caractere == 'ê':
contador_vogais += 1
elif caractere == 'i' or caractere == 'í' or
caractere == 'ì' or caractere == 'î':
contador_vogais += 1
elif caractere == 'o' or caractere == 'ó' or
caractere == 'ò' or caractere == 'ô' or caractere ==
'õ':
contador_vogais += 1
elif caractere == 'u' or caractere == 'ú' or
caractere == 'ù' or caractere == 'û':
contador_vogais += 1

402
# Mostrar resultado
print(f"O texto tem {contador_vogais} vogais.")

##### Versão mais compacta


# Pedir texto ao utilizador
texto = input("Introduza uma palavra ou frase: ")

# Converter para minúsculas


texto = [Link]()
# Inicializar contador
contador = 0
# Percorrer cada caractere
for letra in texto:
# Verificar se é vogal (usando comparações
múltiplas)
if (letra == 'a' or letra == 'e' or letra == 'i' or
letra == 'o' or letra == 'u' or
letra == 'á' or letra == 'é' or letra == 'í' or
letra == 'ó' or letra == 'ú' or
letra == 'à' or letra == 'è' or letra == 'ì' or
letra == 'ò' or letra == 'ù' or
letra == 'â' or letra == 'ê' or letra == 'î' or
letra == 'ô' or letra == 'û' or
letra == 'ã' or letra == 'õ'):
contador += 1

print(f"Número de vogais encontradas: {contador}")

##### Versão com mais detalhes


print("=== CONTADOR DE VOGAIS ===")
print("Este programa conta quantas vogais existem no
texto que introduzir.")
print("Considera vogais com e sem acentos.")
print("-" * 40)

# Receber input do utilizador


texto_utilizador = input("Por favor, escreva algo: ")

# Processamento
texto_minusculo = texto_utilizador.lower()
total_vogais = 0

403
for caractere in texto_minusculo:
# Verificar cada tipo de vogal separadamente
if caractere in ['a', 'á', 'à', 'â', 'ã']:
total_vogais += 1
elif caractere in ['e', 'é', 'è', 'ê']:
total_vogais += 1
elif caractere in ['i', 'í', 'ì', 'î']:
total_vogais += 1
elif caractere in ['o', 'ó', 'ò', 'ô', 'õ']:
total_vogais += 1
elif caractere in ['u', 'ú', 'ù', 'û']:
total_vogais += 1

print("-" * 40)
print(f"Texto analisado: {texto_utilizador}")
print(f"Total de vogais encontradas: {total_vogais}")

4)
# Versão básica
print("Contagem regressiva para lançamento:")

for minuto in range(2, -1, -1): # Minutos: 2, 1, 0


for segundo in range(59, -1, -1): # Segundos: 59
até 0
# Formatação para ter sempre 2 dígitos
seg_str = f"0{segundo}" if segundo < 10 else
str(segundo)
print(f"T-{minuto}:{seg_str}")

if minuto > 0:
print(f"Faltam {minuto} minutos!")

print("LANÇAMENTO!")

# Versão com pausa


import time
for minuto in range(2, -1, -1):
for segundo in range(59, -1, -1):
seg_str = f"0{segundo}" if segundo < 10 else
str(segundo)

404
print(f"T-{minuto}:{seg_str}")
[Link](1) # Pausa de 1 segundo
if minuto > 0:
print(f"Faltam {minuto} minutos!")
[Link](2) # Pausa extra entre minutos
print("LANÇAMENTO!")

5)

while True:
try:
numerador = int(input("Numerador: "))
denominador = int(input("Denominador: "))
resultado = numerador / denominador
print(f"Resultado: {resultado}")
break # termina o ciclo se tudo correr bem
except ValueError:
print("� Erro: Digite números válidos!")
except ZeroDivisionError:
print("� Erro: Impossível dividir por zero!")

6)

# Perguntar ao usuário o número de séries e repetições


print("�
� PROGRAMA DE SIMULAÇÃO DE TREINO")
print("=" * 40)

try:
series = int(input("Quantas séries quer fazer? "))
repeticoes = int(input("Quantas repetições por
série? "))
# Validar os valores
if series <= 0 or repeticoes <= 0:
print("Por favor, insira valores maiores que
zero!")
else:
print(f"\n�
� INICIAR TREINO: {series} séries de
{repeticoes} repetições!")
print("=" * 50)

405
# Simular cada série
serie = 1
while serie <= series:
print(f"\nSÉRIE {serie}:")
# Simular cada repetição da série atual
repeticao = 1
while repeticao <= repeticoes:
print(f" Repetição
{repeticao}/{repeticoes}")
repeticao += 1

# Mensagem de descanso entre séries


#(exceto após a última série)
if serie < series:
print(f"\n�
� DESCANSO: Descanse 60
segundos antes da próxima série...")
print("-" * 40)

serie += 1
# Mensagem final de incentivo
print("\n" + "=" * 50)
print("�
� PARABÉNS! Completaste o treino!")
print("�
� Continue! Cada treino te torna mais
forte!")
print("�
� És incrível! Até o próximo treino!")

except ValueError:
print("Erro: Inserir apenas números inteiros!")

7)

print("�
� PROGRAMA DE DIVISÃO DE NÚMEROS INTEIROS")
print("=" * 40)

try:
# Ler o primeiro número
num1 = int(input("Digite 1º número (dividendo): "))

# Ler o segundo número


num2 = int(input("Digite 2º número (divisor): "))

406
# Verificar se o divisor é zero
if num2 == 0:
print("� ERRO: Impossível dividir por zero!")
else:
# Realizar a divisão
resultado = num1 / num2
print(f"�
� Resultado da divisão: {num1} ÷
{num2} = {resultado}")

except ValueError:
print("� ERRO: Digite apenas números inteiros!")
except Exception as e:
print(f"� Ocorreu um erro inesperado: {e}")

8)

import random

print("�
� SIMULADOR DE DADOS COM ESTATÍSTICAS")
print("=" * 40)

try:
num_lancamentos = int(input("Quantas vezes queres
lançar o dado? "))

if num_lancamentos <= 0:
print("� O número deve ser maior que zero!")
else:
# Inicializar contadores
contadores = {1: 0, 2: 0, 3: 0, 4: 0, 5: 0, 6:
0}
soma_total = 0

print(f"\nLançando o dado {num_lancamentos}


vezes...")
print("-" * 30)

# Simular lançamentos
for i in range(1, num_lancamentos + 1):
resultado = [Link](1, 6)

407
contadores[resultado] += 1
soma_total += resultado

# Mostrar cada lançamento


print(f"Lançamento {i}: {resultado}")

# Calcular estatísticas
media = soma_total / num_lancamentos

# Encontrar o valor mais frequente


mais_frequente = max(contadores,
key=[Link])

# Mostrar resultados
print("\n" + "=" * 40)
print("�
� ESTATÍSTICAS:")
print(f"Total: {soma_total}")
print(f"Média: {media:.2f}")
print(f"Valor mais frequente:
{mais_frequente}")

print("\n�
� DISTRIBUIÇÃO:")
for face, quantidade in [Link]():
percentual = (quantidade / num_lancamentos)
* 100
print(f"Face {face}: {quantidade} vezes
({percentual:.1f}%)")

except ValueError:
print("� Erro: Digitar um número inteiro válido!")

9)

print("�
� GERADOR DE PADRÕES NUMÉRICOS")
print("=" * 40)

while True:
# Menu de opções
print("\nEscolha o tipo de padrão:")
print("1 - Triângulo numérico")

408
print("2 - Tabuada personalizada")
print("3 - Sequência de Fibonacci")
print("4 - Números primos")
print("0 - Sair")

try:
opcao = input("Sua escolha (0-4): ")

if opcao == "1":
# Triângulo numérico
altura = int(input("\nAltura do triângulo:
"))
print("\n� Triângulo Numérico:")
for i in range(1, altura + 1):
for j in range(1, i + 1):
print(j, end=" ")
print()

elif opcao == "2":


# Tabuada personalizada
numero = int(input("\nNúmero para a
tabuada: "))
limite = int(input("Até que múltiplo? "))
print(f"\n�
� Tabuada do {numero}:")
for i in range(1, limite + 1):
resultado = numero * i
print(f"{numero} × {i} = {resultado}")

elif opcao == "3":


# Sequência de Fibonacci
termos = int(input("\nQuantos termos da
sequência? "))
print("\n�
� Sequência de Fibonacci:")
a, b = 0, 1
for i in range(termos):
print(a, end=" ")
a, b = b, a + b
print()

elif opcao == "4":


# Números primos

409
limite = int(input("\nAté que número
verificar primos? "))
print(f"\n�
� Números primos até {limite}:")
for num in range(2, limite + 1):
primo = True
for i in range(2, int(num ** 0.5) + 1):
if num % i == 0:
primo = False
break
if primo:
print(num, end=" ")
print()

elif opcao == "0":


print("\n�
� Até logo!")
break # sai do ciclo principal

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

except ValueError:
print("� Digite um número válido!")
except KeyboardInterrupt:
print("\n\n�
� Programa interrompido!")
break # garante saída também no Ctrl+C

410
15.5 CAPÍTULO 6

1)

def celsius_para_fahrenheit(celsius):
"""Converte temperatura de Cº para Fahrenheit"""
return (celsius * 9/5) + 32

def fahrenheit_para_celsius(fahrenheit):
"""Converte temperatura de Fahrenheit para Cº"""
return (fahrenheit - 32) * 5/9

entrada = input("Temperatura (exemplo: 25C ou 77F): ")

# Verificando se é Celsius ou Fahrenheit


if 'C' in [Link]() or 'c' in entrada:
# É Celsius, converter para Fahrenheit
celsius = float([Link]('C',
'').replace('c', ''))
fahrenheit = celsius_para_fahrenheit(celsius)
print(f"{celsius}°C = {fahrenheit:.1f}°F")

elif 'F' in [Link]() or 'f' in entrada:


# É Fahrenheit, converter para Celsius
fahrenheit = float([Link]('F',
'').replace('f', ''))
celsius = fahrenheit_para_celsius(fahrenheit)
print(f"{fahrenheit}°F = {celsius:.1f}°C")

else:
# Se não especificou a unidade, assumir Celsius
try:
celsius = float(entrada)
fahrenheit = celsius_para_fahrenheit(celsius)
print(f"Assumindo Celsius: {celsius}°C =
{fahrenheit:.1f}°F")
except ValueError:
print("Entrada inválida. Digite no formato: 25C
ou 77F")

411
2)

def verificar_password(password):
if len(password) < 8:
return False

tem_maiuscula = False
tem_minuscula = False
tem_numero = False
for c in password:
if [Link]():
tem_maiuscula = True
if [Link]():
tem_minuscula = True
if [Link]():
tem_numero = True

return tem_maiuscula and tem_minuscula and


tem_numero

# Programa principal
print("===== Verificação de Palavra-Passe =====")
print("Requisitos mínimos:")
print("1) Ter pelo menos 8 caracteres")
print("2) Conter pelo menos uma letra maiúscula")
print("3) Conter pelo menos uma letra minúscula")
print("4) Conter pelo menos um número")
print("========================================")

palavra = input("Digite uma palavra-passe: ")

if verificar_password(palavra):
print("Palavra-passe válida")
else:
print("Palavra-passe inválida")

3)
from datetime import datetime

412
def gerar_saudacao(nome):
hora_atual = [Link]().hour

if 6 <= hora_atual <= 12:


return f"Bom dia, {nome}!"
elif 13 <= hora_atual <= 18:
return f"Boa tarde, {nome}!"
else:
return f"Boa noite, {nome}!"

print(gerar_saudacao(input("Nome: ")))
# OU em 2 linhas:
# nome = input("Nome: ")
# print(gerar_saudacao(nome))

4)

def gerar_relatorio_produto(nome, preco, quantidade):


total = preco * quantidade
print(f"Produto: {nome}")
print(f"Preço: €{preco:.2f}")
print(f"Qtd: {quantidade}")
print(f"Total: €{total:.2f}")
return total # devolve só um float

def main():
acumulado = 0.0
while True:
nome = input("Nome do produto (ou 'sair'):
").strip()
if [Link]() == "sair":
break
try:
preco = float(input("Preço (€):
").replace(",", "."))
quantidade = int(input("Quantidade: "))
except ValueError:
print("Dados inválidos. Tente de novo.\n")
continue

413
print() # linha em branco antes do relatório
acumulado += gerar_relatorio_produto(nome,
preco, quantidade)
print() # linha em branco depois do relatório

print(f"Valor total de todos os produtos:


€{acumulado:.2f}")

if __name__ == "__main__":
main()

5)

def somar(a, b):


return a + b

def subtrair(a, b):


return a - b

def multiplicar(a, b):


return a * b

def dividir(a, b):


try:
return a / b
except ZeroDivisionError:
return "Erro: Divisão por zero!"

# Programa principal
try:
num1 = int(input("Digite o primeiro número: "))
num2 = int(input("Digite o segundo número: "))

print(f"Soma: {num1} + {num2} = {somar(num1,


num2)}")
print(f"Subtração: {num1} - {num2} =
{subtrair(num1, num2)}")
print(f"Multiplicação: {num1} * {num2} =
{multiplicar(num1, num2)}")

414
print(f"Divisão: {num1} / {num2} = {dividir(num1,
num2)}")

except ValueError:
print("� Erro: Digite apenas números inteiros
válidos!")

6)

import random

def gerar_numero_secreto():
return [Link](1, 100)

def obter_palpite():
while True:
s = input("Palpite (1..100): ").strip()
try:
n = int(s)
if 1 <= n <= 100:
return n
except ValueError:
pass
print("Entrada inválida. Digite um inteiro
entre 1 e 100.")

def dar_dica(palpite, numero_secreto):


if palpite < numero_secreto:
return "Tente um número MAIOR!"
elif palpite > numero_secreto:
return "Tente um número MENOR!"
else:
return "ACERTOU!"

def jogar_adivinhacao():
secreto = gerar_numero_secreto()
tentativas = 10

for tentativa in range(1, tentativas + 1):


print(f"\nTentativa {tentativa}/{tentativas}")

415
palpite = obter_palpite()
dica = dar_dica(palpite, secreto)
if dica == "ACERTOU!":
print("Parabéns! Acertou o número.")
return
else:
print(dica)

print(f"\nTerminaram as tentativas. O número era


{secreto}.")

if __name__ == "__main__":
jogar_adivinhacao()

416
15.6 CAPÍTULO 7

1)

emails = ["[Link]@[Link]", "rui@[Link]",


"maria@[Link]", "joao@[Link]"]
dominios = sorted(set([Link]("@")[1] for e in emails))
print(dominios)

2)

# criar dados
jogadores = [
{"user": "mariana", "desafios": 12},
{"user": "rui", "desafios": 7},
{"user": "maria", "desafios": 19},
{"user": "luis", "desafios": 14},
]

# ordenar e imprimir top-3


top = sorted(jogadores, key=lambda d: d["desafios"],
reverse=True)[:3]
for i, j in enumerate(top, start=1):
print(f"{i}º {j['user'].upper()} — {j['desafios']}
desafios")

3)
# Criar lista de compras inteligente
def lista_compras_inteligente():
lista = []

while True:
produto = input("Digite um produto (ou 'fim'
para terminar): ")
if [Link]() == 'fim':
break
[Link](produto)

417
# Remover duplicados usando set e ordenar
lista_sem_duplicados = sorted(set(lista))

print("\nLista de compras organizada:")


for i,produto in enumerate(lista_sem_duplicados,1):
print(f"{i}. {produto}")

# Executar
lista_compras_inteligente()

4)

agenda = [
("2025-10-28", "09:30", "Consulta Médica de
Família"),
("2025-11-27", "14:00", "Dentista"),
("2025-11-21", "10:00", "Consultoria Python"),
]

# ordenar por data e hora


[Link](key=lambda t: (t[0], t[1]))

# imprimir DD/MM/AAAA HH:MM — Título, desempacotando o


tuplo
for data, hora, titulo in agenda:
ano, mes, dia = [Link]("-")
print(f"{dia}/{mes}/{ano} {hora} — {titulo}")

5)
# Processar dados de sensores
def processar_dados_iot():
# Matriz de temperaturas (cada linha = um sensor,
cada coluna = uma hora)
temperaturas = [
[22.5, 23.1, 24.0, 25.2, 26.1], # Sensor 1
[21.8, 22.5, 23.3, 24.0, 24.8], # Sensor 2
[23.0, 23.7, 24.5, 25.3, 26.0] # Sensor 3
]

418
# Média por sensor
medias = [sum(sensor) / len(sensor) for sensor in
temperaturas]

# Temperatura máxima global


maxima_global = max(max(sensor) for sensor in
temperaturas)

# Converter para Fahrenheit (map + lambda)


fahrenheit = list(map(lambda sensor:
list(map(lambda t: t * 9/5 + 32, sensor)),
temperaturas))

print("=== ANÁLISE DE TEMPERATURAS ===\n")

print("Médias por sensor:")


for i, media in enumerate(medias, 1):
print(f" Sensor {i:<2} ➝ {media:5.1f} °C")

print(f"\nTemperatura máxima global:


{maxima_global:.1f} °C")

print("\nLeituras em Fahrenheit:")
for i, sensor in enumerate(fahrenheit, 1):
leituras_fmt = " ".join(f"{temp:5.1f}°F" for
temp in sensor)
print(f" Sensor {i:<2} ➝ {leituras_fmt}")

# Executar
processar_dados_iot()

6)
# Construir gerador de equipas
def gerador_equipas():
jogadores = [
("Leonor", 85),("Santiago", 92),("Diana", 78),
("Sofia", 88),("Beatriz", 95),("Vicente", 82),
("Rui", 90),("Teresa", 86)
]

419
# Ordenar por rating (decrescente)
jogadores_ordenados = sorted(jogadores, key=lambda
x: x[1], reverse=True)

# Dividir em equipas
equipa_a = jogadores_ordenados[0::2] # Índices pares
equipa_b = jogadores_ordenados[1::2] # Índices
ímpares

print("=== EQUIPA A ===")


for jogador, rating in equipa_a:
print(f"{jogador}: {rating}")

print("\n=== EQUIPA B ===")


for jogador, rating in equipa_b:
print(f"{jogador}: {rating}")

# Calcular rating médio


rating_a = sum(rating for _, rating in equipa_a) /
len(equipa_a)
rating_b = sum(rating for _, rating in equipa_b) /
len(equipa_b)

print(f"\nRating médio - Equipa A: {rating_a:.1f},


Equipa B: {rating_b:.1f}")

# Executar
gerador_equipas()

7)
import random

def gerar_grelha_aleatoria_simples():
N = 5

# criar matriz 5x5 preenchida com '#'


grelha = []
for _ in range(N):
linha = []
for _ in range(N):

420
[Link]('#')
[Link](linha)

# posição aleatória do jogador J


rJ = [Link](0, N - 1)
cJ = [Link](0, N - 1)
grelha[rJ][cJ] = 'J'

# colocar 3 obstáculos '|' em posições aleatórias livres


obstaculos = [] # guardar coordenadas dos obstáculos
obst_colocados = 0
while obst_colocados < 3:
r = [Link](0, N - 1)
c = [Link](0, N - 1)
if grelha[r][c] == '#': # só se estiver vazio
grelha[r][c] = '|'
[Link]((r, c))
obst_colocados += 1

# exibir grelha
print("=== GRELHA 5x5 ===")
for i in range(N):
linha_txt = ""
for j in range(N):
if j == 0:
linha_txt = grelha[i][j]
else:
linha_txt += " " + grelha[i][j]
print(linha_txt)

# exibir coordenadas
print(f"\nJogador em: ({rJ}, {cJ})")

coords_txt = ""
for idx, par in enumerate(obstaculos):
if idx > 0:
coords_txt += ", "
coords_txt += f"({par[0]}, {par[1]})"
print("Obstáculos em:", coords_txt)

# Executar
gerar_grelha_aleatoria_simples()

421
8)

def ler_posicao(rotulo, N):


while True:
s = input(f"{rotulo} (ex.: 0 0 ou 0,0):
").strip()
# aceitar espaço, vírgula ou ponto e vírgula como
separador
s = [Link](",", " ").replace(";", " ")
partes = [Link]()
if len(partes) != 2:
print("Digite dois inteiros: linha coluna.
Ex.: 0 0")
continue
try:
r = int(partes[0])
c = int(partes[1])
except ValueError:
print("Valores inválidos. Use números
inteiros.")
continue
if 0 <= r < N and 0 <= c < N:
return r, c
print(f"Fora da grelha. Use 0..{N-1}.")

def exibir_grelha(g):
print("=== GRELHA 5x5 ===")
for i in range(len(g)):
linha_txt = g[i][0]
for j in range(1, len(g[i])):
linha_txt += " " + g[i][j]
print(linha_txt)

def programa_input_simples():
N = 5

# criar matriz 5x5 com '#'


grelha = []
for _ in range(N):
linha = []
for _ in range(N):

422
[Link]('#')
[Link](linha)

# ler jogador (zero é válido!)


rJ, cJ = ler_posicao("Posição do jogador J", N)
grelha[rJ][cJ] = 'J'

# ler 3 obstáculos '|' sem sobrepor ao J nem


repetir
obstaculos = []
i = 1
while i <= 3:
r, c = ler_posicao(f"Posição do obstáculo {i}",
N)
if (r, c) == (rJ, cJ):
print("Já existe o jogador J nessa posição.
Escolha outra.")
continue
if (r, c) in obstaculos:
print("Obstáculo repetido. Escolha outra
posição.")
continue
[Link]((r, c))
grelha[r][c] = '|'
i += 1

exibir_grelha(grelha)
print(f"\nJ em: ({rJ}, {cJ}) | Obstáculos: {',
'.join(str(p) for p in obstaculos)}")

# Executar
programa_input_simples()

9)

def votar_series():
votos = {}

while True:

423
nome = input("Nome do votante (ou 'fim' para
terminar): ").strip()
if [Link]() == "fim":
break

serie = input("Digite a série que deseja votar:


").strip()
if not serie:
continue # ignora voto vazio

votos[serie] = [Link](serie, 0) + 1

print("\n=== TOP 3 SÉRIES ===")


ranking = sorted([Link](), key=lambda kv: (-
kv[1], kv[0]))
for i, (serie, cont) in enumerate(ranking[:3],
start=1):
print(f"{i}º lugar: {serie} - {cont} votos")

# Executar
votar_series()

10)

# Analisar sentimentos em comentários


def analisador_sentimentos():
palavras_positivas = {"bom", "excelente", "ótimo",
"maravilhoso", "fantástico", "adoro"}
palavras_negativas = {"ruim", "péssimo",
"horrível", "odeio", "terrível", "chato"}

comentarios = [
"O filme é bom e excelente!",
"Que experiência horrível, péssimo serviço.",
"Adoro este restaurante, maravilhoso!",
"Não gostei, muito chato e ruim.",
"Fantástico! Recomendo a todos.",
"Apesar de chato, é um bom filme."
]

424
contador_positivas = 0
contador_negativas = 0
total_palavras = 0
palavras_analisadas = []

for comentario in comentarios:


# Limpar e separar palavras
palavras = [Link]().replace("!",
"").replace(",", "").replace(".", "").split()
total_palavras += len(palavras)
palavras_analisadas.extend(palavras)

# Contar ocorrências
for palavra in palavras:
if palavra in palavras_positivas:
contador_positivas += 1
elif palavra in palavras_negativas:
contador_negativas += 1

# Encontrar palavras únicas encontradas


positivas_encontradas = set(palavras_analisadas) &
palavras_positivas
negativas_encontradas = set(palavras_analisadas) &
palavras_negativas

# Calcular percentagens corretas


perc_positivas = (contador_positivas /
total_palavras) * 100 if total_palavras > 0 else 0
perc_negativas = (contador_negativas /
total_palavras) * 100 if total_palavras > 0 else 0
perc_neutras = 100 - perc_positivas -
perc_negativas

print("=== ANÁLISE DE SENTIMENTOS ===")


print(f"Total de palavras analisadas:
{total_palavras}")
print(f"Ocorrências positivas:
{contador_positivas}")
print(f"Ocorrências negativas:
{contador_negativas}")
print(f"Palavras positivas encontradas:\n
{positivas_encontradas}")

425
print(f"Palavras negativas encontradas:\n
{negativas_encontradas}")
print(f"Positivas: {perc_positivas:.1f}% |
Negativas: {perc_negativas:.1f}% | Neutras:
{perc_neutras:.1f}%")

# Análise por comentário individual


print("\n=== ANÁLISE POR COMENTÁRIO ===")
for i, comentario in enumerate(comentarios, 1):
palavras_comentario =
[Link]().replace("!", "").replace(",",
"").replace(".", "").split()
pos = sum(1 for p in palavras_comentario if p
in palavras_positivas)
neg = sum(1 for p in palavras_comentario if p
in palavras_negativas)

sentimento = "POSITIVO" if pos > neg else


"NEGATIVO" if neg > pos else "NEUTRO"
print(f"Comentário {i}: {sentimento} (+{pos}/-
{neg}) - '{comentario}'")

# Executar
analisador_sentimentos()

426
15.7 CAPÍTULO 8

1)

def iniciais(nome: str, ignorar_particulas: bool =


True) -> str:
PARTICULAS = {"de", "da", "das", "do", "dos", "e"}

# separar por espaços e depois por hífen


# mantendo apenas partes não vazias
palavras = []
for token in [Link]().split():
partes_hifen = [p for p in [Link]('-') if
p]
[Link](partes_hifen)

if ignorar_particulas:
palavras = [p for p in palavras if [Link]() not
in PARTICULAS]

# apanhar a 1ª letra alfabética de cada palavra


letras = []
for p in palavras:
primeira_letra = next((ch for ch in p if
[Link]()), "")
if primeira_letra:
[Link](primeira_letra.upper())

return '.'.join(letras) + '.' if letras else ''

# Leitura repetida
print("Nomes completos (Enter vazio para terminar).")
while True:
nome = input("Nome completo sem abreviar:
").strip()
if not nome: # Enter vazio termina
break
print(iniciais(nome))

427
2)

# Dicionário completo de código Morse (A-Z, 0-9 e


espaço)
MORSE_CODE = {
'A': '.-', 'B': '-...', 'C': '-.-.', 'D': '-..',
'E': '.', 'F': '..-.', 'G': '--.', 'H': '....', 'I':
'..', 'J': '.---', 'K': '-.-', 'L': '.-..', 'M': '--',
'N': '-.', 'O': '---', 'P': '.--.', 'Q': '--.-', 'R':
'.-.', 'S': '...', 'T': '-', 'U': '..-', 'V': '...-',
'W': '.--', 'X': '-..-', 'Y': '-.--','Z': '--..','0':
'-----', '1': '.----', '2': '..---', '3': '...--','4':
'....-', '5': '.....', '6': '-....', '7': '--...','8':
'---..', '9': '----.', ' ': '/', '.': '.-.-.-', ',': '-
-..--', '?': '..--..',"'": '.----.', '!': '-.-.--',
'/': '-..-.', '(': '-.--.', ')': '-.--.-', '&': '.-
...', ':': '---...', ';': '-.-.-.', '=': '-...-', '+':
'.-.-.', '-': '-....-', '_': '..--.-','"': '.-..-.',
'$': '...-..-', '@': '.--.-.'
}

def texto_para_morse(texto):
"""
Converte texto normal para código Morse.
"""
resultado = []
texto = [Link]().strip()

for caractere in texto:


if caractere in MORSE_CODE:
[Link](MORSE_CODE[caractere])
else:
# Para caracteres não suportados
# manter o caractere original
[Link](f"[{caractere}]")

return ' '.join(resultado)

def morse_para_texto(codigo_morse):
"""
Converte código Morse para texto normal.
"""

428
# Criar dicionário reverso
morse_caractere = {codigo: caractere for caractere,
codigo in MORSE_CODE.items()}

# Separar palavras (usando ' / ' como delimitador)


palavras_morse = codigo_morse.split(' / ')
resultado = []

for palavra_morse in palavras_morse:


# Separar letras dentro de cada palavra
letras_morse = palavra_morse.split()
palavra_traduzida = []

for codigo in letras_morse:


if codigo in morse_caractere:

palavra_traduzida.append(morse_caractere[codigo])
else:
palavra_traduzida.append(f"[{codigo}]")
# Código não reconhecido

[Link](''.join(palavra_traduzida))

return ' '.join(resultado)

def detectar_direcao(entrada):
"""
Deteta automaticamente se o input é texto ou Morse.
"""
entrada = [Link]()
if not entrada:
return "vazio"

# Se contém apenas caracteres Morse (. - / e


espaços), assume que é Morse
if all(c in '.-/ ' for c in entrada):
return "morse_para_texto"
else:
return "texto_para_morse"

def main():
print("�� TRADUTOR DE CÓDIGO MORSE")

429
print("=" * 40)
print("O tradutor funciona em ambas as direções:")
print("- Digite texto para converter para Morse")
print("- Digite código Morse para converter para
texto")
print("- Pressione Enter sem texto para sair")
print("=" * 40)

while True:
print("\n" + "-" * 40)
entrada = input("Digite o texto ou código
Morse: ").strip()

if not entrada:
print("Programa terminado. Até logo!")
break

direcao = detectar_direcao(entrada)

if direcao == "texto_para_morse":
resultado = texto_para_morse(entrada)
print(f"\n�
� Texto original: {entrada}")
print(f"�
� Código Morse: {resultado}")

elif direcao == "morse_para_texto":


resultado = morse_para_texto(entrada)
print(f"\n�
� Código Morse: {entrada}")
print(f"�
� Texto traduzido: {resultado}")

# Explicação da tradução (opcional)


if direcao == "texto_para_morse":
print("\nDica: Use ' / ' para separar
palavras no Morse")

# Função de teste para verificar o funcionamento


def testar_tradutor():
"""
Testa o tradutor com exemplos conhecidos.
"""
testes = [
("SOS", "... --- ..."),

430
("HELLO WORLD", ".... . .-.. .-.. --- / .-- ---
.-. .-.. -.."),("123", ".---- ..--- ...--"), ("PYTHON",
".--. -.-- - .... --- -.")
]

print("TESTANDO TRADUTOR")
print("=" * 50)

for texto, morse_esperado in testes:


# Teste texto → Morse
morse_resultado = texto_para_morse(texto)
status = "�
�" if morse_resultado ==
morse_esperado else "�"
print(f"{status} {texto} → {morse_resultado}")

# Teste Morse → texto (volta ao original)


texto_resultado =
morse_para_texto(morse_esperado)
status_volta = "�
�" if texto_resultado.upper()
== [Link]() else "�"
print(f"{status_volta} {morse_esperado} →
{texto_resultado}")
print()

if __name__ == "__main__":
# Executar testes rápidos
testar_tradutor()

# Executar programa interativo


main()

3)

def analisar_texto(texto):
"""
Analisa um texto e retorna estatísticas detalhadas.
"""
# 1. Análise básica - remover espaços extras
texto_limpo = [Link]()

431
# 2. Contar parágrafos (separados por quebras de linha)
paragrafos = [p for p in texto_limpo.split('\n') if
[Link]()]
num_paragrafos = len(paragrafos)

# 3. Contar frases (terminadas com .!?)


frases = []
for paragrafo in paragrafos:
# Dividir por exclamação, interrogação e pontos
frases_paragrafo = []
frase_atual = ""

for char in paragrafo:


frase_atual += char
if char in '.!?':

frases_paragrafo.append(frase_atual.strip())
frase_atual = ""

# Adicionar última frase se não terminou com pontuação


if frase_atual.strip():

frases_paragrafo.append(frase_atual.strip())

[Link](frases_paragrafo)

num_frases = len(frases)

# 4. Contar palavras (separadas por espaços)


palavras = []
for frase in frases:
# Remover pontuação no final das palavras
palavras_frase = []
for palavra in [Link]():
palavra_limpa =
[Link]('.,!?;:()[]{}"\'')
if palavra_limpa:
palavras_frase.append(palavra_limpa)
[Link](palavras_frase)

num_palavras = len(palavras)

432
# 5. Encontrar palavra mais longa
palavra_mais_longa = ""
for palavra in palavras:
if len(palavra) > len(palavra_mais_longa):
palavra_mais_longa = palavra

# 6. Calcular média de caracteres por palavra


total_caracteres = sum(len(palavra) for palavra in
palavras)
media_caracteres = total_caracteres / num_palavras
if num_palavras > 0 else 0

# 7. Retornar resultados
return {
'palavras': num_palavras,
'frases': num_frases,
'paragrafos': num_paragrafos,
'palavra_mais_longa': palavra_mais_longa,
'comprimento_mais_longa':
len(palavra_mais_longa),
'media_caracteres': media_caracteres,
'total_caracteres': total_caracteres
}

def mostrar_estatisticas(texto):
"""
Mostra as estatísticas formatadas em tabela.
"""
resultados = analisar_texto(texto)

print("ESTATÍSTICAS DO TEXTO")
print("=" * 50)

# Tabela principal
print(f"{'PARÂMETRO':<20} | {'VALOR':>10} |
{'DETALHES':<15}")
print("-" * 50)

print(f"{'Palavras':<20} |
{resultados['palavras']:>10} | {'Total':<15}")
print(f"{'Frases':<20} | {resultados['frases']:>10}
| {'Pontuações (.!?)':<15}")

433
print(f"{'Parágrafos':<20} |
{resultados['paragrafos']:>10} | {'Quebras de
linha':<15}")
print(f"{'Palavra mais longa':<20} |
{resultados['comprimento_mais_longa']:>10} |
{resultados['palavra_mais_longa']:<15}")
print(f"{'Média de caracteres':<20} |
{resultados['media_caracteres']:>10.1f} | {'Por
palavra':<15}")
print(f"{'Total caracteres':<20} |
{resultados['total_caracteres']:>10} | {'Sem
espaços':<15}")

print("-" * 50)

# Programa principal
def main():
print("ANALISADOR DE TEXTO")
print("=" * 30)

# Exemplo de texto para teste


texto_exemplo = """Python é uma linguagem de
programação incrível.
É fácil de aprender e muito poderosa!
Podes criar desde scripts simples até aplicações
complexas.
A comunidade é muito ativa e existem milhares de
bibliotecas disponíveis."""

print("Texto a analisar:")
print(texto_exemplo)
print("\n" + "=" * 50)

# Analisar e mostrar resultados


mostrar_estatisticas(texto_exemplo)

# Opção para o utilizador inserir seu próprio texto


print("\n" + "=" * 50)
opcao = input("Deseja analisar seu próprio texto?
(s/n): ").lower()

if opcao == 's':

434
print("\nDigite seu texto (pressione Enter duas
vezes para finalizar):")
linhas = []
while True:
linha = input()
if linha == "":
break
[Link](linha)

texto_usuario = "\n".join(linhas)

if texto_usuario.strip():
print("\n" + "=" * 50)
mostrar_estatisticas(texto_usuario)
else:
print("Nenhum texto inserido.")

# Executar o programa
if __name__ == "__main__":
main()

435
15.8 CAPÍTULO 9

1)

import os
import re
import csv
import json

# -------------------------------
# PADRÕES DE PHISHING
# -------------------------------
PALAVRAS_SUSPEITAS = [
"urgente", "verifique a sua conta", "clique aqui",
"atualize os seus dados",
"password", "palavra-passe", "credenciais", "dados
bancários",
"confirme", "login", "acesso bloqueado", "conta
suspensa",
"oferta imperdível", "prémio", "ganhou", "pagamento
pendente"
]

PEDIDOS_DADOS_PESSOAIS = [
"número de cartão", "cartão de crédito", "iban",
"nif",
"senha", "pin", "cvv", "dados pessoais"
]

DOMINIOS_SUSPEITOS = [
"[Link]", "[Link]", "[Link]", "shady-
[Link]",
"[Link]", "[Link]"
]

# -------------------------------
# FUNÇÕES AUXILIARES
# -------------------------------
def extrair_urls(texto):
padrao = r'https?://[^\s]+|www\.[^\s]+'
return [Link](padrao, [Link]())
def contar_palavras_suspeitas(texto):
436
texto = [Link]()
encontradas = []

for palavra in PALAVRAS_SUSPEITAS:


if palavra in texto:
[Link](palavra)

return encontradas

def contar_pedidos_dados(texto):
texto = [Link]()
encontrados = []

for termo in PEDIDOS_DADOS_PESSOAIS:


if termo in texto:
[Link](termo)

return encontrados

def verificar_urls_suspeitas(urls):
urls_suspeitas = []

for url in urls:


for dominio in DOMINIOS_SUSPEITOS:
if dominio in url:
urls_suspeitas.append(url)
break

# Heurística simples: URL com IP em vez de domínio


if [Link](r'https?://\d+\.\d+\.\d+\.\d+',
url):
urls_suspeitas.append(url)

return list(set(urls_suspeitas))

def calcular_risco(texto):
pontuacao = 0
detalhes = []

urls = extrair_urls(texto)
palavras = contar_palavras_suspeitas(texto)
dados = contar_pedidos_dados(texto)

437
urls_suspeitas = verificar_urls_suspeitas(urls)

if urls:
pontuacao += len(urls)
[Link](f"Foram encontradas {len(urls)}
URL(s).")

if palavras:
pontuacao += len(palavras) * 2
[Link](f"Palavras suspeitas: {',
'.join(palavras)}")

if dados:
pontuacao += len(dados) * 3
[Link](f"Pedidos de dados pessoais:
{', '.join(dados)}")

if urls_suspeitas:
pontuacao += len(urls_suspeitas) * 4
[Link](f"URLs suspeitas: {',
'.join(urls_suspeitas)}")

if pontuacao >= 8:
risco = "ALTO"
elif pontuacao >= 4:
risco = "MÉDIO"
else:
risco = "BAIXO"

return {
"pontuacao": pontuacao,
"risco": risco,
"urls": urls,
"urls_suspeitas": urls_suspeitas,
"palavras_suspeitas": palavras,
"pedidos_dados_pessoais": dados,
"detalhes": detalhes
}

# -------------------------------
# LEITURA DE FICHEIROS
# -------------------------------

438
def ler_txt(caminho):
with open(caminho, "r", encoding="utf-8") as f:
return [[Link]()]

def ler_csv(caminho):
emails = []

with open(caminho, "r", encoding="utf-8") as f:


leitor = [Link](f)
for linha in leitor:
texto = " ".join(linha).strip()
if texto:
[Link](texto)

return emails

def ler_emails(caminho):
extensao = [Link](caminho)[1].lower()

if extensao == ".txt":
return ler_txt(caminho)
elif extensao == ".csv":
return ler_csv(caminho)
else:
raise ValueError("Formato não suportado. Use
.txt ou .csv")

# -------------------------------
# RELATÓRIO
# -------------------------------
def gerar_relatorio(resultados):
print("\n" + "=" * 60)
print("RELATÓRIO DE ANÁLISE DE PHISHING")
print("=" * 60)

for i, resultado in enumerate(resultados, start=1):


print(f"\nEmail {i}")
print("-" * 40)
print(f"Risco: {resultado['risco']}")
print(f"Pontuação: {resultado['pontuacao']}")

if resultado["detalhes"]:

439
print("Indicadores encontrados:")
for d in resultado["detalhes"]:
print(f" - {d}")
else:
print("Nenhum indicador suspeito
encontrado.")

def guardar_json(resultados,
ficheiro_saida="relatorio_phishing.json"):
with open(ficheiro_saida, "w", encoding="utf-8") as
f:
[Link](resultados, f, ensure_ascii=False,
indent=4)

# -------------------------------
# PROGRAMA PRINCIPAL
# -------------------------------
def main():
caminho = input("Indique o nome do ficheiro de
emails (.txt ou .csv): ").strip()

try:
emails = ler_emails(caminho)

resultados = []
for email in emails:
analise = calcular_risco(email)
analise["conteudo"] = email
[Link](analise)

gerar_relatorio(resultados)
guardar_json(resultados)

print("\nResultados guardados em
'relatorio_phishing.json'.")

except FileNotFoundError:
print("Erro: ficheiro não encontrado.")
except ValueError as e:
print("Erro:", e)
except Exception as e:
print("Ocorreu um erro:", e)

440
if __name__ == "__main__":
main()

Exemplo de ficheiro para teste

Caro utilizador,

A sua conta foi suspensa. Clique aqui para atualizar os


seus dados:
[Link]

Confirme o seu número de cartão e a sua password


urgentemente.

2)

import os
import re
import json
from [Link] import ElementTree as ET

# -----------------------------------
# BASE DE DADOS DE VULNERABILIDADES
# -----------------------------------

BASE_VULNERABILIDADES = [
{
"id": "VULN-001",
"nome": "Palavra-passe em claro",
"descricao": "Foi encontrada uma password
armazenada diretamente no ficheiro."
},
{
"id": "VULN-002",
"nome": "Permissões demasiado permissivas",
"descricao": "Foi detetada uma configuração de
permissões insegura."
},
{
"id": "VULN-003",

441
"nome": "Uso de utilizador root/admin",
"descricao": "Foi encontrado uso direto de
root/admin em contexto sensível."
}
]

PADROES_PASSWORD = [
r'password\s*[:=]\s*.+',
r'passwd\s*[:=]\s*.+',
r'secret\s*[:=]\s*.+',
r'"password"\s*:\s*".+"'
]

PADROES_PERMISSOES_FRACAS = [
r'chmod\s+777',
r'permission\s*[:=]\s*777',
r'"permission"\s*:\s*"777"',
r'allow_all\s*[:=]\s*true',
r'"allow_all"\s*:\s*true'
]

PADROES_ROOT = [
r'user\s*[:=]\s*root',
r'username\s*[:=]\s*admin',
r'"user"\s*:\s*"root"',
r'"username"\s*:\s*"admin"'
]

# -----------------------------------
# LEITURA DOS FICHEIROS
# -----------------------------------
def ler_ficheiro_texto(caminho):
with open(caminho, "r", encoding="utf-8") as f:
return [Link]()

def ler_json(caminho):
with open(caminho, "r", encoding="utf-8") as f:
dados = [Link](f)
return [Link](dados, indent=4,
ensure_ascii=False)

442
def ler_xml(caminho):
arvore = [Link](caminho)
raiz = [Link]()
return [Link](raiz, encoding="unicode")

def ler_configuracao(caminho):
extensao = [Link](caminho)[1].lower()

if extensao == ".json":
return ler_json(caminho)
elif extensao == ".xml":
return ler_xml(caminho)
elif extensao == ".conf":
return ler_ficheiro_texto(caminho)
else:
raise ValueError("Formato não suportado. Use
.conf, .xml ou .json")

# -----------------------------------
# DETEÇÃO DE VULNERABILIDADES
# -----------------------------------
def procurar_padroes(texto, lista_padroes):
encontrados = []

for padrao in lista_padroes:


correspondencias = [Link](padrao, texto,
flags=[Link])
[Link](correspondencias)

return encontrados

def analisar_ficheiro(caminho):
texto = ler_configuracao(caminho)
vulnerabilidades_encontradas = []
passwords = procurar_padroes(texto,
PADROES_PASSWORD)
permissoes = procurar_padroes(texto,
PADROES_PERMISSOES_FRACAS)
root_admin = procurar_padroes(texto, PADROES_ROOT)
if passwords:
vulnerabilidades_encontradas.append({
"id": "VULN-001",

443
"nome": "Palavra-passe em claro",
"ocorrencias": passwords
})
if permissoes:
vulnerabilidades_encontradas.append({
"id": "VULN-002",
"nome": "Permissões demasiado permissivas",
"ocorrencias": permissoes
})
if root_admin:
vulnerabilidades_encontradas.append({
"id": "VULN-003",
"nome": "Uso de utilizador root/admin",
"ocorrencias": root_admin
})
return vulnerabilidades_encontradas

def obter_descricao_vulnerabilidade(vuln_id):
for vuln in BASE_VULNERABILIDADES:
if vuln["id"] == vuln_id:
return vuln["descricao"]
return "Descrição não encontrada."

# -----------------------------------
# RELATÓRIO HTML
# -----------------------------------
def gerar_html(caminho_ficheiro, vulnerabilidades,
ficheiro_saida="relatorio_vulnerabilidades.html"):
html = """
<html>
<head>
<meta charset="UTF-8">
<title>Relatório de Vulnerabilidades</title>
<style>
body { font-family: Arial, sans-serif;
margin: 30px; background-color: #f4f4f4; }
h1 { color: #333; }
h2 { color: #444; }
.bloco { background: white; padding: 15px;
margin-bottom: 20px; border-radius: 8px; }
.critico { color: red; font-weight: bold; }

444
.seguro { color: green; font-weight: bold;
}
ul { line-height: 1.6; }
</style>
</head>
<body>
"""

html += f"<h1>Relatório de Vulnerabilidades</h1>"


html += f"<div class='bloco'><h2>Ficheiro
analisado</h2><p>{caminho_ficheiro}</p></div>"

if not vulnerabilidades:
html += "<div class='bloco'><p
class='seguro'>Nenhuma vulnerabilidade
encontrada.</p></div>"
else:
html += "<div class='bloco'><p
class='critico'>Foram encontradas
vulnerabilidades.</p></div>"

for vuln in vulnerabilidades:


descricao =
obter_descricao_vulnerabilidade(vuln["id"])
html += "<div class='bloco'>"
html += f"<h2>{vuln['id']} -
{vuln['nome']}</h2>"
html += f"<p>{descricao}</p>"
html +=
"<p><strong>Ocorrências:</strong></p><ul>"

for ocorrencia in vuln["ocorrencias"]:


html += f"<li>{ocorrencia}</li>"

html += "</ul></div>"

html += "</body></html>"

with open(ficheiro_saida, "w", encoding="utf-8") as


f:
[Link](html)

445
# -----------------------------------
# PROGRAMA PRINCIPAL
# ----------------------------------
def main():
caminho = input("Indique o nome do ficheiro (.conf,
.xml ou .json): ").strip()

try:
vulnerabilidades = analisar_ficheiro(caminho)
gerar_html(caminho, vulnerabilidades)

print("\nAnálise concluída.")
print("Relatório guardado em
'relatorio_vulnerabilidades.html'.")

except FileNotFoundError:
print("Erro: ficheiro não encontrado.")
except ValueError as e:
print("Erro:", e)
except Exception as e:
print("Ocorreu um erro:", e)

if __name__ == "__main__":
main()

Exemplo de ficheiro para teste

user=root
password=123456
permission=777
allow_all=true

446
15.9 CAPÍTULO 10

1)

class Estudante:

def __init__(self, nome):


self.__nome = nome

def get_nome(self):
return self.__nome

def set_nome(self, novo_nome):


if not isinstance(novo_nome):
raise TypeError("Nome deve ser string.")
if novo_nome == "":
raise ValueError("Nome não pode ser vazio")
self.__nome = novo_nome

def imprimir_nome(self):
print(f"Estudante: {self.__nome}")

estudante_1 = Estudante("Guido van Rossum")

print(f"O estudante chama-se {estudante_1.get_nome()}")

2)

class Colaborador:
empresa = "Tech & Data, SA"

def __init__(self, nome, nif, salario_base,


departamento):
self.__nome = None
self.__nif = None
self.__salario_base = None
self.__departamento = None
self.__avaliacoes = []
self.set_nome(nome)
self.set_nif(nif)
447
self.set_salario_base(salario_base)
self.set_departamento(departamento)

def get_nome(self):
return self.__nome

def set_nome(self, novo_nome):


if not isinstance(novo_nome, str) or
novo_nome.strip() == "":
raise ValueError("Nome inválido.")
self.__nome = novo_nome.strip()

def get_nif(self):
return self.__nif

def set_nif(self, novo_nif):


if not isinstance(novo_nif, str) or
len(novo_nif) != 9 or not novo_nif.isdigit():
raise ValueError("NIF inválido.")
self.__nif = novo_nif

def get_salario_base(self):
return self.__salario_base

def set_salario_base(self, valor):

try:
v = float(valor)

except (TypeError, ValueError):


raise ValueError("Salário inválido.")
if v <= 0:
raise ValueError("Salário inválido.")
self.__salario_base = v

def get_departamento(self):
return self.__departamento

def set_departamento(self, novo_departamento):


if not isinstance(novo_departamento, str) or
novo_departamento.strip() == "":
raise ValueError("Departamento inválido.")

448
self.__departamento = novo_departamento.strip()

def get_avaliacoes(self):
return tuple(self.__avaliacoes)

def transferir_para(self, novo_departamento):


self.set_departamento(novo_departamento)

def registar_desempenho(self, nota):


if isinstance(nota, bool):
raise ValueError("Nota inválida.")

try:
n = int(nota)
except (TypeError, ValueError):

raise ValueError("Nota inválida.")


if n < 1 or n > 5:
raise ValueError("Nota inválida.")

c = Colaborador("guido van rossum", "123456789", 1600,


"Engenharia")

# Testes de getters
print(c.get_nome())
print(c.get_nif())
print(c.get_salario_base())
print(c.get_departamento())
print([Link])

# Testes de setters
c.set_nome("Guido van Rossum")
c.set_nif("987654321")
c.set_salario_base(1700)
c.set_departamento("Plataforma")

# Transferência de departamento
c.transferir_para("I&D")

# Registo de desempenho
c.registar_desempenho(4)
c.registar_desempenho(5)

449
c.registar_desempenho(3)

# Verificação final
print(c.get_nome(), c.get_nif(), c.get_salario_base(),
c.get_departamento(), c.get_avaliacoes())

3)

class Estagiario(Colaborador):
def __init__(self, nome, nif, salario_base,
departamento, bolsa):
super().__init__(nome, nif, salario_base,
departamento)
self.__bolsa = bolsa

def get_bolsa(self):
return self.__bolsa

def set_bolsa(self, valor):

try:
v = float(valor)

except (TypeError, ValueError):


raise ValueError("Bolsa inválida.")
if v <= 0:
raise ValueError("Bolsa inválida.")
self.__bolsa = v

def salario_mensal(self):
return self.__bolsa

def elegivel_efetivacao(self):
avals = self.get_avaliacoes()
if len(avals) < 3:
return False
media = sum(avals) / len(avals)
return media >= 4

e = Estagiario("James Gosling", "123456789", 1600,


"Engenharia", 900)

450
e.registar_desempenho(4)
e.registar_desempenho(5)
e.registar_desempenho(4)

print(e.get_nome(), e.get_nif(), e.get_departamento(),


e.get_bolsa())

print("Salário mensal (bolsa):", e.salario_mensal())

if e.elegivel_efetivacao():
print(f"Sim, está elegível para efetivação")
else:
print(f"Não está elegível para efetivação")

451
Capítulo 13

1)

import tkinter as tk
from tkinter import filedialog, messagebox

# ----------------------------
# Janela principal
# ----------------------------
root = [Link]()
[Link]("Editor de Texto - Tkinter")
[Link]("800x500")

ficheiro_atual = None

# ----------------------------
# Área de texto com scrollbars
# ----------------------------
frame_texto = [Link](root)
frame_texto.pack(fill="both", expand=True)

scroll_y = [Link](frame_texto)
scroll_y.pack(side="right", fill="y")

scroll_x = [Link](frame_texto,
orient="horizontal")
scroll_x.pack(side="bottom", fill="x")

texto = [Link](
frame_texto,
wrap="none",
undo=True,
yscrollcommand=scroll_y.set,
xscrollcommand=scroll_x.set,
font=("Consolas", 12)
)
[Link](fill="both", expand=True)

scroll_y.config(command=[Link])

452
scroll_x.config(command=[Link])

# ----------------------------
# Barra de estado
# ----------------------------
barra_status = [Link](root, text="Pronto",
anchor="w")
barra_status.pack(fill="x")

def atualizar_status(msg):
barra_status.config(text=msg)

# ----------------------------
# Funções de ficheiros
# ----------------------------
def novo_ficheiro(event=None):
global ficheiro_atual
[Link]("1.0", [Link])
ficheiro_atual = None
atualizar_status("Novo ficheiro criado")

def abrir_ficheiro(event=None):
global ficheiro_atual
ficheiro = [Link](
filetypes=[("Ficheiros de texto", "*.txt"),
("Todos os ficheiros", "*.*")]
)
if ficheiro:
try:
with open(ficheiro, "r", encoding="utf-8")
as f:
conteudo = [Link]()
[Link]("1.0", [Link])
[Link]("1.0", conteudo)
ficheiro_atual = ficheiro
atualizar_status(f"Abrido: {ficheiro}")
except Exception as e:
[Link]("Erro", f"Não foi
possível abrir o ficheiro.\n{e}")

def guardar_ficheiro(event=None):
global ficheiro_atual

453
if ficheiro_atual:
try:
with open(ficheiro_atual, "w",
encoding="utf-8") as f:
[Link]([Link]("1.0", [Link]))
atualizar_status(f"Guardado:
{ficheiro_atual}")
except Exception as e:
[Link]("Erro", f"Erro ao
guardar o ficheiro.\n{e}")
else:
guardar_como()

def guardar_como():
global ficheiro_atual
ficheiro = [Link](
defaultextension=".txt",
filetypes=[("Ficheiros de texto", "*.txt"),
("Todos os ficheiros", "*.*")]
)
if ficheiro:
ficheiro_atual = ficheiro
guardar_ficheiro()

# ----------------------------
# Operações de edição
# ----------------------------
def cortar(event=None):
texto.event_generate("<<Cut>>")

def copiar(event=None):
texto.event_generate("<<Copy>>")

def colar(event=None):
texto.event_generate("<<Paste>>")

def desfazer(event=None):
try:
texto.edit_undo()
except:
pass

454
def refazer(event=None):
try:
texto.edit_redo()
except:
pass

# ----------------------------
# Menu
# ----------------------------
menu_principal = [Link](root)

menu_ficheiro = [Link](menu_principal, tearoff=0)


menu_ficheiro.add_command(label="Novo",
command=novo_ficheiro, accelerator="Ctrl+N")
menu_ficheiro.add_command(label="Abrir",
command=abrir_ficheiro, accelerator="Ctrl+O")
menu_ficheiro.add_command(label="Guardar",
command=guardar_ficheiro, accelerator="Ctrl+S")
menu_ficheiro.add_command(label="Guardar Como",
command=guardar_como)
menu_ficheiro.add_separator()
menu_ficheiro.add_command(label="Sair",
command=[Link])

menu_editar = [Link](menu_principal, tearoff=0)


menu_editar.add_command(label="Desfazer",
command=desfazer, accelerator="Ctrl+Z")
menu_editar.add_command(label="Refazer",
command=refazer, accelerator="Ctrl+Y")
menu_editar.add_separator()
menu_editar.add_command(label="Cortar", command=cortar,
accelerator="Ctrl+X")
menu_editar.add_command(label="Copiar", command=copiar,
accelerator="Ctrl+C")
menu_editar.add_command(label="Colar", command=colar,
accelerator="Ctrl+V")

menu_principal.add_cascade(label="Ficheiro",
menu=menu_ficheiro)
menu_principal.add_cascade(label="Editar",
menu=menu_editar)

455
[Link](menu=menu_principal)

# ----------------------------
# Atalhos de teclado
# ----------------------------
[Link]("<Control-n>", novo_ficheiro)
[Link]("<Control-o>", abrir_ficheiro)
[Link]("<Control-s>", guardar_ficheiro)
[Link]("<Control-x>", cortar)
[Link]("<Control-c>", copiar)
[Link]("<Control-v>", colar)
[Link]("<Control-z>", desfazer)
[Link]("<Control-y>", refazer)

# ----------------------------
# Iniciar aplicação
# ----------------------------
[Link]()

456
457

Você também pode gostar