---
title: Estrutura Básica da Linguagem Python
author: >
  Prof. Dr. <a href="http://leg.ufpr.br/~wagner" style="color: inherit;">Wagner Hugo Bonat</a>
---


<!-- class: middle section -->
# Estrutura Básica da Linguagem

---
# Modo de Uso: REPL vs Batch

O `Python` trabalha com a ideia de **interface de linha de comando** (_command line interface_).

+ **Modo REPL** (*Read, Eval, Print and Loop*):
  - Escreve instruções e avalia imediatamente no *console*.
  - Supervisão comando a comando.
  - Forma mais comum para análise interativa de dados.

+ **Modo Batch**:
  - Escreve uma sequência completa de códigos e executa de uma só vez via terminal.
  - Necessário em ambientes de produção sem supervisão direta do programador.

---
# Ambiente RStudio IDE e Markdown

Neste material, usamos o **RStudio IDE** para programar em Python.

+ Arquivo `.py`: Para criar, acesse `Arquivo > Novo Arquivo > Python Script`.
+ Execução: Posicione o cursor na linha e pressione `CTRL+ENTER`.
+ Os resultados da execução do Python aparecem marcados com `##`.

```{python, eval = TRUE, results = "hold"}
# Executando um comando simples em Python
2 + 3
```

---
# Instrução vs Comentário

+ Instrução: Diz ao computador o que executar.
+ Comentário: Tudo após o caractere #. Serve para documentação e não é executado.

```{python}
# Faz uma soma
2 + 3

# Meu índice de massa corporal
83 / pow(1.85, 2)

# Quantos segundos tem um dia?
24 * 60 * 60
```

---
# Organização e Boas Práticas do Código

+ É comum colocar cada instrução em uma linha:

```{python}
# Instrução em uma linha
2 + 2 + 7 + 5

# Várias instruções em uma linha
2 + 2; 7 + 5
```

+ Recomendações de código limpo:
  + Evite ultrapassar 72 ou 80 caracteres por linha.
  + Mantenha o código indentado (no RStudio, use CTRL+I).
  + Evite muitas instruções em uma única linha.


---
<!-- class: middle section -->
# Diretório de Trabalho

---
# Módulo `os`

* Biblioteca padrão do Python para interação com o sistema operacional.
* Permite manipular arquivos, diretórios e caminhos de forma programática.
* Usada para automatizar tarefas relacionadas ao gerenciamento de dados.
* Permite descobrir e alterar o diretório de trabalho atual.
* Facilita a criação, remoção e navegação entre pastas.
* Possui funções para verificar a existência de arquivos e diretórios.
* Muito utilizada em projetos de Ciência de Dados para organizar datasets, scripts e resultados.
* Documentação: https://docs.python.org/3/library/os.html

---
# Módulo `os`

+ Principais funções

```{python, eval = FALSE}
import os

## Captura o diretório atual de trabalho
os.getcwd() 
## Altera o diretório de trabalho
os.chdir("/caminho/do/diretorio")
## Lista arquivos e pastas
os.listdir()
## Cria uma nova pasta
os.mkdir()
## Verifica se o caminho existe
os.path.exists()
## Monta um path robusto ao SO
arquivo = os.path.join("dados","clientes.csv")

```

---
<!-- class: middle section -->
# Área de Trabalho

---
# Atribuição e Objetos

+ Podemos armazenar informações em objetos na área de trabalho usando o operador `=`. 

$$IMC = \frac{Peso}{Altura^2}$$

```{python}
# Criação dos objetos peso e altura
peso = 83
altura = 1.80

# Cálculo e exibição do IMC
imc = peso / altura ** 2
print(imc)

```

+ A potenciação em Python pode ser feita com ** ou pow(base, exp).

---
# Removendo objetos

+ Quando necessário, podemos apagar objetos da área de trabalho usando a função del.

```{python}
# Lista os arquivos criados pelo usuário
[obj for obj in dir() if not obj.startswith("__")]

# Apagando o objeto imc
del imc

# Tentar acessar o imc apos o del retornará erro
# print(imc)
```

---
<!-- class: middle section -->
# Operadores Aritméticos, Lógicos e Relacionais

---
# Operadores Aritméticos

.pull-left[
+ Principais operadores aritméticos nativos:

```{python, eval = FALSE}
2 + 3      # Soma
2 - 3      # Subtração
2 * 3      # Multiplicação
2 / 3      # Divisão
2 ** 3     # Potenciação
2 ** (1/3) # Radiciação
10 % 3     # Resto da divisão
```

+ Funções Matemáticas do Módulo `math`.

```{python, eval = FALSE}
import math

math.exp(2)    # Exponencial
math.log(10)   # Logaritmo neperiano
math.log10(10) # Logaritmo base 10
math.log2(10)  # Logaritmo base 2
math.log(10,5) # Logaritmo base qualquer
math.gamma(10) # Função Gamma

```
]

.pull-right[
+ Funções Trigonométricas do módulo `math`


```{python, eval = FALSE}
import math

math.sin(3)    # Seno
math.cos(3)    # Cosseno
math.tan(3)    # Tangente
math.asin(0.5) # Arco seno
math.acos(0.5) # Arco cosseno
math.atan(0.5) # Arco tangente

```

] 

---
# Operadores relacionais e lógicos

.pull-left[
## Relacionais

```{python, eval = FALSE}
x = 3
y = 4

print("x == y:", x == y)  # Igual a
print("x != y:", x != y)  # Diferente de
print("x <= y:", x <= y)  # Menor ou igual a
print("x >= y:", x >= y)  # Maior ou igual a
print("x > y :", x > y)   # Maior que
print("x < y :", x < y)   # Menor que
```

]

.pull-right[
## Lógicos

```{python}
# 2 < 5 E 7 >= 3
print((2 < 5) and (7 >= 3))

# 2 < 5 OU 7 >= 3
print((2 < 5) or (7 >= 3))

# Negativo de (2 < 5)
print(not(2 < 5))
```

]


---
<!-- class: middle section -->
# Tipos de dados

---
# Tipos numéricos em Python

+ Os principais tipos numéricos em Python são int, float, complex e bool.

```{python}
# Inteiro
a = 2 + 2
print(a, type(a))

# Real / Ponto Flutuante
b = 2 + 2.1
print(b, type(b))

# Complexo
c = 20.1 + 20j
print(c, type(c))

# Booleano como Inteiro
print(2 > 3, int(2 > 3))
```

---
# Trabalhando com Strings (str)

+ Cadeias de caracteres podem ser declaradas com aspas simples, duplas ou triplas.

```{python}
# Declarando uma string
x = "Estatística aplicada"
print(x, type(x))

# Converter número em string
s = str(10.40)
print(s, type(s))
```

---
# Atributos e Métodos de str

```{python}
t = "isto é um objeto str"

print(t.capitalize())    # Capitaliza primeira letra
print(t.split())         # Divide em palavras
print(t.find('str'))     # Índice de início do termo
print(t.replace(" ", "|")) # Substitui caractere
print(t.upper())         # Caixa alta
print(t.lower())         # Caixa baixa

# Concatenação de Strings
t3 = "Texto1 "
t4 = "Texto2"
print(t3 + t4)
```


---
<!-- class: middle section -->
# Estruturas de Dados

---
# Visão geral

+ Estruturas para armazenar múltiplos elementos em Python:
  + `tuple`: Coleção ordenada e imutável.
  + `range`: Sequência de números (geralmente usada em loops).
  + `list`: Coleção ordenada e mutável.
  + `dict`: Estrutura de pares chave-valor (key-value).
  + `set`: Coleção não ordenada de elementos únicos.

---
# `tuple`

.pull-left[
+ Definida por parênteses ().

```{python, eval = FALSE}
t1 = (1, 2.5, 'tuple')
print(t1, type(t1))

# Tamanho da tuple
print("Tamanho:", len(t1))
```

+ Indexação e _slicing_ 

```{python, eval = FALSE}
t2 = (0, 1, 2, 3, 4, 5, 6, 7, 8, 9)

print("Elemento 0:", t2[0])
print("Fatia 2:9:2:", t2[2:9:2])
print("Fatia :9:2 :", t2[:9:2])

# Índices negativos
print("Último elemento:", t2[-1])
print("Invertido      :", t2[-1::-1])
```
]

.pull-right[
+ Imutabilidade e métodos

```{python, eval = FALSE}
t2 = (1, 2, 2, 2, 3, 3, 3, 5, 5, 5, 5, "tuple2")
t3 = ("eu", 2, 3, "mesmo")

# Concatenação
print(t2 + t3)

# Métodos disponíveis
print("Contagem do 5:", t2.count(5))
print("Primeiro índice do 5:", t2.index(5))
```

]

---
# `range`

+ Estrutura imutável e eficiente em uso de memória para gerar sequências de inteiros.

```{python}
# Criando um range
r = range(0, 10)
print(r)

# Convertendo em tuple para visualizar elementos
print(tuple(r))

# Fatiamento em range
i2 = range(0, 10)
print(tuple(i2[2:9:2]))
```

---
# `list`

+ Definida por colchetes `[]`. As listas são mutáveis (`mutable`).

```{python}
l1 = [1, 2.5, 'lista']
print(l1, type(l1))

# Adicionando elementos
l1.append([4, 3])
print("Após append:", l1)

l1.extend([1.0, 1.5, 2.0])
print("Após extend:", l1)

# Inserindo em posição específica
l1.insert(1, 'insert')
print("Após insert:", l1)
```

---
# `list`

```{python}
# Removendo elementos
l1.remove('lista')
print("Após remove:", l1)

popped = l1.pop(3)
print("Elemento removido com pop(3):", popped)

# Ordenação e inversão
num_list = [20, 10, 30, 5, 15]
num_list.sort()
print("Lista ordenada:", num_list)
num_list.reverse()
print("Lista invertida:", num_list)

```

---
# `dict` · Dicionários

+ Armazena dados no formato chave-valor (`key-value`). É mutável e não ordenado.

```{python}
d = {
    'Nome': ('Wagner Hugo Bonat', 'Walmes Marques Zeviani'),
    'País': ('Brasil', 'Brasil'),
    'Profissão': ('Professor', 'Professor'),
    'Idade': (41, 40),
}

print(type(d))
print("Nomes:", d['Nome'])
print("Chaves:", list(d.keys()))
print("Valores:", list(d.values()))

```

---
# `sets`

```{python}
s = set(['A', 'B', 'AB', 'BA', 'B', 'BA'])
t = set(['B', 'BB', 'AA', 'A'])

print("Conjunto s:", s)
print("União    :", s.union(t))
print("Intersecção:", s.intersection(t))
print("Diferença (s - t):", s.difference(t))
```


---
<!-- class: middle section -->
# Estruturas de Controle e Repetição

---
# Estrutura condicional (`if`, `elif` e `else`).

+ Executa instruções com base em condições lógicas.

```{python}
nota = 70
faltas = 20

if nota >= 70 and faltas < 15:
    condicao = "Aprovado"
elif nota < 70:
    condicao = "Reprovado por nota"
else:
    condicao = "Reprovado por falta"

print("Situação do aluno:", condicao)
```

---
# Exemplo: Classificação do IMC

```{python}
import math

peso = 80
altura = 1.60
imc = round(peso / (math.pow(altura, 2)), 1)

if imc < 18.5:
    classifica = "Magreza"
elif imc >= 18.5 and imc < 25:
    classifica = "Adequado"
elif imc >= 25 and imc < 30:
    classifica = "Pré-obeso"
else:
    classifica = "Obeso"

print(f"IMC: {imc} | Classificação: {classifica}")
```

---
# Escopo e indentação

+ Em Python, espaços e indentação definem o escopo dos blocos de código.

```{python}
x = 1
if x > 10:
    # Dentro do bloco 'if'
    x = x + 1

# Fora do bloco 'if'
x = x + 3
print("Valor final de x:", x)

```

---
# Estrutura de repetição `for`

+ Utilizada quando o número de iterações é conhecido previamente.

```{python}
x = [1, 2, 3, 4, 5]
quadrado = []

for i in range(5):
    quadrado.append(x[i] ** 2)

print("Quadradro dos elementos:", quadrado)
```

---
# Exemplo: Soma de vetores

+ A soma de vetores é definida por: $$\mathbf{a} + \mathbf{b} = (a_1 + b_1, \ldots, a_n + b_n)$$

```{python}
a = [1, 2, 3]
b = [5, 6, 7]
resultado = [0] * 3

for i in range(3):
    resultado[i] = a[i] + b[i]

print("Soma dos vetores:", resultado)
```

---
# Controle de loops: `break` e `continue`

+ `break`: Interrompe a execução do loop.
+ `continue`: Pula para a próxima iteração do loop.

```{python}
y = [1, 2, 1, 4, 0, 'NA', 3, 2, 4]
s = 0

for i in range(len(y)):
    if s > 10:
        break
    if y[i] == 'NA':
        continue
    s = s + y[i]
    print(f"Soma parcial: {s}")
```

---
# Iterando sobre sequências e strings

+ Podemos iterar diretamente sobre os elementos de qualquer sequência:
```{python}
vogais = 'aeiou'
nome = 'wagner'

print("Vogais encontradas no nome:")
for char in nome:
    if char in vogais:
        print(char)
```

---
# List Comprehensions

+ Sintaxe concisa para criar listas a partir de iteráveis.

```{python}
# Quadrado dos números de 0 a 4
quadrados = [i ** 2 for i in range(5)]
print(quadrados)
```

+ Cálculo de $\pi$ (Fórmula de Wallis)$$\pi = 2 \prod_{i=1}^n \frac{4i^2}{4i^2 - 1}$$

```{python}
import math

# Usando list comprehension + math.prod
temp = [4 * pow(i, 2) / (4 * pow(i, 2) - 1) for i in range(1, 1000)]
pi_aprox = 2 * math.prod(temp)

print("Aproximação de Pi com 1000 termos:", pi_aprox)
```

---
# Estrutura de repetição `while`

+ Utilizada quando o número de iterações depende de uma condição de parada.

```{python}
import numpy as np

n_numbers = 12
total = 0
i = 1

while i < n_numbers:
    u = total + np.random.rand(1)[0]
    if u > 6:
        break
    total = u
    i = i + 1

print(f"Tentativas: {i} | Total acumulado: {round(total, 2)}")
```


---
<!-- class: middle section -->
# Funções

---
# Funções: Conceito e componentes

.pull-left[
Uma função encapsula um conjunto de instruções reutilizáveis.

+ Componentes:
  1. Nome da função: Usado para chamá-la (`def nome()` ).
  2. Lista de parâmetros: Argumentos formais de entrada.
  3. Corpo da função: Bloco de código a ser executado.
  4. Retorno: Declaração return enviando a saída.
]

.pull-right[
```{python}
def calcula_imc(peso, altura):
    imc = round(peso / (pow(altura, 2)), 1)
    if imc < 18.5:
        classifica = "Magreza"
    elif imc >= 18.5 and imc < 25:
        classifica = "Adequado"
    elif imc >= 25 and imc < 30:
        classifica = "Pré-obeso"
    else:
        classifica = "Obeso"
    return [imc, classifica]

# Chamando a função com argumentos nomeados
print(calcula_imc(peso = 80, altura = 1.80))
```
]

---
# Tipos de argumentos

+ Podemos definir valores padrão (default) para os argumentos da função.

```{python}
def calcula_imc(peso, altura = 1.70):
    imc = round(peso / (pow(altura, 2)), 1)
    if imc < 18.5:
        classifica = "Magreza"
    elif imc >= 18.5 and imc < 25:
        classifica = "Adequado"
    elif imc >= 25 and imc < 30:
        classifica = "Pré-obeso"
    else:
        classifica = "Obeso"
    return [imc, classifica]

# Usa a altura default (1.70m)
print(calcula_imc(peso = 80))
```

---
# Argumentos especiais

+ O parâmetro *args permite passar um número indeterminado de argumentos posicionais organizados como uma tuple.

```{python}
def calcula_imc(*args):
    peso = args[0]
    altura = args[1]
    imc = round(peso / (pow(altura, 2)), 1)
    return imc

# Passando múltiplos argumentos por posição
print("IMC:", calcula_imc(80, 1.80, "argumento extra ignorado"))
```


---
# Argumentos especiais

+ O parâmetro **kwargs recebe um número arbitrário de argumentos nomeados estruturados em um dicionário (`dict`).

```{python}
def calcula_imc(**kwargs):
    peso = kwargs['peso']
    altura = kwargs['altura']
    imc = round(peso / (pow(altura, 2)), 1)
    return imc

# Passando argumentos nomeados
print("IMC:", calcula_imc(peso = 80, altura = 1.80))
```

---
# Tratamento de exceções

+ Controla erros e entradas inadequadas fornecidas pelo usuário.

```{python}
def calcula_imc_seguro(peso, altura):
    try:
        if peso <= 0 or altura <= 0:
            raise ValueError("Peso e altura devem ser maiores que zero!")
    except ValueError as ve:
        print("Erro capturado:", ve)
    else:
        imc = round(peso / (pow(altura, 2)), 1)
        return imc

# Testando entradas válidas e inválidas
calcula_imc_seguro(peso = -90, altura = 1.90)
print("IMC válido:", calcula_imc_seguro(peso = 90, altura = 1.90))
```

---
<!-- class: middle section -->
# Módulos e Importação

---
# Importação básica e alias

+ Módulos são arquivos contendo definições e funções reusáveis.

```{python}
# Importação completa do módulo 'os'
import os
print("CPUs disponíveis:", os.cpu_count())

# Importação com apelido (alias)
import numpy as np
print("Sequência Numpy:", np.linspace(0, 5, 3))
```

+ Importação Específica (from ... import)

```{python}
from os import cpu_count

# Chamada direta sem necessidade do prefixo os.
print("Número de CPUs:", cpu_count())
```

+ **Atenção:** Evite usar from modulo import *, pois pode sobrescrever variáveis e comprometer a legibilidade do código.

---
# Criando um módulo

+ Você pode salvar suas funções em um arquivo .py (ex: `gauss.py`) e importá-lo em outros scripts:

```{python eval = FALSE}
# Conteúdo do arquivo gauss.py
from math import sqrt, pi, exp, pow

def dnorm(y, mu = 0, sigma2 = 1):
    output = (1 / (sqrt(2 * pi * sigma2))) * exp(-(1 / (2 * sigma2)) * pow(y - mu, 2))
    return output
```

+ Para usar no script principal:

```{python, eval = FALSE}
import gauss

# Chama a função criada no módulo próprio
gauss.dnorm(0)
```


---
<!-- class: middle section -->
# Gerenciamento de ambientes e pacotes

---
# O que é o Conda?

.pull-left[
  * Conda é um gerenciador de pacotes e ambientes virtuais.
  * Desenvolvido originalmente para o ecossistema Anaconda.
  * Permite instalar bibliotecas Python, R e dependências do sistema.
  * Resolve automaticamente conflitos entre versões de pacotes.
  * Facilita a reprodução de projetos em diferentes computadores.
  * Amplamente utilizado em Ciência de Dados e Machine Learning.
  * https://docs.conda.io/
]

.pull-right[
```{r, echo = FALSE, out.width = "800px"}
knitr::include_graphics("./img/Conda.png")
```
]

---
# Por que utilizar ambientes virtuais?

.pull-left[
  * Evitam conflitos entre projetos.
  * Permitem utilizar versões diferentes da mesma biblioteca.
  * Facilitam o compartilhamento de projetos.
  * Melhoram a reprodutibilidade das análises.
  * Cada ambiente possui suas próprias bibliotecas.

Exemplo:

Projeto A
- pandas 2.3
- scikit-learn 1.7

Projeto B
- pandas 1.5
- scikit-learn 1.2
]

.pull-right[
```text
Computador

├── ambiente_a
│   ├── pandas 2.3
│   └── sklearn 1.7
│
└── ambiente_b
    ├── pandas 1.5
    └── sklearn 1.2
```
]


---
# Criando um ambiente virtual

.pull-left[

+ Criar um ambiente:

```bash
conda create \
    --name ds_env
```

+ Criar especificando a versão do Python:

```bash
conda create \
    --name ds_env \
    python=3.12
```

+ Criar já instalando pacotes:

```bash
conda create \
    --name ds_env \
    python=3.12 \
    pandas numpy
```

]

.pull-right[

Fluxo típico:

1. Criar ambiente
2. Ativar ambiente
3. Instalar bibliotecas
4. Trabalhar no projeto

]

---
# Ativando e desativando ambientes

.pull-left[

+ Ativar ambiente:

```bash
conda activate ds_env
```

+ Desativar ambiente:

```bash
conda deactivate
```

+ Ver o ambiente ativo:

```bash
conda info
```

]

.pull-right[

Exemplo:

```text
(base)

conda activate ds_env

(ds_env)
```

O nome do ambiente aparece no prompt do terminal.

]

---
# Instalando pacotes

.pull-left[

Instalar um pacote:

```bash
conda install pandas
```

Instalar vários pacotes:

```bash
conda install \
    pandas \
    numpy \
    matplotlib
```

Instalar uma versão específica:

```bash
conda install pandas=2.3
```

]

.pull-right[

Pacotes comuns:

```bash
conda install pandas
conda install numpy
conda install scipy
conda install matplotlib
conda install seaborn
conda install scikit-learn
conda install jupyter
```

]

---
# Canais de distribuição

.pull-left[

+ Pacotes são obtidos de repositórios chamados canais.

+ Canal padrão:

```bash
defaults
```

+ Canal da comunidade:

```bash
conda-forge
```

]

.pull-right[

+ Instalar usando o `conda-forge`:

```bash
conda install \
  -c conda-forge \
  plotnine
```

+ Adicionar permanentemente:

```bash
conda config \
  --add channels \
  conda-forge
```

]


---
# Listando e removendo pacotes

.pull-left[

+ Listar pacotes:

```bash
conda list
```

+ Buscar pacote:

```bash
conda search pandas
```

+ Atualizar pacote:

```bash
conda update pandas
```

]

.pull-right[

+ Remover pacote:

```bash
conda remove pandas
```

+ Remover ambiente:

```bash
conda env remove \
  --name ds_env
```

]


---
# Exportando ambientes

.pull-left[

+ Exportar ambiente:

```bash
conda env export \
 > environment.yml
```

+ Arquivo gerado:

```yaml
name: ds_env
dependencies:
  - python=3.12
  - pandas
  - numpy
```

]

.pull-right[

+ Recriar ambiente:

```bash
conda env create \
 -f environment.yml
```

Vantagem:

* Reproduz exatamente o ambiente de desenvolvimento.
* Essencial para projetos colaborativos.

]


---
# Conda e pip

.pull-left[

## Conda:

* Gerencia ambientes.
* Instala bibliotecas.
* Instala dependências do sistema.
* Resolve conflitos automaticamente.

]

.pull-right[

## Pip:

* Instala pacotes Python.
* Usa o repositório PyPI.

Pode-se usar ambos:

```bash
conda install pandas

pip install openai
```

]

---
# Fluxo recomendado

.pull-left[

```text
Criar Ambiente
       ↓
Ativar Ambiente
       ↓
Instalar Pacotes
       ↓
Desenvolver Projeto
       ↓
Exportar environment.yml
       ↓
Compartilhar Projeto
```
]


.pull-right[
+ Boas práticas:
  + Um ambiente por projeto
  + Nomear ambientes adequadamente
  + Exportar o ambiente regularmente
  + Preferir conda-forge para pacotes científicos
]

---
# Revisão

+ Estrutura básica da linguagem.
+ Diretório e área de trabalho. 
+ Atribuição de objetos.
+ Operadores aritméticos, trigonométricos, relacionais e lógicos. 
+ Tipos de dados (int, float, complex, bool, str e métodos).
+ Estruturas de dados (tuple, range, list, dict, set e fatiamento).
+ Estruturas de controle (if/elif/else) e repetição (for, while, list comprehensions). 
+ Funções (parâmetros, valores default, *args, **kwargs, tratamento de exceções com try/except). 
+ Módulos e importação (import, as, from ... import).
+ Criação de módulos.
+ Gestão de ambientes e pacotes com `conda`.

---
# Exercícios 

.pull-left[
## Conversor de Temperatura com Validação Lógica

Escreva uma função chamada celsius_para_fahrenheit que receba uma temperatura em graus Celsius e a converta para Fahrenheit. A fórmula matemática é:

𝐹=𝐶×1.8+32

Se o usuário passar um valor que não seja numérico, a função deve interromper a execução e exibir uma mensagem amigável de erro usando a função stop().
]


.pull-right[
## Cálculo do Índice de Massa Corporal (IMC) com Classificação

Crie uma função chamada calcular_imc que receba o peso (em kg) e a altura (em metros) de uma pessoa. A função deve calcular o IMC através da fórmula:

$$IMC = \frac{peso}{altura}^2$$

Em seguida, ela deve retornar uma string classificando o indivíduo de acordo com as regras:

    Se IMC < 18.5: “Abaixo do peso”;
    Se IMC >= 18.5 e IMC < 25: “Peso normal”;
    Se IMC >= 25: “Sobrepeso”.

]



---
# Solução

```{python, eval=FALSE}
def celsius_para_fahrenheit(celsius):
    """
    Converte temperatura de Celsius para Fahrenheit.
    """
    
    # Validação de tipo
    if not isinstance(celsius, (int, float)):
        raise TypeError(
            "Erro: A entrada fornecida deve ser um valor numérico!"
        )

    # Aplicação da fórmula
    fahrenheit = (celsius * 1.8) + 32

    # Retorno do resultado
    return fahrenheit


# --- Testes da Função ---
print(celsius_para_fahrenheit(25))  # 77.0
print(celsius_para_fahrenheit(0))   # 32.0

# Descomente para testar o erro
# print(celsius_para_fahrenheit("vinte"))
```


---
# Solução

```{python, eval = FALSE}
def calcular_imc(peso, altura):
    # Cálculo do IMC
    imc = peso / (altura ** 2)

    # Estrutura condicional
    if imc < 18.5:
        classificacao = "Abaixo do peso"
    elif imc < 25:
        classificacao = "Peso normal"
    else:
        classificacao = "Sobrepeso"

    # Construção da mensagem de saída
    resultado_texto = (
        f"IMC: {imc:.2f} -> Classificação: {classificacao}"
    )

    return resultado_texto


# --- Testes da Função ---
print(calcular_imc(peso=70, altura=1.75))
print(calcular_imc(peso=90, altura=1.80))
```


