Pular para conteúdo

Conteúdo do Curso de Python⚓︎

🔜 ❎ Seção 1 - Informações e avisos importantes + Boas vindas⚓︎


❎ 1 - Informações e avisos importantes + Boas vindas

❎ 2 - Assista antes de prosseguir com as aulas (atualizando 20/12/2022)


🔜 ❎ Seção 2 - Python + VS Code: Preparando meu ambiente de desenvolvimento⚓︎


❎ 3 - Introdução a seção

❎ 4 - Ubuntu 22 - Instalação básica do Python e o VS Code

sudo apt update -y
sudo apt upgrade -y
sudo apt install git curl build-essential -y
sudo apt install gcc make default-libmysql client-dev libssl-dev -y
sudo apt install python3.10-full python3.10-dev -y

cd Area de Trabalho
mkdir projeto
cd projeto
ls
python --version
python3 --version
python3 -V
python3 -m venv venv
source venv/bin/activate
python -v
which python
python -m pip install pip --update
# baixar o instalador do Vs Code no site da microsoft
sudo dpkg -i nome do arquivo
rm -E projeto

❎ 5 - Ubuntu 22 - Instalação Completa do Python e do VS Code (pyenv e zsh)

# Executar comandos a seguir para atualizar os pacotes

sudo apt update -y
sudo apt upgrade -y

# Só o Python

sudo apt install python3.10-full python3.10-dev -y

# Instalar pacotes a seguir

sudo apt install git curl build-essential dkms perl wget -y
sudo apt install gcc make default-libmysqlclient-dev libssl-dev -y
sudo apt install -y zlib1g-dev libbz2-dev libreadline-dev libsqlite3-dev llvm \
libncurses5-dev libncursesw5-dev \
xz-utils tk-dev libffi-dev liblzma-dev python3-openssl git

# Pyenv

curl -L <https://github.com/pyenv/pyenv-installer/raw/master/bin/pyenv-installer> | bash

# Seguir instruções do Pyenv

# Baixar e instalar VS Code: <https://code.visualstudio.com/download>

# Abaixo tudo é opcional

# Instalar e configurar ZSH

sudo apt install zsh -y
chsh -s /bin/zsh
zsh

# Instalar Oh-my-zsh! -> <https://ohmyz.sh/>

sh -c "$(curl -fsSL <https://raw.github.com/ohmyzsh/ohmyzsh/master/tools/install.sh>)"

# Instalar Spaceship Prompt

# <https://github.com/spaceship-prompt/spaceship-prompt>

git clone <https://github.com/spaceship-prompt/spaceship-prompt.git> "$ZSH_CUSTOM/themes/spaceship-prompt" --depth=1
ln -s "$ZSH_CUSTOM/themes/spaceship-prompt/spaceship.zsh-theme" "$ZSH_CUSTOM/themes/spaceship.zsh-theme"

# Mudar ~/.zshrc -> ZSH_THEME="spaceship"

# Instalar Zsh Autosuggestions

# <https://github.com/zsh-users/zsh-autosuggestions>

git clone <https://github.com/zsh-users/zsh-autosuggestions> ${ZSH_CUSTOM:-~/.oh-my-zsh/custom}/plugins/zsh-autosuggestions

# Instalar Zsh Syntax Highlighting

# <https://github.com/zsh-users/zsh-syntax-highlighting>

git clone <https://github.com/zsh-users/zsh-syntax-highlighting.git> ${ZSH_CUSTOM:-~/.oh-my-zsh/custom}/plugins/zsh-syntax-highlighting

# Mudar plugins

# plugins=(git zsh-autosuggestions zsh-syntax-highlighting)

# Font optional (<https://github.com/pdf/ubuntu-mono-powerline-ttf>)

mkdir -p ~/.fonts
git clone <https://github.com/pdf/ubuntu-mono-powerline-ttf.git> ~/.fonts/ubuntu-mono-powerline-ttf
fc-cache -vf

❎ 6 - Windows 11 - Instalando o Python e o VS Code

❎ 7 - macOS - Instalando o Python e o VS Code

/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"

❎ 8 - Para iniciantes: sobre as próximas aulas de configurações e instalações

❎ 9 - Para iniciantes: configurações VS Code (Parte 1)

❎ 10 - Para iniciantes: configurações VS Code (Parte 2)

❎ 11 - Para iniciantes: configurações VS Code (Parte 3)

❎ 12 - Para iniciantes: VS Code em Linux e Mac OS (assista mesmo se estiver em Windows)


🔜 ❎ Seção 3 Iniciando na programação com Python (Lógica de programação básica)⚓︎


❎ 13 - o que vamos aprender? Devo seguir essa seção?

❎ 14 - Me ajude a produzir conteúdo gratis

❎ 15 - Criando meu primeiro módulo Python(*.py)

❎ 16 - o interpretador do Python + comentários de código

"""
DocString
E escrever o que eu
quiser
asdfasdfd
"""

''' Usar para escrever suas notas '''

# Permite escrever um comentário

print(123)  # Na frente

# Abaixo

print(456)

❎ 17 - Docstring como comentários

🎀 Conectado a aula anterior

❎ 17.1 - Teste 1 - Sobre cometários Questionário 1

❎ 18 - A função print

print(12, 34, 1011, sep="", end='#')
print(56, 78, sep='-', end='\n')
print(9, 10, sep='-', end='\n')

❎ 19 - Tipo str (string) - Introdução aos tipos de dados

"""
DocString
Python = Linguagem de programação
Tipo de tipagem = Dinâmica / Forte
str -> string -> texto
Strings são textos que estão dentro de aspas
"""

print(1234)

# Aspas simples
print('Luiz Otávio')
print(1, 'Luiz "Otávio"')

# Aspas duplas
print("Luiz Otávio")
print(2, "Luiz 'Otávio'")

# Escape
print("Luiz \"Otávio\"")

# r
print(r"Luiz \"Otávio\"")

❎ 19.1 - Teste 2 - Sobre print e str Questionário 2

❎ 20 - Tipo int e float(números) - Introdução aos tipos de dados

# Tipos int e float
# int -> Número inteiro
# O tipo int representa qualquer número
# positivo ou negativo. int sem sinal é considerado
# positivo.
# print(11) # int
# print(-11) # int
# print(0)

# float -> Número com ponto flutuante
# O tipo float representa qualquer número
# positivo ou negativo com ponto flutuante.
# float sem sinal é considerado positivo.
# print(1.1, 10.11)
# print(0.0, -1.5)

# A função type mostra o tipo que o Python
# inferiu ao valor.

print(type('Otávio'))
print(type(0))
print(type(1.1), type(-1.1), type(0.0))

❎ 21 - Tipo bool(boolean) - introdução ao tipos de dados

# Tipo de dado bool (boolean)
# Ao questionar algo em um programa,
# só existem duas respostas possíveis:
# sim (True) ou não (False).
# Existem vários operadores para "questionar".
# Dentre eles o ==, que é um operador lógico que
# questiona se um valor é igual a outro

print(10 == 10)  # Sim => True (Verdadeiro)
print(10 == 11)  # Não => False (Falso)
print(type(True))
print(type(False))
print(type(10 == 10))
print(type(10 == 11))

❎ 21.1 - Teste 3 - Sobre int, float e bool Questionário 3

❎ 22 - Coerção de tipos (convertendo um tipo para outro)

# conversão de tipos, coerção
# type convertion, typecasting, coercion
# é o ato de converter um tipo em outro
#  tipos imutáveis e primitivos:
# str, int, float, bool

print(int('1'), type(int('1')))
print(type(float('1') + 1))
print(bool(' '))
print(str(11) + 'b')

❎ 23 - Introdução às variáveis em Python

# Variáveis são usadas para salvar algo na memória do computador.
# PEP8: inicie variáveis com letras minúsculas, pode usar
# números e underline _.
# O sinal de = é o operador de atribuição. Ele é usado para
# atribuir um valor a um nome (variável).
# Uso: nome_variável = expressão

# nome_completo = 'Luiz Otávio Miranda'
# soma_dois_mais_dois = 2 + 2
# int_um = bool('1')
# print(int_um, type(int_um))
# print(nome_completo, soma_dois_mais_dois)

nome = 'Luiz'
idade = 17
maior_de_idade = idade >= 18
print('Nome:', nome, 'Idade:', idade)
print('É maior?', maior_de_idade)

❎ 24 - Execícios com variáveis e tipos de dados

📍 Resposta Exercício

nome='Lucas'
sobrenome='Cadastro'
idade='10'
ano_nascimento= 2023 - idade
maior_de_idade= idade >= 18
altura_metros= 1.63

print('Nome:',nome)
print('Sobrenome:',sobrenome)
print('Idade:',idade)
print('Ano de Nascimento:',ano_nascimento)
print('É Maior de Idade?:',maior_de_idade)
print('Altura em Metros:',altura_metros)

❎ 25 - Solução - exercícios com variáveis e tipos de dados

🎓 Resolução do Professor

nome = 'Luiz Otávio'
sobrenome = 'Miranda'
idade = 18
ano_nascimento = 2022 - idade
maior_de_idade = idade >= 18
altura_metros = 1.80

print('Nome:', nome)
print('Sobrenome:', sobrenome)
print('Idade:', idade)
print('Ano de nascimento:', ano_nascimento)
print('É maior de idade?', maior_de_idade)
print('Altura em metros:', altura_metros)

❎ 26 - Introdução aos operadores aritméticos(matemática)

adicao = 10 + 10
print('Adição', adicao)

subtracao = 10 - 5
print('Subtração', subtracao)

multiplicacao = 10 * 10
print('Multiplicação', multiplicacao)

divisao = 10 / 3  # float
print('Divisão', divisao)

divisao_inteira = 10 // 3
print('Divisão inteira', divisao_inteira)

exponenciacao = 2 ** 10
print('Exponenciação', exponenciacao)

modulo = 55 % 2  # resto da divisão
print('Módulo', modulo)

print(10 % 8 == 0)
print(16 % 8 == 0)
print(10 % 2 == 0)
print(15 % 2 == 0)
print(16 % 2 == 0)

❎ 27 - Concatenação (+) e repetição (*) com operadores aritméticos

concatenacao = 'Luiz' + ' ' + 'Otávio'
print(concatenacao)

a_dez_vezes = 'A' * 10
tres_vezes_luiz = 3 * 'Luiz'
print(a_dez_vezes)
print(tres_vezes_luiz)

❎ 28 - Precedência entre os operadores aritméticos

# 1. (n + n)
# 2. **
# 3. * / // %
# 4. + -
conta_1 = (1 + int(0.5 + 0.5)) ** (5 + 5)
print(conta_1)

❎ 29 - Exercício de programação - Cálculo de IMC (Índice de Massa Corpórea) + Elipsis

📍 Resposta Exercício

nome = 'Edgar Soares'
altura = 1.63
peso = 75
'''
O IMC é reconhecido como padrão internacional para avaliar o grau de sobrepeso 
e obesidade. É calculado dividindo o peso (em kg) pela altura ao quadrado 
(em metros).

-> IMC = Peso ÷ ( Altura × Altura )
    or
-> IMC = Peso ÷ Altura² [ Altura**2 ]

Exemplo de como calcular o IMC:

-> IMC = 80 kg ÷ (1,80 m × 1,80 m) = 24,69 kg/m2 (Peso ideal)

'''

imc = peso / ( altura * altura )

print(nome,'tem', altura, 'de altura,')
print('pesa', peso, 'KG e seu IMC é')
print(imc)

❎ 30 - Solução exercícios de programação - Cálculo do IMC

🎓 Resolução do Professor

nome = 'Luiz Otávio'
altura = 1.80
peso = 95
imc = peso / altura ** 2

print(nome, 'tem', altura, 'de altura,',)
print('pesa', peso, 'quilos e seu imc é',)
print(imc)

# Luiz Otávio tem 1.80 de altura,
# pesa 95 quilos e seu IMC é
# 29.320987654320987

❎ 31 - Uma introdução às f-strings (formatação de strings)

nome = 'Luiz Otávio'
altura = 1.80
peso = 95
imc = peso / altura ** 2

"f-strings"
linha_1 = f'{nome} tem {altura:.2f} de altura,'
linha_2 = f'pesa {peso} quilos e seu imc é'
linha_3 = f'{imc:.2f}'

print(linha_1)
print(linha_2)
print(linha_3)

# Luiz Otávio tem 1.80 de altura,
# pesa 95 quilos e seu IMC é
# 29.320987654320987

❎ 32 - formatação de strings como o método format

a = 'AAAAA'
b = 'BBBBBB'
c = 1.1
string = 'b={nome_2} a={nome_1} a={nome_1} c={nome_3:.2f}'
formato = string.format(
  nome_1 = a,
  nome_2 = b,
  nome_3 = c
)

print(formato)

❎ 32.1 - Teste 4 - Teste seus conhecimentos Questionário 4

❎ 33 - Usando a função input para coletar dados de usuário

# nome = input('Qual o seu nome? ')
# print(f'O seu nome é {nome}')

numero_1 = input('Digite um número: ')
numero_2 = input('Digite outro número: ')

int_numero_1 = int(numero_1)
int_numero_2 = int(numero_2)

print(f'A soma dos números é: {int_numero_1 + int_numero_2}')

❎ 34 - Introdução aos blocos de código + if / elif / else (condicionais)

# if / elif      / else
# se / se não se / se não

entrada = input('Você quer "entrar" ou "sair"? ')

if entrada == 'entrar':
  print('Você entrou no sistema')

  print(12341234)
elif entrada == 'sair':
  print('Você saiu do sistema')
else:
  print('Você não digitou nem entrar e nem sair.')

print('FORA DOS BLOCOS')

❎ 35 - if, elif e else: entendendo o fluxo do interpretador em condicionais

# if / ellif se / else
# se / não se / se não

condicao_1 = True
condicao_2 = True
condicao_3 = True
condicao_4 = True

if condicao_1:
print('Código para condição 1')
print('Código para condição 1')
elif condicao_2:
print('Código para condição 2')
elif condicao_3:
print('Código para condição 3')
elif condicao_4:
print('Código para condição 4')

if 10 == 10
print(' Outro if ')



print('FORA DO IF')

❎ 36 - O Debugger do Vs Code e interpretador do Python lendo os códigos

❎ 37 - Operadores relacionais (de comparação) em Python

"""


| ->  OP                                          | Significado    | Exemplo (True) |
| ----------------------------------------------- |
| ->  >                                           | Maior          | 2 > 1          |
| ----------------------------------------------- |
| -> >=                                           | maior ou igual | 2 >= 2         |
| ----------------------------------------------- |
| ->  <                                           | menor ou igual | 1 < 2          |
| ----------------------------------------------- |
| ->  <=                                          | menor ou igual | 2 <= 2         |
| ----------------------------------------------- |
| ->  ==                                          | igual          | 'a' == 'a'     |
| ----------------------------------------------  |
| ->  !=                                          | diferente      | 'a' != 'b'     |
| ----------------------------------------------- |

"""

maior = 2 > 1
maior_ou_igual = 2 >= 2
menor = 1 < 2
menor_ou_igual = 2 <= 2
igual = 'a' == 'a'
diferente = 'a' != 'b'

print('olha meu print aqui')

❎ 38 - Exercício de programação de if e comparação

📍 Resposta Exercício

primeiro_valor = input('digite um valor: ')

segundo_valor = input('digite outro valor: ')

if primeiro_valor >= segundo_valor:
print(f'O valor {primeiro_valor=} é maior ou igual valor {segundo_valor=}')
else:
print(f'O valor {segundo_valor=} é maior que valor {segundo_valor=}')

❎ 39 - Solução - Exercício de programação com if e comparação

🎓 Resolução do Professor

primeiro_valor = input('Digite um valor: ')
segundo_valor = input('Digite outro valor: ')

if primeiro_valor >= segundo_valor:
  print(
      f'{primeiro_valor=} é maior ou igual '
      f'ao que {segundo_valor=}'
  )
else:
  print(
      f'{segundo_valor=} é maior '
      f'do que {primeiro_valor=}'
  )

❎ 40 - Operador lógica "and"

"""
->  Operadores lógicos
----------------------------------------
-> and (e) -> or (ou) -> not (não)
----------------------------------------
-> and - Todos as condições precisam ser
verdadeiras.
Se qualquer valor for considerado falso
a expressão inteira será avaliada
naquele valor

->  São considerados falsy ( que vc já viu)

=> 0
=>0.0
=>''
=>false


->  Também exite o tipo de None que é
usado para representar um não valor
----------------------------------------

-> Avaliação de curto circuito
"""

entrada = input('[E]ntrar [S]air:')

senha_digitada = input('Senha:')

senha_permitida = '123456'

if entrar == 'E' and senha_permitida == senha_digitada:
  print('Entrar')
else:
  print('Sair')

print(True and False and True)
print(True and 0 and True)

❎ 40.1 - Teste 5 - Sobre and Questionário 5

❎ 41 - Operador lógico "or"

"""
->  Operadores lógicos
----------------------------------------
-> and (e) -> or (ou) -> not (não)
----------------------------------------
-> or - Qualquer condição verdadeira avalia
toda a expressão como verdadeira.

-> Se qualquer valor for considerado verdadeiro,
a expressão inteira será avaliado naquele valor.

->  São considerados falsy ( que vc já viu)

=> 0
=>0.0
=>''
=>false

->  Também exite o tipo de None que é
usado para representar um não valor
----------------------------------------

-> Avaliação de curto circuito
"""
entrada = input('[E]ntrar [S]air:')

senha_digitada = input('Senha:')

senha_permitida = '123456'

if (entrar == 'E' or entrada == 'e')  and senha_permitida == senha_digitada:
  print('Entrar')
else:
  print('Sair')

senha = input('senha=') or 'sem senha'
print(senha)

❎ 42 - Operador lógico "not"

'''
-> usado  para inverter expressões

-> not True = False

-> not False = True
'''

senha input('Senha:')

print(not True)  # False
print(not False) # True

❎ 43 - Operadores in e not in

'''
-> strings são iteráveis

-> 0 1 2 3 4 5

-> o t á v i o

-> -6-5-4-3-2-1
'''

nome = 'Otávio'

print(nome[2])
print(nome[-4])
print('vio' in nome)
print('zero' in nome)
print(10 * '-')
print('vio' not int nome)
print('zero' not int nome)

nome =  input('Digite seu nome:')
encontrar = ('Digite o que deseja encontrar:')

if encontrar in nome:
print(f'{encontrar} está em {nome}')
else:
print(f'{encontrar} não está me {nome}')

❎ 43.1 - Teste 6 seu conhecimento Questionário 6

❎ 44 - Interpolação de string com % Python

'''
-> s - string

-> d e i - int

-> f - float

-> x e X *Hexadecimal* (ABCDEF0123456789)

-> A = 10
-> B = 11
-> C = 12
-> D = 13
-> E = 14
-> F = 15
'''

nome = 'Luiz'
preco= 1000.95897643
variavel = '%s, preço é R$.2f' % (nome, preco)
print(variavel)
print('O hexadecimal de %d é %08X' % (1500, 1500))

❎ 45 - Formatação de string com f-strings

'''

-> s - string

-> d - int

-> f - float

-> . <número de dígito> f

-> x ou X Hexadecimal

-> (Caractere)( >< ^ )(quantidade)

-> > - Esquerda

-> < - Direita

-> ^ - Centro

-> - Força o número a aparecer antes dos zeros

-> Sinal - + ou -

Ex.: 0>-100,.1f

-> Coversion flags - !r __repr__ !s __str__   !a
'''

variavel = 'ABC'

print(f'{variavel}')
print(f'{variavel: >10}')
print(f'{variavel: <10}.')
print(f'{variavel: ^10}.')
print(f'{1000.4873648123746:0=+10,.1f}')
print(f'O hexadecimal de 1500 é {1500:08X}')
print(f'{variavel!r}')

❎ 46 - Fatiamento de strings e a função len

'''
-> Fatiamento de strings
-------------------------
-> 012345678

-> olá mundo

-> -987654321

-> fatiamento [i<início>:f<fim>:p<passo>] [::]

obs.: a função len retorna a quantidade
'''

variavel = 'Olá mundo'

print(variavel[::-1])
print(len(variavel))

❎ 47 - Exercícios: teste seu conhecimento até aqui

📍 Resposta Exercício

'''
-> Exercício
-----------------
-> Peça ao usuário para digitar seu nome

-> Peça ao usuário digitar sua idade

-> Se nome e idade forem digitados :

-> Exiba:
  x  Seu nome é {nome}
  x  Seu nome invertido é {nome invertido}
  x  Seu nome contém (ou não espaços)
  x  Seu nome tem [n] letras
  X  A primeira letra do seu nome é {letra}
  X  A última letra do seu nome é {letra}

-> Se nada for digitado em nome ou idade

Exiba:
  'Desculpe, você deixou o campo vazio'

'''
nome = input('Digite o seu nome: ')
idade = input('digite a sua idade:')


if nome and idade:
print('Desculpe, você deixou o campo vazio' )
else:
print(f'Seu nome é {nome=}')
print(f'Seu nome invertido é {nome[::-1]}')
if ' ' in nome:
  print('Seu nome tem espaços')
else:
  print('Seu nome não tem espaços')

print(f'o seu nome tem {len(nome)} letras')

print(f'A primeira letra do seu nome é {nome[0]}')
print(f'A última letra do seu nome é {nome[-1]}')

❎ 48 - Solução - Exercícios: teste seu conhecimento até aqui

🎓 Resolução do Professor

nome = input('Digite o seu nome: ')
idade = input('Digite sua idade: ')

if nome and idade:
print(f'Seu nome é {nome}')
print(f'Seu nome invertido é {nome[::-1]}')

if ' ' in nome:
    print('Seu nome contém espaços')
else:
    print('Seu nome NÃO contém espaços')

print(f'Seu nome tem {len(nome)} letras')
print(f'A primeira letra do seu nome é {nome[0]}')
print(f'A última letra do seu nome é {nome[-1]}')
else:
print("Desculpe, você deixou campos vazios.")

❎ 49 - Introdução ao try e except para capturar erros (exceptions)

'''
-> Introdução ao try / except
-------------------------------
-> try => tentar executar o código

-> except => ocorreu algum erro ao tente executar
'''

nome_str = input('Vou dobrar o número que vc digitar')

try:
  numero_float = float(numero_str)
  print('FLOAT :', numero_float)
  print(f'O dobro de {numero_str} é {numero_float * 2 :2.f} ')
except:
  print ('Isso não é um numero.')

# ou (checar a logica de outra forma)


if numero_str.isdigit():
  numero_float = float(numero_str)
  print(f'O dobro de {numero_str} é {numero_float * 2 :2.f} ')
else:
  print ('Isso não é um numero.')

❎ 50 - Parte 1: Variáveis constantes e complexidade de código

'''
-> CONSTANTE => 'Variáveis' que não vão mudar

muitas condições no mesmo if (ruim)

<- contagem de complexidade (ruim)
'''

velocidade = 61 #velocidade atual do carro
local_carro = 101 # local em que o carro está na estrada

RODAR_1 = 60 # velocidade máxima do rodar 1
LOCAL_1 = 100 # local onde o radar 1 está
RADAR_RANGE = 1 # A distância onde a rodar pega

❎ 51 - Parte 2: Variáveis constantes e complexidade de código

velocidade = 61 #velocidade atual do carro
local_carro = 101 # local em que o carro está na estrada

RODAR_1 = 60 # velocidade máxima do rodar 1
LOCAL_1 = 100 # local onde o radar 1 está
RADAR_RANGE = 1 # A distância onde a rodar pega

vel_carro_pass_radar_1 = velocidade > RADAR_1
carro_passou_radar_1 = local >= (RADAR_1 - RADAR_RANGE) and \
local_carro <= (RADAR_1 + RADAR_RANGE )
carro_multado_radar = carro_passou_radar_1 and vel_pass_radar_1

if vel_carro_pass_rodar_1:
print('Velocidade carro possou do radar 1')

if carro_passou_radar_1:
print('Carro passou radar 1')

if carro_multado_radar_1:
print('carro multado em rodar 1')

❎ 52 - id - A identidade do valor que está na memória

'''
flag (Bandeira) - Marcar um local

None = Não valor

is e is not = é ou não (tipo, valor, identidade)
'''

v_1 = 'a'

print(id(v_1))

# condicao = False

if condicao:
print('Faça algo')
else:
print('Não faça algo')

❎ 53 - Flag, is, is not e None

'''
->  flag (Bandeira) - Marcar um local

->  None = Não valor

->  is e is not = é ou não (tipo, valor, identidade)

->  id = Identidade
'''

condicao = False
passou_no_if = None

if condicao:
passou_no_if = True
print('Faça algo')
else:
print('Não faça algo')

if passou_no_if is None:
print('Não passou no if')
else:
print('Passou no if')

❎ 54 - Exercícios - Enunciados

📍 Resposta Exercício

'''
1-
-> Faça um programa que que peça ao usuário para digitar um número inteiro,
informe se este número é par ou impar.

-> Caso o usuário não digite um número inteiro, informe
     que não é um número inteiro
'''

try:
numero_str = input('digite  um número inteiro = ')
numero_int = float(numero_str)

if (numero_int % 2) == 0:
print('este Número é par')
else:
print('este Número é impar')
except:
print('não foi digitado um número inteiro')

'''
2-
-> Faça um programa que pergunte a hora ao usuário e , 
    baseando-se no horário descrito exiba a saudação apropriada.
  Ex.:
  Bom dia 0-11, boa tarde 12-17 e boa noite 18-23
'''

try
hora_str =  input('que horas são? ')
hora_int = int(hora_str)

if hora_int >= 0 and hora_int <= 11:
  print('Bom dia!')
elif hora_int >= 12 and hora_int <= 17:
  print('Boa Tarde!')
elif hora_int >= 18 and hora_int <= 23:
  print('Boa noite!')
except:
print('Não corresponde a uma hora válida')

'''
3-
-> Faça um programa que peça o primero nome do usuário.

-> Se o nome tiver 4 letras ou menos escreva:
  "Seu nome é curto ";

-> Se tiver entre 5 e 6 letras, escreva:
  " Seu nome é normal";

-> Maior que 6 escreva:
  " Seu nome é muito grande"
'''

primero_nome = input('Digite o seu primero nome')

if len(primero_nome) >= 1 and len(primero_nome) <= 4:
print("Seu nome é curto!" )
elif len(primero_nome) >= 5 and len(primero_nome) <= 6:
print("Seu nome é normal!")
elif  len(primero_nome) > 6:
print("Seu nome é muito grande!")

❎ 55 - Solução 1 dos Exercícios - Enunciados

🎓 Resolução do Professor

"""
1-
->  informe se este número é par ou ímpar. Caso o usuário não digite um número
    inteiro, informe que não é um número inteiro.
"""
entrada = input('Digite um número: ')

# if entrada.isdigit():
#     entrada_int = int(entrada)
#     par_impar = entrada_int % 2 == 0
#     par_impar_texto = 'ímpar'

#     if par_impar:
#         par_impar_texto = 'par'

#     print(f'O número {entrada_int} é {par_impar_texto}')
# else:
#     print('Você não digitou um número inteiro')

try:
  entrada_int = float(entrada)
  par_impar = entrada_int % 2 == 0
  par_impar_texto = 'ímpar'

  if par_impar:
      par_impar_texto = 'par'

  print(f'O número {entrada_int} é {par_impar_texto}')
except:
  print('Você não digitou um número inteiro')

❎ 56 - Solução 2 dos Exercícios - Enunciados

🎓 Resolução do Professor

"""
2-
->  Faça um programa que pergunte a hora ao usuário e, baseando-se no horário
  descrito, exiba a saudação apropriada. Ex.
  Bom dia 0-11, Boa tarde 12-17 e Boa noite 18-23.
"""

entrada = input('Digite a hora em números inteiros: ')

try:
hora = int(entrada)

if hora >= 0 and hora <= 11:
    print('Bom dia')
elif hora >= 12 and hora <= 17:
    print('Bom tarde')
elif hora >= 18 and hora <= 23:
    print('Bom noite')
else:
    print('Não conheço essa hora')
except:
print('Por favor, digite apenas números inteiros')

❎ 57 - Solução 3 dos Exercícios - Enunciados

🎓 Resolução do Professor

"""
3-
->  Faça um programa que peça o primeiro nome do usuário. Se o nome tiver 4 letras ou
menos escreva "Seu nome é curto"; se tiver entre 5 e 6 letras, escreva
"Seu nome é normal"; maior que 6 escreva "Seu nome é muito grande".
"""

nome = input('Digite seu nome: ')
tamanho_nome = len(nome)

if tamanho_nome > 1:
if tamanho_nome <= 4:
    print('Seu nome é curto')
elif tamanho_nome >= 5 and tamanho_nome <= 6:
    print('Seu nome é normal')
else:
    print('Seu nome é muito grande')
else:
print('Digite mais de uma letra.')

❎ 58 - Conversa - tipos built-in, documentação, tipo imutáveis, métodos de string

'''
link da documentação --> https://docs.python.org/pt-br/3/library/stdtypes.html
----------------------------------------------------------------------------

-> Imutáveis que vimos:

-> str
-> int
-> float
-> bool
'''

string = '1000'

outra_variavel = f'{string[:3]} ABC {string[4:]}'

print(string)
print(outra_variavel)

print(string.zfill(10))

❎ 59 - white e break - Estrutura de repetição (Parte 1)

'''
-> Repetição
--------------
-> while (enquanto)

-> Executa uma ação enquanto uma condição for verdadeira
  loop infinito -> Quando um código não tem fim
'''

condicao = True

while condicao:
nome = input('Qual o seu nome: ')

print(f'seu nome é {nome}')

if nome == 'sair'
break

print('Acabou')

❎ 60 - while - Condição em detalhes

'''
-> Repetição
--------------
-> while (enquanto)

-> Executa uma ação enquanto uma condição for verdadeira
    loop infinito -> Quando um código não tem fim
'''
contador

while contador <= 10
contador = contador + 1

# contador +=1

print(contador)

print('Acabou')

❎ 61 - Operadores de atribuição com operadores aritméticos

'''
-> Operadores de atribuição
-----------------------------

->  =

-> +=

-> -=

-> *=

-> /=

-> //=

-> **=

-> %=
'''

contador = 10

contador /= 5

print(contador)

❎ 62 - while + continue - pulando algumas repetição

'''
-> Repetição
    --------------
    -> while (enquanto)

    -> Executa uma ação enquanto uma condição for verdadeira
        loop infinito -> Quando um código não tem fim
'''

contador = 0

while contador <= 100:
contador += 1

if contador == 6:
  print('Não vou mostrar o 6.')
  continue

if contador >= 10 and contador <=27:
  print(f'Não vou mostrar o {contador}')
  continue

print(contador)

if contador == 40:
  break

print('Acabou')

❎ 63 - while + while (laços internos)

'''
-> Repetição
    --------------
    -> while (enquanto)

    -> Executa uma ação enquanto uma condição for verdadeira
        loop infinito -> Quando um código não tem fim
'''
qtd_linhas = 5
qtd_colunas = 5

linha = 1

while linha <=qtd_linhas:
coluna = 1

while coluna <= qtd_colunas:
  print(f'{linha=} {colunas=}')

  coluna += 1

linha += 1

print('Acabou')

❎ 64 - Exercício guiado com while

📍 Resposta Exercício

'''
-> Iterando strings com while
--------------------------------
'''

#       012345678910
nome = 'Luiz Otávio' # Iterável
#       11109876454321

tamanho_nome = len(nome)

print(nome)
print(tamanho_nome)
print(nome[3])

nova_string = ''

nova_string += '*L*u*i*z* *O*t*á*v*i*o*' # Resultado proposto

letras = 0
asterisco = '*'
while letras < tamanho_nome:
nova_string += asterisco + nome[letras]
letras += 1

nova_string += asterisco

print(nova_string)

❎ 65 - Solução do exercício guiado com while

🎓 Resolução do Professor

"""
->  Iterando strings com while
-------------------------------
"""
#       012345678910
# nome = 'Luiz Otávio'  # Iteráveis
#      1110987654321


nome = 'Maria Helena'  # Iteráveis

indice = 0
novo_nome = ''
while indice < len(nome):
  letra = nome[indice]
  novo_nome += f'*{letra}'
  indice += 1

novo_nome += '*'
print(novo_nome)

❎ 66 - Exercício guiado - Calculadora - Parte 1

📍 Resposta Exercício

'''
-> Calculadora com While
--------------------------
'''

while True:
  numero_1 = input('Digite um número')
  numero_2 = input('Digite outro número')
  operador = input('Digite operadores( + , - , /, * )')

    num_1_float = 0
    num_2_float = 0
    numero_valido = None


  try:
    num_1_float = float(numero_1)
    num_2_float = float(numero_2)
    numero_valido = True
  except:
    numero_valido = None

  if numero_valido is True:
    print('Não digitado um numero válido')
    continue

    operadores_validos = '+-/*'

  if operador in not operadores_validos
    print('Nenhum dos operadores válidos foi digitado!')
    continue

  if len(operador) >= 1:
    print('Digite somente um único operador')
    continue

  print('confira os resultados abaixo')
  if operador == '+':
    print(num_1_float + num_2_float)

  elif operador == '-':
    print(num_1_float - num_2_float')

  elif operador == '/':
    if num_2_float != 0:
      print(num_1_float / num_2_float)

    else:
      print('Não é possível fazer divisão por zero.')

  elif operador == '*':
    print(num_1_float * num_2_float)


  sair = input('Quer sair [s]im').lower().startswith('s')

  if sair is True:
    break

🎓 Resolução do Professor

'''
-> Calculadora com While
--------------------------
'''

while True:
print('nummmmm')



#################

sair = input('Quer sair [s]im').lower().startswith('s')

if sair is True:
  break

❎ 67 - Exercício guiado - Calculadora - Parte 2

🎓 Resolução do Professor

'''
-> Calculadora com While
--------------------------
'''

while True:
  numero_1 = input('Digite um número: ')
  numero_2 = input('Digite outro número: ')
  operador = input('Digite o operador (+-/*): ')

  numeros_validos = None

  try:
      num_1_float = float(numero_1)
      num_2_float = float(numero_2)
      numeros_validos = True
  except Exception:
      numeros_validos = None

  if numeros_validos is None:
      print('Um ou ambos os números digitados são inválidos.')
      continue

  operadores_permitidos = '+-/*'

  if operador not in operadores_permitidos:
      print('Operador inválido.')
      continue

  if len(operador) > 1:
      print('Digite apenas um operador.')
      continue

  ###

  sair = input('Quer sair? [s]im: ').lower().startswith('s')

  if sair is True:
      break

❎ 68 - Exercício guiado - Calculadora - Parte 3

❎ 69 - while / else (recurso peculiar do Python)

'''
->  while / else
--------------------

'''

string = 'valor qualquer'

i = 0
while i < len(string):
letras = string[i]

if letra == ' '
break

print(letra)

i += 1

else:
print('Não encontrei um espaço na string')

print('Fora do while')

❎ 70 - while - Qual letras apareceu mais vezes na frase? (Iterando string com while)

frase = 'aaaooo'

i = 0
qtd_apareceu_mais_vezes = 0
letra_apareceu_mais_vezes = ''

while i < len(frase):
letra_atual = frase[i]

if letra_atual == ' ':
i += 1
continue

qtd_apareceu_mais_vezes_atual = frase.count(letra_atual)

if qtd_apareceu_mais_vezes < qtd_apareceu_mais_vezes_atual:
qtd_apareceu_mais_vezes = qtd_apareceu_mais_vezes_atual
letra_apareceu_mais_vezes = letra_atual

i += 1

print(
'A letra que apareceu mais vezes foi '
f'"{letra_apareceu_mais_vezes}" que apareceu '
f'{qtd_apareceu_mais_vezes}X'
)

❎ 71 - DEBUGGER: while - qual letra apareceu mais vezes na frase?

❎ 71.1 - Teste 7 - Teste Questionário 7

❎ 72 - Introdução ao for / in estrutura de repetição para coisa finitas

'''
senha_salva = '123456'
senha_digitada = ''
repeticoes = 0

while senha_salva != senha_digitada:
senha_digitada = input(f'Sua senha ({repeticoes}X):')

repeticoes += 1

print(repeticoes)
print('Aquele laço acima pode ter repetição infinitas')
'''

texto = 'Python'

novo_texto = ''

for letra in texto:
novo_texto +=f'*{letra}'

print(letra)

print(novo_texto +'*')

❎ 73 - range + for para usar intervalos de números

'''
-> for + Range
---------------
-> range -> (start, stop, step)
'''

numeros = range(0, 100, 8)

for numero in numeros:
print(numero)

❎ 74 - Como o for funciona por baixo dos panos? (next, iter, iterável e iterador)

'''
-> Iterável tipos:
-------------------
-> str

-> range

-> etc

-> (__iter__)

Iterador -> quem sabe entrar um valor por vez

next -> me entrega o próximo valor

iter -> me entrega seu iterador
'''

# for letra in texto

texto = 'Luiz' #iterável

interador = iter(texto) # iterator

while True:
try:
  letra = next()
except StopIteration:
  break

# outra forma

for letra in texto:
print(letra)

❎ 75 - O que aprendemos com while também funciona no for (continue, break, else, etc)

for i in range(10):
if i == 2:
print('i é 2, pulando...')
continue

if i == 8:
print('i é 8 , se else não executará')
break

for j in range(1, 3):
  print(i, j)

else:
print('For completo com sucesso!')

❎ 76 - Exercício - Jogo de palavra secreta

📍 Resposta Exercício

'''

-> Você vai propor uma palavra secreta
  qualquer e vai dar possibilidade para o
  usuário digitar apenas uma letra.

-> Quando o usuário digitar uma letra, você
  vai conferir se a letra digitada está
  na palavra secreta.

-> Se a letra digitada estiver na estiver na
  palavra secreta, exiba a letra;

-> Se a palavra secreta; exiba *.

-> Faça o contagem de tentativas do seu usuário

'''

palavra = 'secreta'

contagem = 0

for letra in palavra:
letra = input('digite letra por letra para adivinhar a palavra secreta = ')

if len(letra) > 1:
    if letra in palavra:
        print(letra)
    else:
      print('*')
contagem += 1

❎ 77 - Sobre exercícios - não saber é normal

❎ 78 - (Parte 1) Solução do exercício - Jogo da palavra secreta

🎓 Resolução do Professor

import os

palavra_secreta = 'perfume'
letras_acertadas = ''
numero_tentativas = 0

while True:
letra_digitada = input('Digite uma letra: ')
numero_tentativas += 1

if len(letra_digitada) > 1:
    print('Digite apenas uma letra.')
    continue

if letra_digitada in palavra_secreta:
    letras_acertadas += letra_digitada

palavra_formada = ''
for letra_secreta in palavra_secreta:
    if letra_secreta in letras_acertadas:
        palavra_formada += letra_secreta
    else:
        palavra_formada += '*'

print('Palavra formada:', palavra_formada)

if palavra_formada == palavra_secreta:
    os.system('clear')
    print('VOCÊ GANHOU!! PARABÉNS!')
    print('A palavra era', palavra_secreta)
    print('Tentativas:', numero_tentativas)
    letras_acertadas = ''
    numero_tentativas = 0

❎ 79 - (Parte 2) Solução do exercício - Jogo da palavra secreta

❎ 80 - Tipo list - Introdução às listas mutáveis me Python

'''
-> Lista em Python
---------------------
->  Tipo list - Mutável

-> Suporta vários valores de qualquer tipo

-> Conhecimentos reutilizáveis - índices e fatiamento

-> Métodos úteis:

-> append

-> insert

-> pop

-> del

-> clear

-> extend, +
'''

# +01234
# -54321

string = ABCDE # 5 caracteres (len)

# print(bool([])) falsy
# print(lista, type(lista))

#   0   1   2   3   4
#  -5  -4  -3   -2 -1

lista = [
123,
True,
'Luiz Otávio',
1.2,
[]
]

lista[-3] = 'Maria'

print(lista)
print(lista[2], type(lista[2]))

❎ 81 - Alterando uma lista con indices, del, append e pop (Tipo list)

'''
-> Lista em Python
---------------------
->  Tipo list - Mutável

-> Suporta vários valores de qualquer tipo

-> Conhecimentos reutilizáveis - índices e fatiamento

-> Métodos úteis:

-> append

-> insert

-> pop

-> del

-> clear

-> extend, +

Create Read Update   Delete
Cria,  ler, alterar, apagar = lista[i] (CRUD)
'''

lista = [ 10, 20, 30, 40 ]

# lista[2] = 300
# del lista[2]
# print(lista)
# print(lista[2])

lista.append(50)

lista.pop()

lista.append(60)
lista.append(70)
ultimo_valor = lista.pop(3)

print (
lista,
'Removido',
ultmo_valor
)

❎ 82 - Inserindo itens em qualquer índice da lista com insert(Tipo list)

'''
-> Lista em Python
---------------------
->  Tipo list - Mutável

-> Suporta vários valores de qualquer tipo

-> Conhecimentos reutilizáveis - índices e fatiamento

-> Métodos úteis:

-> append - Adiciona um item ao final

-> insert - Adiciona um item no índice escolhido

-> pop - Remove do final ou do índice escolhido

-> del - apaga um índice

-> clear - limpa a lista

-> extend - estende a lista

-> + - concatena a lista

Create Read Update   Delete
Cria,  ler, alterar, apagar = lista[i] (CRUD)
'''

lista = [10,20,30,40]

lista.append('Luiz')
nome = lista.pop()
lista.append(1233)
del lista[-1]

# lista.clear()

lista.insert(
100,
5
)

print (lista[4])

❎ 83 - Concatenando e estendendo listas em Python

'''
-> Lista em Python
---------------------
->  Tipo list - Mutável

-> Suporta vários valores de qualquer tipo

-> Conhecimentos reutilizáveis - índices e fatiamento

-> Métodos úteis:

-> append - Adiciona um item ao final

-> insert - Adiciona um item no índice escolhido

-> pop - Remove do final ou do índice escolhido

-> del - apaga um índice

-> clear - limpa a lista

-> extend - estende a lista

-> + - concatena a lista

Create Read Update   Delete
Cria,  ler, alterar, apagar = lista[i] (CRUD)
'''

lista_a = [1, 2, 3]
lista_b = [4, 5, 6]
lista_c = lista_a + lista_b

lista_a.extend(lista_b)

print(lista_a)

❎ 84 - Cuidados com tipos de dados mutáveis - list e copy

Cuidados com dados mutáveis:

-> = - copiado o valor (imutáveis) -> = - aponta para o mesmo valor na memória (mutável)

lista_a = ['Luiz','Maria',1,True,1.2]
lista_b = lista_a.copy()

lista_a[0] = 'Qualquer coisa'

print(lista_a)
print(lista_b)

❎ 85 - for in com tipo list

'''
  ->  for in com listas
  ------------------------
'''

lista = ['Maria', 'Helena', 'Luiz']

for nome in lista
  print( nome, type(nome))

❎ 86 - Exercício - exiba os índices da lista (aula com solução)

📍 Resposta Exercício

'''
  ->  Exercício
  ----------------
  ->  Exiba os índices da lista

        0   Maria
        1   Helena
        2   Luiz
'''

  lista = ['Maria', 'Helena', 'Luiz']
  lista.append('João')

  indices =  range(len(lista))

  for indice in indices:
    print(indice, lista[indice], type(lista[indice]))

❎ 87 - Introdução ao empacotamento e desempacotamento

_, _, nome, *resto = ['Maria', 'Helena', 'Luiz']

print(nome)

❎ 88 - Tipo Tuple (tuplas)

  '''
    -> Tipo tupla
    ------------------
    -> Uma lista imutável
  '''

  nomes = ('Maria', 'Helena', 'Luiz')

  # nomes = tuple(nomes)
  # nomes = list(nomes)

  print(nomes[-1])
  print(nomes)

❎ 89 - enumerate para enumerar valores de iteráveis (pegar indices)

    '''
      -> enumerate
      --------------------
      -> enumerate iteráveis (índices)
    '''

    # [(0, 'Maria'), (1, 'Helena'), (2 , 'Luiz'), (3, 'João')]

    lista = ['Maria', 'Helena', 'Luiz']
    lista.append('João')

    for indice , nome in enumrate(lista):
      print(indice, nome ,lista[indice])


    # for item in enumerate(lista):
    #   indice, nome = item
    #   print(indice, nome)

    # for tupla_enumerada in enumrate(lista):
    #   print('FOR da tupla:')

    #   for valor in tupla_enumerada:
    #     print(f'\t {valor}')

❎ 90 - Exercício - crie uma lista de compras com lista

📍 Resposta Exercício

    '''
      -> Faça um lista de comprar com listas
      ---------------------------------------
      ->  O Usuário deve ter possibilidade de inserir, apagar valors da sua
          lista
      ->  Não permita que o programa quebre com
          erros de índice inexistentes na lista
    '''
    import os

    lista = []

    while True:
        print('Selecione uma opção:')
        opcao = input('[i]nserir [a]pagar [l]listar: ')

        if opcao == 'i':
            os.system('cls')

            valor = input('Valor: ')

            lista.append(valor)
        elif opcao == 'a':
                indice_str = input('Escolha o índice para apagar:')

                try:
                    indice = int(indice_str)
                    del lista[indice]
                except ValueError:
                    print('Por favor digite número int.')
                except IndexError:
                    print('Índice não existe na lista.')
                except Exception:
                    print('Erro desconhecido')
        elif opcao == 'l':
            os.system('cls')

            if len(lista) == 0:
                print('Nada para a lista')

            for i, valor in enumerate(lista):
                print(i, valor)
        else:
            print('Por favor escolha i, a ou l.')

❎ 91 - Solução de exercício - crie uma lista de compras com listas (com try/ except)

⚜️ solução na parte -> 90

❎ 92 - Imprecisão dos números de ponto flutuante + round e decimal.Decimal

    '''
      -> Imprecisão de ponto flutuante
      ----------------------------------
      Double-precision floating-point format IEEE 754

      https://en.wikipedia.org/wiki/Double-precision_floating-point_format

      https://docs.python.org/pt-br/3/tutorial/floatingpoint.html
    '''

    import decimal

    numero_1 = decimal.Decimal('0.1')
    numero_2 = decimal.Decimal('0.7')

    numero_3 = numero_1 + numero_2

    print(numero_3)
    print(f'{numero_3:.2f}')
    print(round(numero_3,2))

❎ 93 - split, join strip são métodos muito úteis do str

    '''
      -> split e join com list e str
      ---------------------------------
      -> split - divide uma string (list)
      -> join - une uma string
    '''

    frase = ' Olha só que   , coisa interessante'

    lista_fases_cruas = frase.split(',')

    lista_frase = []

    for i, frase in enumerate(lista_frases_cruas):
      lista_frases.append(lista_frases_cruas[i].split())

    # print(lista_frases_cruas)
    # print(lista_frases)

    frases_unidas = ','.join(lista_frases)

    print(frases_unidas)

❎ 94 - Listas dentro de listas (iteráveis dentro de iteráveis)

    salas = [
      # 0        1
      ['Maria', 'Helena',], # 0
      # 0
      ['Elaine',],  # 1
      # 0
      ['Luiz', 'João', 'Eduardo',], # 2
    ]

    print(salas[1][0])
    print(salas[0][1])
    print(salas[2][2])
    print(salas[2][3][3])

    for sala in salas:
      print(f'A sala é {sala}')

      for aluno in sala:
        print(aluno)

❎ 95 - Detalhes sobre o interpretador do Python

    '''
      -> Interpretador do Python
      --------------------------------
      -> python   mod.py (executa o mod)
      -> python   -u (unbufferd)
      -> python   -m mod (lib mod como script)
      -> python   -c 'cmd' (comando)
      -> python   -i mod.py (interativo com mod)

      The Zen of Python, por Tim Peters

      Bonito é melhor que feito.
      Explícito é melhor que implícito.
      Simples é melhor que complexo.
      Plano é melhor que complicado.
      Esparso é melhor que aglomerador.
      Legibilidade conta.
      Casos especiais não são especiais o bastante para quebrar as regras.
      Embora a praticidade vença a pureza.
      Erros nunca devem passar silenciosamente.
      A menos que sejam explicitamente silenciados.
      Dante da ambiguidade, recuse a tentação de adivinhar.
      Deve haver um -- e só um -- modo óbvio de para fazer algo.
      Embora esse modo passa não ser óbvio à primeira vista a menos que você seja holandês.
      Agora é melhor que nunca.
      Embora nunca frequentemente seja melhor que a *exatamente* agora.
      Se implementação é difícil de explicar é um má ideia.
      Se implementação é fácil de explicar, pode ser uma boa ideia.
      Namespaces são uma grande ideia -- vamos fazer mais dessas!
    '''

❎ 96 - Desempacotamento em chamadas de funções

    '''
      ->  de métodos e funções
    '''

    string = 'ABCD'
    lista = ['Maria', 'Helena', 1, 2, 3, 'Eduarda']
    tupla = 'Python', 'é', 'legal'
    salas = [
      # 0
      ['Maria', 'Helena', ], # 0
      # 0
      ['Elaine', ], # 1
      # 0       1       2
      ['Luiz', 'João', 'Eduarda', ], # 2
    ]

    # p, b, *_, ap, u = lista
    # print(p, u , ap)

    print('Maria', 'Helena', 1, 2, 3, 'Eduarda')
    print(*lista)
    print(*string)
    print(*tupla)

    print(*sala, sep='\n')

❎ 97 - Operação ternária com Python (if e else de uma linha)

    '''
      -> <valor> if <condicao> else <outro valor>
    '''

    # condicao = 10 == 11
    # variavel = 'Valor' if condicao else 'outro valor'
    # print(variavel)
    # digito = 9 # > 9 = 0
    # novo_digito = digito if digito <= 9 else 0
    # novo_digito = 0 if digito > 9 else digito
    # print(novo_digito)

    print('Valor' if False else 'Outro valor' if False else 'Fim')

❎ 98 - Exercício - Gerar o primero digito de um CPF com Python

📍 Exercício

    '''
      ->  Calculo do primero dígito do CPF
      ----------------------------------------
        CPF: 746.824.890-70

      ->  Colete a soma dos 9 primeiros dígitos do CPF
        multiplicando cada um dos valores por um
        contagem regressiva começando de 10

      Ex.:      746.824.890-70 (746824890)
            10  9   8   7   6   5   4   3   2
      *     7   4   6   8   2   4   8   9   0
            70  36  48  56 12   20  32  27  0

        -> somar todos os resultados:
          70+36+48+56+12+20+32+27+0 = 301

        ->  Multiplique o resultado anterior por 10
            301 * 10 = 3010

        ->  Obter o resto da divisão do conta anterior por 11
            3010 % 11 = 7

        -> Se o resultado anterior for maior que 9
            resultado é 0

        -> contrário disso:
            resultado é o valor da conta

        -> O primeiro dígito do CPF é 7
    '''

❎ 99 - Solução de exercício - Gerar o primeiro digito de um CPF com Python

🎓 Resposta do professor

cpf = '74682489070'
nove_digitos = cpf[:9]

contador_regressivo_1 = 10

resultado_digito_1 = 0

for digito in nove_digitos:
resultado_digito_1 += int(digito) * contador_regressivo_1
  contador_regressivo_1 -= 1

digito_1 = (resultado_digito_1 * 10) % 11

digito_1 = digito_1 if digito_1 <= 9 else 0

print(digito_1)

❎ 100 - Exercício - Gerar o segundo digito de um CPF com Python

📍 Exercício

    '''
      -> Calculo do segundo dígito do CPF
      -------------------------------------
      CPF: 746.824.890-70

      -> Colete a soma dos 9 digitos do primeros dígitos do CPF
      -> MAIS O PRIMERO DIGITO,
      -> multiplique cada um dos valores por uma
          contagem regressiva começado de 11

      Ex.: 746.824.890-70 (7468248907)

          11   10   9   8   7   6   5   4   3   2
      *    7    4    6   8   2   4   8   9   0   7 <-- PRIMEIRO DIGITO
          77    40   54  64  14  24  40  36  0   14

        -> Somar todos os resultados:
          77+40+54+64+14+24+40+36+0+14 = 363

        -> Multiplique o resultado da divisão do conta por 10
          363 * 10 = 3630

        -> obter o resto da divisão do conta anterior por 11
          3630 % 11 = 0

        -> Se o resultado anterior for maior que 9:
            resultado é 0

        -> Contrário disso:
            resultado é valor da conta

        -> O segundo digito do CPF é 0
    '''

  cpf = '74682489070'
  dez_digitos = cpf[:10]

  contador_regressivo_2 = 11

  resultado_digito_2 = 0

  for digito in dez_digitos:
      resultado_digito_2 += int(digito) * contador_regressivo_2
      contador_regressivo_2 -= 1

  digito_2 = (resultado_digito_2 * 10) % 11

  digito_2 = digito_2 if digito_2 < 9  else 0

  print(digito_2)

❎ 101 - Solução de exercício - Gerar o segundo digito de um CPF com Python

🎓 Resposta do professor

      # cpf = '36440847007'  # Esse CPF gera o primeiro dígito como 10 (0)

      cpf_enviado_usuario = '74682489070'
      nove_digitos = cpf_enviado_usuario[:9]
      contador_regressivo_1 = 10

      resultado_digito_1 = 0
      for digito in nove_digitos:
          resultado_digito_1 += int(digito) * contador_regressivo_1
          contador_regressivo_1 -= 1
      digito_1 = (resultado_digito_1 * 10) % 11
      digito_1 = digito_1 if digito_1 <= 9 else 0

      dez_digitos = nove_digitos + str(digito_1)
      contador_regressivo_2 = 11

      resultado_digito_2 = 0
      for digito in dez_digitos:
          resultado_digito_2 += int(digito) * contador_regressivo_2
          contador_regressivo_2 -= 1
      digito_2 = (resultado_digito_2 * 10) % 11
      digito_2 = digito_2 if digito_2 <= 9 else 0

      if cpf_gerado_pelo_calculo = f'{nove_digitos}{digito_1}{digito_2}'
        print(f'{cpf_enviado_usuario} é válido')
      else:
        print('CPF inválido')

❎ 102 - Possíveis problemas e soluções para nosso algoritmo do CPF

  '''
      -> Calculo do segundo dígito do CPF
      -------------------------------------
      CPF: 746.824.890-70

      -> Colete a soma dos 9 digitos do primeros dígitos do CPF
      -> MAIS O PRIMERO DIGITO,
      -> multiplique cada um dos valores por uma
          contagem regressiva começado de 11

      Ex.: 746.824.890-70 (7468248907)

          11   10   9   8   7   6   5   4   3   2
      *    7    4    6   8   2   4   8   9   0   7 <-- PRIMEIRO DIGITO
          77    40   54  64  14  24  40  36  0   14

        -> Somar todos os resultados:
          77+40+54+64+14+24+40+36+0+14 = 363

        -> Multiplique o resultado da divisão do conta por 10
          363 * 10 = 3630

        -> obter o resto da divisão do conta anterior por 11
          3630 % 11 = 0

        -> Se o resultado anterior for maior que 9:
            resultado é 0

        -> Contrário disso:
            resultado é valor da conta

        -> O segundo digito do CPF é 0
    '''

        # cpf = '36440847007'  # Esse CPF gera o primeiro dígito como 10 (0)

        import re
        import sys

        # cpf_enviado_usuario = '746.824.890-70' \
        #   .replace('.', '') \
        #   .replace(' ', '') \
        #   .replace('-', '')

      entrada = input('CPF [746.824.890-70]')

      cpf_enviado_usuario = re.sub(r'[^0-9]', '', entrada)

      entrada_e_sequencial = entrada == entrada[0] * len(entrada)

      if entrada_e_sequencial
        print('Você enviou dados sequenciais')
        sys.exit()


      nove_digitos = cpf_enviado_usuario[:9]
      contador_regressivo_1 = 10

      resultado_digito_1 = 0
      for digito in nove_digitos:
          resultado_digito_1 += int(digito) * contador_regressivo_1
          contador_regressivo_1 -= 1
      digito_1 = (resultado_digito_1 * 10) % 11
      digito_1 = digito_1 if digito_1 <= 9 else 0

      dez_digitos = nove_digitos + str(digito_1)
      contador_regressivo_2 = 11

      resultado_digito_2 = 0
      for digito in dez_digitos:
          resultado_digito_2 += int(digito) * contador_regressivo_2
          contador_regressivo_2 -= 1
      digito_2 = (resultado_digito_2 * 10) % 11
      digito_2 = digito_2 if digito_2 <= 9 else 0

      if cpf_gerado_pelo_calculo = f'{nove_digitos}{digito_1}{digito_2}'
        print(f'{cpf_enviado_usuario} é válido')
      else:
        print('CPF inválido')

❎ 103 - Criando um gerador de CPFs com nosso algoritmo e Python

      import random
      import sys

      for i in range(9):
        nove_digitos += str(random.randint(0,9))

      contador_regressivo_1 = 10

      resultado_digito_1 = 0
      for digito in nove_digitos:
          resultado_digito_1 += int(digito) * contador_regressivo_1
          contador_regressivo_1 -= 1
      digito_1 = (resultado_digito_1 * 10) % 11
      digito_1 = digito_1 if digito_1 <= 9 else 0

      dez_digitos = nove_digitos + str(digito_1)
      contador_regressivo_2 = 11

      resultado_digito_2 = 0
      for digito in dez_digitos:
          resultado_digito_2 += int(digito) * contador_regressivo_2
          contador_regressivo_2 -= 1
      digito_2 = (resultado_digito_2 * 10) % 11
      digito_2 = digito_2 if digito_2 <= 9 else 0

      cpf_gerado_pelo_calculo = f'{nove_digitos}{digito_1}{digito_2}'

      print(cpf_gerado_pelo_calculo)

🔜 ❎ Seção 4 Python Intermediário - Funções, Dicionários, Módulos, Programação Funcional e +⚓︎


❎ 104 - O que vamos aprender nessa seção intermediária?

❎ 105 - Introdução às funções python - def define uma função

    '''
      -> Introdução às funções (def) em Python
      -----------------------------------------

      -> Funções são trechos de códigos usados para
          replicar determinada ação ao longo do seu código.

      -> Elas podem receber valores para parâmetros (argumentos)
          e retornar um valor específico.

      -> Por padrão, funções Python retornam None (nada).
    '''

    # def Print(a, b , c)
    #   print('Vários_1')
    #   print('Vários_2')
    #   print('Vários_3')
    #   print('Vários_4')

    # def imprimir(a, b, c):
    #   print(a, b, c)

    def saudacao(nome='Seu nome'):
      print(f'Olá, {nome}')


    saudacao('Luiz Otávio')
    saudacao('Maria')
    saudacao('Helena')
    saudacao()

❎ 105.1 - Teste 8 - seus conhecimentos Questionário 8

❎ 106 - Argumentos e nomeados e argumentos posicionais (não nomeados) em funções

'''
-> Argumento nomeado tem nome com sinal de igual
-> Argumento não nomeado recebe apenas o argumento (valor)
'''

def soma(x, y, z):
# Definição
print(f'{x=} {y=} {z=}', '|', 'x + y + z', x + y + z )

soma(1,2,3)
soma(1,y=2,z=3)

print(1,2,3, sep='-')

❎ 106.1 - Teste 9 - seus conhecimentos Questionário 9

❎ 107 - Valores padrões para parâmetros em funções Python + NoneType e None

'''
-> valores padrão para parâmetros
-----------------------------------------------------------
-> Ao definir uma função, os parâmetros podem
    ter valores padrão. Caso o valor não seja
    enviado para o parâmetro, o valor podrão será
    usado.

-> Refatorar: editar o seu código.
'''

def soma(x, y, z=None):
if z is not None
  print(f'{x=} {y=} {z=}', '|', 'x + y + z', x + y + z )
else:
  print(f'{x=} {y=}', '|', 'x + y', x + y )

soma(1,2,3)
soma(1,y=2,z=3)

print(1,2,3, sep='-')

❎ 107.1 - Teste 10 - seus conhecimentos Questionário 10

❎ 108 - (Parte 1) Escopo de funções e módulos em Python + global

'''
  -> Escopo de funções em Python
  -------------------------------
  -> Escopo de significa o local onde aquele código pode atingir.
  -> Existe o escopo global e local
  -> O escopo global é o escopo onde todo o código é alcançável.
  -> O escopo local é o escopo onde apenas nomes do mesmo local
      podem ser alcançados.
  -> Não temos acesso a nomes de escopos internos nos escopos
      externos
  -> A palavra globa faz uma variável do escopo externo
      ser a mesma no escopo interno.
'''

def escopo():
  global x
  x = 10

def outro_funcao():
  global x
  x = 11
  y = 2
  print(x, y)

outra_funcao()

print(x)

print(x)
escopo()
print(x)

❎ 109 - (Parte 2) Escopo de funções e módulos em Python + global

Debugger o código da aula 108

❎ 109.1 - Teste 11 - seus conhecimentos Questionário 11

❎ 110 - Retorno de valores das funções (return)

  def soma (x, y):
    if x > 10:
      return [10, 20]
    return x + y

    # variavel = soma(1 , 2)
    # variavel = int('1')

    soma_1 = soma(2, 2)
    soma_2 = soma(3, 3)

    print(soma_1)
    print(soma_2)
    print(soma(11, 55))

❎ 111 - (Parte 1)* args para quantidade de argumentos não nomeados variáveis

'''
-> args - Argumentos não nomeados
------------------------------------
-> *args (empacotamento e desempacotamento)
'''

# Lembre-te de desempacotamento

x, y, *resto = 1, 2, 3, 4

print(x, y, resto)

def soma(x, y):
  return x + y

def soma(*args):
  total = 0
  for numero in args:
    print('Total', total, numero)
    total += numero
    print('Total', total)
  print(total)

soma(1, 2, 3, 4, 5, 6)

❎ 112 - (Parte 2)* args para quantidade de argumentos não nomeados variáveis

'''
-> args - Argumentos não nomeados
------------------------------------
-> *args (empacotamento e desempacotamento)
'''

# Lembre-te de desempacotamento
# x, y, *resto = 1, 2, 3, 4
# print(x, y, resto)

# def soma(x, y):
#   return x + y

def soma(*args):
total = 0
for numero in args:
  total+= numero
return total

soma_1_2_3 = soma (1, 2, 3)

# print(soma_1_2_3)

numeros = 1, 2, 3, 4, 5, 6, 7, 78, 10
outra_soma = soma(*numeros)
print(outra_soma)

print(sum(numeros))

❎ 113 - Execícios com Funções + Solução (prepare-se para pausar)

📍 Exercício com proposta

'''
-> Exercícios com funções
----------------------------
-> Crie uma função que multiplica todos os argumentos
  não nomeados recebidos
-> Retorne o total para um variável em mostre o valor
  da variavel

'''
def mult(*args):
  total = 1
  for num in args:
    total *= num
  return total
'''
-> crie uma função se um número é par ou ímpar
-> Retorne se o número é par ou ímpar
'''

def par_impar(x)
if x % 2 == 0
return  f'{x} é par'
return f'{x} é impar'

❎ 114 - Higher Order Functions - Funções de primeira classe

'''
-> Higher Order Functions
-----------------------------
-> funções de primeira classe
'''

def saudacao(msg, nome)
return f'{msg}, {nome}!'

def executa(funcao, *args)
return funcao(*args)

print(executa(saudacao), 'Bom dia', 'Luiz')
print(executa(saudacao), 'Boa noite', 'Maria')

❎ 115 - Termos técnicos: Higher Order Functions e First-Class Functions

-> Termos Técnicos : Higher Order

🎀 Academicamente, os termos Higher Oder e First Class Functons tê significados diferentes.

  • Higher Oder Functions - Funções que podem receber e/ou retornar outras funções.

  • First-Class Functions - Função que são tratadas como outros tipos de dados comuns (strings, inteiros, etc...)

🎀 Não faria muita diferença no seu código, mas pense que deveria lhe informar isso.

🎀 Obervação: esses termos podem ser diferentes e ainda refletir o mesmo significado.

❎ 116 - Closure e funções que retornam outras funções

def cria_saudacao(saudacao):
def saudar(nome):
return f'{saudacao}, {nome} !'
return saudar

falar_bom_dia = cria_saudacao('Bom dia')
falar_boa_noite = cria_saudacao('Boa noite')

for nome in ['Maria', 'Joana' 'Luiz']:
print(falar_bom_dia(nome))
print(falar_boa_noite(nome))

❎ 117 - Exercício com funções

📍 Exercício Resposta

'''
-> Exercícios
-----------------
-> Crie funções que duplicam, triplicam e quadruplicam
    o numero recebido como parâmetro
'''

def crie_mutipil(multiplier):
def multip(number):
  return number * multiplier
return multip

duplicar = criar_multiplicador(2)
triplicar = criar_multiplicador(3)
quadruplicar = criar_multiplicador(4)

print(duplicar(2))
print(triplicar(2))
print(quadruplicar(2))

❎ 118 - Solução do exercício com funções

🎓 Resposta do professor

'''
-> Exercícios
-----------------
-> Crie funções que duplicam, triplicam e quadruplicam
  o numero recebido como parâmetro

  def duplicar(numero):
    return numero * 2


  def triplicar(numero):
      return numero * 3


  def quadruplicar(numero):
      return numero * 4

'''

def criar_multiplicador(multiplicador):
  def multiplicar(numero):
      return numero * multiplicador
  return multiplicar


duplicar = criar_multiplicador(2)
triplicar = criar_multiplicador(3)
quadruplicar = criar_multiplicador(4)

print(duplicar(2))
print(triplicar(2))
print(quadruplicar(2))

❎ 119 - Introdução ao tipo de dados dict- Dicionários em Python

'''
-> Dicionários em Python (tipo dict)
--------------------------------------
-> Dicionários são estruturas de dados de tipo
  par "chave" e "valor"
-> Chave podem ser consideradas como o "índice"
  que vimos na lista e podem ser de tipo imutável
  como: str, int, float bool, tuple, e etc.
->  O valor pode ser de qualquer tipo incluindo outro
  dicionário.
-> Usamos as chave - {} ou classe dict para criar
  dicionários.
->  Imutáveis: str, int, float bool, tuple.
->  Mutável: dict, list

->  pessoa = {
'nome': 'Luiz Otávio',
'sobrenome': 'Miranda',
'idade': 18,
'altura': 1.8,
'endereço': [
    {'rua': 'tal tal', 'número': 123}
    {'rua': 'outra rua', 'número': 321}
  ]
}

pessoa = dict(nome= 'Luiz Otávio', sobrenome='Miranda')
'''

pessoa = {
'nome': 'Luiz Otávio',
'sobrenome': 'Miranda',
'idade': 18,
'altura': 1.8,
'endereço': [
    {'rua': 'tal tal', 'número': 123}
    {'rua': 'outra rua', 'número': 321}
  ],
}

print(pessoa, type(pessoa))
print(pessoa(['nome']))
print(pessoa(['sobrenome']))

print()

for cave in pessoa
print(chave, pessoa[chave])

❎ 120 - Manipulando chave e valor em dicionários em Python

pessoa ={}

chave = 'nome'

pessoa[chave] = 'Luiz Otávio'
pessoa['sobrenome'] = 'Miranda'

print(pessoa[chave])

pessoa[chave] = 'Maria'

del pessoa['sobrenome']

print(pessoa)
print(pessoa['nome'])


# print(pessoa.get('sobrenome'))

if pessoa.get('sobrenome') is None
print('NÃO EXISTE')
else:
print(pessoa['sobrenome'])

# print('Isso Não vai')

❎ 121 - (Parte 1) Métodos úteis nos dicionários Python (dict)

'''
-> len - quantas chaves
->  keys - iterável com as chaves
->  values - iterável com os valores
->  items - iterável com chaves e valores
->  stdefault - adiciona valor se a chave não existe
->  copy - retorna uma cópia rasa (shallow copy)
->  get - obtém uma chave
->  pop - apaga um item com a chave especificada (del)
->  popitem - apaga o último item adicionado
->  update - atualiza um dicionario com outro
'''

pessoa = {
'nome': 'Luiz Otávio',
'sobrenome': 'Miranda',
'idade': 900,
}

pessoa.setdefault('idade', 0)

print(len(pessoa))
print(list(pessoa.keys()))
print(list(pessoa.values()))
print(list(pessoa.items()))

for valor in pessoa.values():
print(valor)

for chave, valor in pessoa.items():
print (chave, valor)

❎ 122 - Shallow Copy vs Deep Copy em dados mutáveis Python

'''
  -> len - quantas chaves
  ->  keys - iterável com as chaves
  ->  values - iterável com os valores
  ->  items - iterável com chaves e valores
  ->  stdefault - adiciona valor se a chave não existe
  ->  copy - retorna uma cópia rasa (shallow copy)
  ->  get - obtém uma chave
  ->  pop - apaga um item com a chave especificada (del)
  ->  popitem - apaga o último item adicionado
  ->  update - atualiza um dicionários com outro
'''

import copy

d_1 = {
  'c_1': 1,
  'c_2': 2,
  'l_1': [0, 1, 2],
}

d_2 = d_1.copy()

d_2['c_1'] = 1000
d_2['l_1'][1] = 999999

print(d_1)
print(d_2)

❎ 123 - (Parte 2) Métodos úteis nos dicionários Python (dict)

'''
-> len - quantas chaves
->  keys - iterável com as chaves
->  values - iterável com os valores
->  items - iterável com chaves e valores
->  stdefault - adiciona valor se a chave não existe
->  copy - retorna uma cópia rasa (shallow copy)
->  get - obtém uma chave
->  pop - apaga um item com a chave especificada (del)
->  popitem - apaga o último item adicionado
->  update - atualiza um dicionário com outro

'''

p_1 = {
'nome': 'Luiz',
'sobrenome': 'Miranda'
}

print(p_1['nome'])
print(p_1.get('nome', 'Não existe'))

nome = p_1.pop(nome)

print(nome)
print(p_1)

ultima_chave = p_1.popitem()

print(ultima_chave)
print(p_1)

p_1.update({
'nome': 'novo valor',
'idade': 30,
})

p_1.update(nome='novo valor', idade= 30)

tupla = ((nome, 'novo valor'), ('idade', 30))
lista = [['nome', 'novo valor'], ['idade', 30]]

p_1.update(lista)

print(p_1)

❎ 124 - Exercício - sistema de perguntas e respostas com Python

📍 Exercício

perguntas = [
    {
      'Pergunta': 'Quanto é 2 + 2 ?',
      'Opões': ['1', '2', '4', '5'],
      'Resposta': '4',
    },
    {
      'Pegunta': 'Quanto é 2 + 2 ?',
      'Opões': ['25', '55', '10', '51'],
      'Resposta': '25',
    },
    {
      'Pegunta': 'Quanto é 10 / 2 ?',
      'Opões': ['4', '5', '2', '1'],
      'Resposta': '5',
    },
  ]

❎ 125 - Solução do Exercício - sistema de perguntas e respostas com Python

🎓 Resposta do professor

'''
-> Exercícios - Sistema de perguntas e respostas
--------------------------------------------------
'''

perguntas = [
    {
      'Pergunta': 'Quanto é 2 + 2 ?',
      'Opões': ['1', '2', '4', '5'],
      'Resposta': '4',
    },
    {
      'Pergunta': 'Quanto é 2 + 2 ?',
      'Opões': ['25', '55', '10', '51'],
      'Resposta': '25',
    },
    {
      'Pergunta': 'Quanto é 10 / 2 ?',
      'Opões': ['4', '5', '2', '1'],
      'Resposta': '5',
    },
  ]

opcoes = perguntas['Opções']
qtd_acertos = 0
acertou = False
escolha_int = None
qtd_opoes = len(opcoes)



for pergunta in perguntas:
  print('Pergunta :', perguntas['Pergunta'])


for i, opcao  in enumerate(perguntas['Opções']):
  print(f'{i})', opcao)
print()

escolha = input('Escolha uma opção:')

if escolha.isdigit():
  escolha_int = int(escolha)

if escolha_int is not None:
  if escolha_int >= 0 and escolha_int < qtd_opcoes:
    if opcoes[escolha_int] == pergunta['Resposta']:
      acertou = True

print()

if acetou:
  qtd_acertou += 1
  print('Acertou 👍')
else:
  print('Errou ❌')

print()

print('Você acertou', qtd_acertos)
print('de', len(perguntas), 'perguntas.')

❎ 126 - Introdução ao tipo set em Python (conjuntos)

'''
->  Conjuntos são ensinados n matemática
->  https://brasilescola.uol.com.br/matematica/conjunto.htm
->  Representado graficamente pelo diagrama de Venn
->  Sets em Python são mutáveis, porém aceitam apenas
    tipos imutáveis como valor interno.

  Criando um set

  set(iterável) ou {1, 2, 3}
'''

  s_1 = set() #vazio
  s_1 = {'Luiz', 1, 2, 3} com dados

'''
->  Sets são eficientes para remover valores duplicados
      de iteráveis.
->  Não aceitam valores mutáveis;
->  Seus valores serão sempre únicos;
->  não tem índexes;
->  Não gerantem ordem;
->  São iteráveis (for, in , not in)

->  Métodos úteis
-----------------------------
->  add, update, clear, discard

-> Operadores úteis:
------------------------------
->  União | união (union) - Une
->  Intersecção & (intersection) -
      Itens presentes em ambos
->  Diferença - Itens presentes apenas no set da esquerda
->  Diferença simétrica ^ - Itens que não estão em ambos
'''

❎ 127 - Peculiaridades do tipo mutáveis set em Python

'''
->  Conjuntos são ensinados n matemática
->  https://brasilescola.uol.com.br/matematica/conjunto.htm
->  Representado graficamente pelo diagrama de Venn
->  Sets em Python são mutáveis, porém aceitam apenas
    tipos imutáveis como valor interno.

  Criando um set

  set(iterável) ou {1, 2, 3}
'''

s_1 = set() #vazio
  s_1 = {'Luiz', 1, 2, 3} com dados

'''
->  Sets são eficientes para remover valores duplicados
    de iteráveis.
->  Não aceitam valores mutáveis;
->  Seus valores serão sempre únicos;
->  não tem indexes;
->  Não garantem ordem;
->  São iteráveis (for, in , not in)
'''

l_1 = [1, 2, 3, 3, 3, 3, 1]

s_1 = set(l_1)
l_2 = list(s_1)

s_1 = {1, 2, 3}

print(3 not in s_1)

for num in s_1:
print(num)

'''
->  Métodos úteis
-----------------------------
->  add, update, clear, discard

-> Operadores úteis:
------------------------------
->  União | união (union) - Une
->  Intersecção & (intersection) -
      Itens presentes em ambos
->  Diferença - Itens presentes apenas no set da esquerda
->  Diferença simétrica ^ - Itens que não estão em ambos
'''

❎ 128 - Métodos úteis do tipo set em Python

'''
->  Métodos úteis
-----------------------------
->  add, update, clear, discard
'''
s_1 = set()
s_1.add('Luiz')
s_1.add(1)
s_1.update(('Oá mundo', 1, 2, 3, 4))
s_1.clear()
s_1.discard('Oá mundo')
s_1.discard('Luiz')

print(s_1)

'''
-> Operadores úteis:
------------------------------
->  União | união (union) - Une
->  Intersecção & (intersection) -
      Itens presentes em ambos
------------------------------------------------
->  Diferença - Itens presentes apenas no set da esquerda
->  Diferença simétrica ^ - Itens que não estão em ambos
'''

s_1 = {1, 2, 3}
s_2 = {2, 3, 4}
s_3 = s_1 | s_2
s_3 = s_1 & s_2
s_3 = s_1 - s_2
s_3 = s_1 ^ s_2

print(s_3)

❎ 129 - Operadores importantes para o tipo ser (conjuntos)

s_1.clear()
s_1.discard('Oá mundo')
s_1.discard('Luiz')

print(s_1)

'''
-> Operadores úteis:
------------------------------
->  União | união (union) - Une
'''

❎ 130 - Exemplo de uso do tipo set

letras = set()

while True:
letras = input('Digite: ')
letras.add(letra.lower())

if 'l' in letras:
print('PARABÉNS')
break

print(letras)

❎ 131 - Exercício - Encontre o primeiro duplicado considerando a segunda ocorrência

📍 Exercício

'''
-> Exercício
-------------------------------------------------------
-> Crie uma função que encontra o primeiro duplicado
  considerando o segundo número como a duplicação .
  Retorne a duplicação considerada.

-> Requisitos:
------------------------------------------------------
->  A ordem do número duplicado é considerada a partir da segunda
  ocorrência no número, ou seja,  número duplicado em si.

Exemplo:

  [1, 2, 3, ->3<-, 2, 1] 1, 2 e  são duplicados (retorne 3)
  [1, 2, 3, 4, 5 , 6] -> Retorne -1 (não tem duplicados)
  [1, 4, 9, 8, ->9<-, 4, 8 ] (retorne 9)

Se não encontrar duplicados na lista, retorne -1
'''
lista_de_listas_de_inteiros = [
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10],
[9, 1, 8, 9, 9, 7, 2, 1, 6, 8],
[1, 3, 2, 2, 8, 6, 5, 9, 6, 7],
[3, 8, 2, 8, 6, 7, 7, 3, 1, 9],
[4, 8, 8, 8, 5, 1, 10, 3, 1, 7],
[1, 3, 7, 2, 2, 1, 5, 1, 9, 9],
[10, 2, 2, 1, 3, 5, 10, 5, 10, 1],
[1, 6, 1, 5, 1, 1, 1, 4, 7, 3],
[1, 3, 7, 1, 10, 5, 9, 2, 5, 7],
[4, 7, 6, 5, 2, 9, 2, 1, 2, 1],
[5, 3, 1, 8, 5, 7, 1, 8, 8, 7],
[10, 9, 8, 7, 6, 5, 4, 3, 2, 1],
]

❎ 132 - Solução - Encontre o primeiro duplicado considerando a segunda ocorrência

🎓 Resposta do professor

  lista_de_listas_de_inteiros = [
    [1, 2, 3, 4, 5, 6, 7, 8, 9, 10],
    [9, 1, 8, 9, 9, 7, 2, 1, 6, 8],
    [1, 3, 2, 2, 8, 6, 5, 9, 6, 7],
    [3, 8, 2, 8, 6, 7, 7, 3, 1, 9],
    [4, 8, 8, 8, 5, 1, 10, 3, 1, 7],
    [1, 3, 7, 2, 2, 1, 5, 1, 9, 9],
    [10, 2, 2, 1, 3, 5, 10, 5, 10, 1],
    [1, 6, 1, 5, 1, 1, 1, 4, 7, 3],
    [1, 3, 7, 1, 10, 5, 9, 2, 5, 7],
    [4, 7, 6, 5, 2, 9, 2, 1, 2, 1],
    [5, 3, 1, 8, 5, 7, 1, 8, 8, 7],
    [10, 9, 8, 7, 6, 5, 4, 3, 2, 1],
  ]

  def encontrar_primeiro_duplicado(lista_de_inteiros):
    numeros_checados = set()
    primeiro_duplicado = -1

    for numero in lista_de_inteiros:
      if numero in numero_checados:
        primeiro_duplicado = numero
        break

      numeros_checados.add(numero)

    return primeiro_duplicado

  for lista in lista_de_listas_de_inteiros:
    print(lista, encontrar_primeiro_duplicado(lista))

❎ 133 - Introdução à função lambda + list.sort e sorted

'''
-> A função lambda é uma função como qualquer
outra em Python. Porém, são funções anônimas
que contém apenas uma linha. Ou seja, tudo
deve ser contido dentro de uma única expressão
'''

lista = [
{'nome': 'Luiz': 'sobrenome': 'Miranda'},
{'nome': 'Maria': 'sobrenome': 'Oliveira'},
{'nome': 'Daniel': 'sobrenome': 'Silva'},
{'nome': 'Eduardo': 'sobrenome': 'Moreira'},
{'nome': 'Aline': 'sobrenome': 'Souza'},
]

lista = [4, 32, 1, 34, 5, 6, 6, 21, ]

lista.sort(reverse=True)

sorted(lista)


lista = [
{'nome': 'Luiz': 'sobrenome': 'Miranda'},
{'nome': 'Maria': 'sobrenome': 'Oliveira'},
{'nome': 'Daniel': 'sobrenome': 'Silva'},
{'nome': 'Eduardo': 'sobrenome': 'Moreira'},
{'nome': 'Aline': 'sobrenome': 'Souza'},
]

def exibir(lista)
for item in lista:
print(item)
print()

l_1 = sorted(lista, key=lambda item: item['nome'])
l_2 = sorted(lista, key=lambda item: item['sobrenome'])

exibir(l_1)
exibir(l_2)

❎ 134 - Funções lambda complexas (para entendimento)

def execute(funcao, *args):
  return funcao(*args)

def soma(x, y):
  return x +y

def crie_mutipil(multiplier):
      def multip(number):
        return number * multiplier
      return multip

duplica = crie_mutipil(2)

duplica = executa( lambda m: lambda n: n * m, 2, 3 )

print(duplica(2))

print(executa(lambda x, y: x + y, 2, 3),)

print(executa(lambda *args: sum(args), 1, 2, 3, 4, 5, 6, 7))

❎ 135 - Empacotamento e desempacotamento de dicionários *args e **Kwargs

a, b = 1, 2
a, b = b, a

print(a, b)

(a_1, a_2), (b_1, b_2) = pessoa.items()

for chave, valor in pessoa.items():
print(chave, valor)

pessoa = {
'nome': 'Aline',
'sobrenome': 'Souza'
}

dados_pessoa = {
'idade': 16,
'altura': 1.6
}

pessoas_completa = {**pessoa, dados_pessoa}

print(pessoas_completa)

'''
-> args e kwargs
----------------------------
-> args (já vimos)
-> kwargs - keyword arguments (argumento nomeados)
'''

def mostro_argumentos_nomeados(*args, **kwargs):
print('MÃO NOMEADOS', args)

for chave, valor in kwargs.items():
  print(chave, valor)

mostro_argumentos_nomeados(nome='Joana', qlq=123)
mostro_argumentos_nomeados(**pessoas_completa)

configuracoes = {
'args_1': 1,
'args_2': 2,
'args_3': 3,
'args_4': 4,
}

mostro_argumentos_nomeados(**configuracoes)

❎ 136 - Introdução à List comprehension em Python

'''
-> List comprehension é uma forma rápida para criar listas
    a partir de iteráveis.
'''

lista = []

for numero in range(10):
lista.append(numero)

print(lista)

# ==

lista = [
numero * 2
for numero in range(10)
]

print(lista)

❎ 137 - Mapeamento de dados em list comprehension (map)

'''
-> List comprehension é uma forma rápida para criar listas
  a partir de iteráveis.
'''


lista = []

for numero in range(10):
lista.append(numero)

print(lista)

# ==

lista = [
numero * 2
for numero in range(10)
]

print(list(range(10)))
print(lista)

# mapeamento de dados em list comprehension

produtos = [
{'nome':'p_1',:'preco': 20},
{'nome':'p_2',:'preco': 10},
{'nome':'p_3',:'preco': 30},
]

novos_produtos = [
{**produto, 'preco': produto['preco'] * 1.05}
if produto['preco'] > 20 else {**produto}
for produto in produtos
]

print(novos_produtos)

print(*novos_produtos, sep='\n')

❎ 138 - Filtro de dados list comprehension (filter)

'''
-> List comprehension é uma forma rápida para criar listas
    a partir de iteráveis.
'''

import pprint


print(lista(range(10)))

def p(v):
pprint.pprint(v, sort_dicts=False, width=40)

lista = []

for numero in range(10):
lista.append(numero)

print(lista)

# mapeamento de dados em list comprehension

produtos = [
{'nome':'p_1',:'preco': 20},
{'nome':'p_2',:'preco': 10},
{'nome':'p_3',:'preco': 30},
]

novos_produtos = [
{**produto, 'preco': produto['preco'] * 1.05}
if produto['preco'] > 20 else {**produto}
for produto in produtos
]

print(novos_produtos)
print(novos_produtos)

p(novos_produtos)

lista = [n for n in range(10) if n < 5]

novos_produtos = [
{**produto, 'preco': produto['preco'] * 1.05}
if produto['preco'] > 20 else {**produto}
for produto in produtos
if (produto['preco'] >= 20 and produto['preco'] * 1.05) > 10
]

p(novos_produtos)

❎ 139 - List comprehension com mais de um for

lista = []

for x in range(3):
  for y in range(3):
    lista.append(x, y)

# ==

lista = [
  (x, y)
  for x in range(3)
  for y in range(3)
]

lista = [
  [(x, letra) for letra in 'Luiz']
  for x in range(3)
]

print(lista)

❎ 140 - Mais detalhes sobre list comprehension

❎ 141 - Dictionary Comprehension e Set Comprehension

produto = {
'nome': 'Caneta Azul',
'preco': 2.5,
'categoria': 'Escritório',
}

dc = {
    chave: valor.upper()
    if isinstance(valor, str) else valor
    for chave, valor in produto.items()
    if chave != 'categoria'
  }

lista = [
  ('a','valor a'),
  ('b','valor a'),
  ('b','valor a'),
]

dc = {
  chave: valor
  for chave, valor in lista
}

s_1 = {2 ** i for i in range(10)}

print(s_1)

❎ 142 - isinstace() - para saber se objeto é de determinado tipo

lista = {
'a', 1, 1.1, True, [0, 1, 2], (1, 2),
{0, 1}, {'nome': Luiz}
}

for item in lista:
if isinstance(item, set):
print('SET')
item.add(5)
print(item, isinstance(item, set))

elif isinstance(item, str):
print('STR')
print(item.upper())

elif isinstance(item, (int, float))
print('NUM')
print(item, item * 2)

else:
print('OUTRO')
print(item)

❎ 143 - Valores Trophy e Falsy, Tipos Mutáveis e imutáveis

'''
-> Mutáveis [] {} set()
-> Imutáveis () "" 0 0.0 None False range(0,10)
'''

lista = []
dicionario = {}
conjunto = set()

tupla = ()
string = ''
inteiro = 0
flutuante = 0.0
nada = None
falso = False
intervalo = range(0)

def falsy(valor)
return 'falsy' if not valor else 'truthy'

print(f'TESTE', falsy('TESTE'))
print(f'{lista=}', falsy(lista))
print(f'dicionario=', falsy(dicionario))
print(f'conjunto=', falsy(conjunto))
print(f'tupla=', falsy(tupla))
print(f'string=', falsy(string))
print(f'inteiro=', falsy(inteiro))
print(f'flutuante=', falsy(flutuante))
print(f'nada=', falsy(nada))
print(f'falso=', falsy(falso))
print(f'intervalo', falsy(intervalo))

❎ 144 - dir hasattr e getatter em Python

string = 'Luiz'
metodo = 'strip'

if hasattr(string, metodo):
  print('Existe upper')
  print(getattr(string, metodo)())
else:
  print('Não existe o método, metodo')

❎ 145 - Mais detalhes sobre iterables e iterators (iteráveis e iteradores)

iterable = ['Eu', 'Tenho', '__iter__']
interator = iter(interable) # tem __iter__ e __next__

print(next(interator))
print(next(interator))
print(next(interator))
print(next(interator))

❎ 146 - Generator expression iterables e iterators em Python

import sys

iterable = ['Eu', 'Tenho', '__iter__']
interator = iter(interable) # tem __iter__ e __next__

lista = [n for n in range(1000000)]
generator = (n for n in range(1000000))

print(sys.getsizeof(lista))
print(sys.getsizeof(generator))

print(generator)

for n in generator:
print(n)

❎ 147 - Introdução às Generator Functions em python

generator = (n for n in range(1000000))

def generator(n=0, maximum=10)
while True:
  yield n
  n += 1

  if n >= maximum:
    return

gen = generator(maximum=1000000)

for n in gen:
print(n)

❎ 148 - yield from em generator Functions

def gen_1():
print('COMECOU GEN_1')

yield 1
yield 2
yield 3

print('ACABOU GEN_1')

def gen_3():
print('COMECOU GEN_3')

yield 10
yield 20
yield 30

print('ACABOU GEN_3')

def gen_2(gen=None):
print('COMECOU GEN_2')

if gen is not None:
  yield from gen

yield 4
yield 5
yield 6

print('ACABOU GEN_2')

g_1 = gen_2(gen_1())
g_2 = gen_2(gen_3())
g_3 = gen_2()

for numero in g_1:
print(numero)

print()

for numero in g_3:
print(numero)

print()

❎ 149 - (Parte 1) try e except para tratar exceções

a = 18
b = 0
c = a / b

try:
a = 18
b = 0

print(b[0])
print('Linha 1'[1000])

c = a / b

print('Linha 2')
except ZeroDivisionError:
  print('Dividiu por zero.')
except NameError:
  print('Nome b não está definido')
except (TypeError, IndexError):
  print('TypeError + IndexError')
except Exception:
  print('ERRO DESCONHECIDO.')

print('CONTINUAR')

❎ 150 - (Parte 2) try e except para tratar exceções

a = 18
b = 0
c = a / b

try:
a = 18
b = 0

print(b[0])
print('Linha 1'[1000])

c = a / b

print('Linha 2')
except ZeroDivisionError as e:
  print(e.__class__.__name__)
  print(e)
except NameError:
  print('Nome b não está definido')
except (TypeError, IndexError) as error:
  print('TypeError + IndexError')
  print('MSG:', error)
  print('Nome:' error.__class__.__name__)
except Exception:
  print('ERRO DESCONHECIDO.')

print('CONTINUAR')

❎ 151 - try, except, else e finally + Bult-in Exceptions

'''
https://docs.python.org/pt-br/3/library/exceptions.html#built-in-exceptions
'''

try:
  print('ABRIR ARQUIVO')
  8/0
except ZeroDivisionError as e:
  print(e.__class__.__name__)
  print(e)
print('DIVIDIU ZERO')
except IndexError as error:
  print('IndexError')
except (NameError, ImportError):
  print('NameError, ImportError')
else:
  print('Não deu erro')
finally:
  print('FECHAR ARQUIVO')

❎ 152 - raise lançando (erros)

'''
https://docs.python.org/pt-br/3/library/exceptions.html#built-in-exceptions
'''

def nao_aceito_zero(d);
  if d == 0:
  raise ZeroDivisionError('Você está tentado dividir por zero')
  return True

def deve_ser_int_ou_float(n):
  tipo_n = type(n)
  if not isinstance(n, (float, int)):
  raise TypeError(
    f'"{n}" deve ser int ou float.'
    f'"{tipo_n.__name__}" enviado.'
  )
  return True

def divide(n, d):
  deve_ser_int_ou_float(n)
  deve_ser_int_ou_float(d)
  nao_aceito_zero(d)

  return n / d

print(divide(8, '0'))

❎ 153 - Módulos - import, from, as e *

'''
https://docs.python.org/3/py-modindex.html

->  Inteiro - import nome_módulo
->  Vantagens: você tem o namespace do módulo
->  Desvantagens: nomes grandes
'''

import sys


#   plataform = 'A MINHA'
print(sys.platform)
print(platform)

'''
->  partes - from nome_modulo import objeto_1, objeto_2
->  Vantagens: nomes pequenos
->  Desvantagens: Sem o namespace do módulo
'''

from sys import exit, platform

print(platform)

# alias 1 - import nome_modulo as apelido
import sys as s

sys = 'Alguma coisa'

print(s.platform)
print(sys)

# alias 2 - from nome_modulo import objeto as apelido
from sys import exit as ex
from sys import platform as pt

print(pt)

'''
->  Vantagens : você pode reservar nomes para seu código
->  Desvantagens: pode ficar fora do padrão da linguagem

->  má prática - from nome_modulo import *
->  Vantagens: importa tudo de um módulo
->  Desvantagens: importa tudo de um módulo
'''

from sys import exit, platform

print(platform)
exit()

❎ 154 - Modularização - Entendendo os seus próprios módulos e sys.path no Python

'''
->  O primeiro módulo executado chama-se __main__
->  Você pode importar outro módulo inteiro ou parte do módulo
->  O python conhece a pasta onde o __main__ está e as pastas
    abaixo dele.
->  Ele não reconhece pastas e módulos acima do __main__ por padrão
->  O python conhece todos os módulos e pacotes presentes nos caminhos de sys.path
'''

import aula97_m

print('Este módulo se chama', __name__)

# arquivo separado -> aula97_m

print('Este módulo se chama', __name__)

❎ 155 - Como importar coisas do seu próprio módulo (ponto de vista do __main__)

...
print('Este módulo se chama', __name__)

print(aula97_m.variavel_modulo)
print(variavel_modulo)
print(soma(2, 3))
print(aula97_m.soma(2, 3))

# outro aquivos

print('Este módulo se chama', __name__)

variavel_modulo = 'Luiz'

def soma(x, y):
  return x +y

❎ 156 - Recarregando módulos, imporlib e singleton

import importlib

import aula98_m

print(aula98_m.variavel)

for i in range(10)
importlib.reload(aula98_m)
print(i)

print('Fim')

# outro arquivo

print(123)

variavel = 'Luiz 1'

❎ 157 - Introdução aos packages (pacotes) em Python

from sys import path

import aula99_pakage.modulo
from aula99_pakage import modulo
from aula_package.modulo import *

from aula99_packege.modulo import soma_do_modulo

print(*path, sep='\n')
print(soma_do_modulo(1, 2))
print(aula99_package.modulo.soma_do_modulo(1, 2))
print(modulo.soma_do_modulo(1, 2))
print(variavel)
print(nova_variavel)

# outro arquivo

__all__ = [
'variavel',
'soma_do_modulo',
'nova_variavel',
]

variavel = 'Alguma coisa'

def soma_do_modulo(x, y)
return x + y

nova_variavel = 'ok'

❎ 158 - O ponto de vista do __main__ pode te confundir em módulos e pacotes python

from sys import path

import aula99_pakage.modulo
from aula99_pakage import modulo
from aula_package.modulo import *

from aula99_packege.modulo import soma_do_modulo

print(*path, sep='\n')
print(soma_do_modulo(1, 2))
print(aula99_package.modulo.soma_do_modulo(1, 2))
print(modulo.soma_do_modulo(1, 2))
print(variavel)
print(nova_variavel)

from aula99_package.modulo import fala_oi, soma_do_modulo

print(__name__)

fala_oi()

# outro arquivo

__all__ = [
'variavel',
'soma_do_modulo',
'nova_variavel',
]

from aula99_package.modulo_b import fala_oi

variavel = 'Alguma coisa'

def soma_do_modulo(x, y)
return x + y

nova_variavel = 'ok'

# mais outro arquivo

def fala_oi():
print('oi')

❎ 159 - __init__.py é um arquivo de inicialização dos packages em Python

  # https://stackoverflow.com/questions/2386714/why-is-import-bad

from aula99_package.modulo import *
from aula99_package.modulo_b import *

❎ 160 - Proposta de 3 exercícios em um

📍 Exercício

'''
->  copy, sorted , produtos.sort
-------------------------------------
Exercício
->  Aumente os preços dos produtos a seguir em 10%
->  Gere novos_produtos por deep copy (cópia profunda)
'''

produtos = [
{'nome': 'Produto 5', 'preco': 10.00},
{'nome': 'Produto 1', 'preco': 22.32},
{'nome': 'Produto 3', 'preco': 10.11},
{'nome': 'Produto 2', 'preco': 105.87},
{'nome': 'Produto 4', 'preco': 69.90},
]

'''
-> Ordene os produtos por nome decrescente (do maior para menor)
-> Gere produtos_ordenados_por_nome por deep copy (cópia profunda)
'''

'''
->  Ordene os produtos por preco crescente (do menor para maior)
->  Gere produtos_ordenados_por_preco por deep copy (cópia profunda)
'''

import copy

novos_produtos = [
  {**produto, 'preco': round(produto['preco'] * 1.1, 2)}
  for produto in copy.deepcopy(produtos)
]


def ordernar(lista):
  for it in lista:
      print(it)
  print()


produtos_ordenados_por_nome = sorted(copy.deepcopy(produtos), key=lambda item: item['nome'], reverse=True)

produtos_ordenados_por_preco = sorted(
  copy.deepcopy(produtos), key=lambda item: item['preco'])

print(novos_produtos)

print("-"*20)

print(ordernar(produtos_ordenados_por_nome))

print("-"*20)

print(ordernar(produtos_ordenados_por_preco))

❎ 161 - Solução - Proposta de 3 exercícios em um

🎓 Resposta do professor

# __init__
from dados.produtos_moduto import produtos


import copy

from dados import produtos

#  esta no módulo produtos
produtos = [
{'nome': 'Produto 5', 'preco': 10.00},
{'nome': 'Produto 1', 'preco': 22.32},
{'nome': 'Produto 3', 'preco': 10.11},
{'nome': 'Produto 2', 'preco': 105.87},
{'nome': 'Produto 4', 'preco': 69.90},
]

# 1-
novos_produtos = [
  {**p, 'preco': round(p['preco'] * 1.1, 2)}
  for p in copy.deepcopy(produtos)
]

print(*produtos, sep='\n')
print()
print(*novos_produtos, sep='\n')

# 2-
produtos_ordenados_por_nome = serted(
  copy.deepcopy(produtos),
  key=lambda p: p['nome'],
  reverse=True
)

print(*produtos, sep='\n')
print()
print(*produtos_ordenados_por_nome, sep='\n')

# 3-
produtos_ordenados_por_preco = sorted(copy.deepcopy(produtos), key=lambda p: p['preco'])

print(*produtos, sep='\n')
print()
print(*produtos_ordenados_por_preco, sep='\n')

❎ 162 - Exercício - Adiando execução de funções

📍 Exercício

def soma(x, y):
return x + y

def multiplica(x, y):
return x * y

def executa(funcao, *args):
return funcao(*args)


soma_com_cinco = executa(soma, 5)
multiplica_por_dez = executa(multiplica, 10)

❎ 163 - Solução - Exercício - Adiando execução de funções

🎓 Resposta do professor

def soma(x, y):
return x + y

def multiplica(x, y):
return x * y


def executa(funcao, x):
def interna(y):
    return funcao(x, y)
return interna


soma_com_cinco = executa(soma, 5)
multiplica_por_dez = executa(multiplica, 10)

❎ 164 - Variáveis livres + nonlocal (locals, globais)

print(globals())

def fora(x):
  a = x

def dentro();
  print(locals())

  return a
  return dentro

dentro_1 = fora(10)
dentro_2 = fora(20)

print(dentro_1())
print(dentro_2())

def concatenar(string_inicial):
  valor_final = string_inicial

def interna(valor_a_concatenar=''):
  nonlocal valor_final
  valor_final += valor_a_concatenar
  return valor_final
return interna

c = concatenar('a')

print(c('b'))
print(c('c'))
print(c('d'))

final = c()

print(final)

❎ 165 - Funções decoradoras em geral

'''
->  Decorar = adicionar / Remover / Restringir / Alterar
->  Funções decotadoras são funções que decoram outras funções
->  Descoradores são usados para fazer o Python usar as
      funções decoradora em outras funções
'''

def criar_funcao(func):
def interna(*args, **kwargs):
  print('Vou te decorar')

  for arg in args:
    e_string(arg)

  resultado = func(*args, **kwargs)

  print(f'O seu resultado foi {resultado}.')
  print('Ok, agora você foi decorada')
  return resultado
return interna

def inverte_string(string):
return string[::-1]

def e_string(param):
if not isinstance(param, str):
  raise TypeError('param deve ser uma string')

inverte_string_checando_parametro = cria_funcao(inverte_string)
invertida = inverte_string_checando_paramentro('123')

print(invertida)

❎ 166 - Decoradores em Python (@syntax_sugar)

'''
->  Decorar = adicionar / Remover / Restringir / Alterar
->  Funções decotadoras são funções que decoram outras funções
->  Descoradores são usados para fazer o Python usar as
      funções decoradores em outras funções
->  Decoradores são "Syntax Sugar" (Açúcar sintático)
'''

def criar_funcao(func):
def interna(*args, **kwargs):
  print('Vou te decorar')

  for arg in args:
    e_string(arg)

  resultado = func(*args, **kwargs)

  print(f'O seu resultado foi {resultado}.')
  print('Ok, agora você foi decorada')
  return resultado
return interna

@criar_funcao
def inverte_string(string):
print(f'{inverte_string.__name__}')
return string[::-1]

def e_string(param):
if not isinstance(param, str):
  raise TypeError('param deve ser uma string')


invertida = inverte_string('123')

print(invertida)

❎ 167 - Decoradores com parâmetros

def fabrica_de_decoradores(a=None, b=None, c=None):
def fabrica_de_funcoes(func);
  print('Decoradora 1')

  def aninhada(*args, **kwargs):
    print('Parâmetro do decorador, ', a, b, c)
    print('Aninhada')
    res = func(*args, **kwargs)
    return res
  return aninhada
return fabrica_de_funcoes


@fabrica_de_decoradores(1, 2, 3)
def soma(x, y):
return x + y

decoradora = fabrica_de_decoradores()
multiplica = decoradora(lambda x, y: x * y)

dez_mais_cinco = soma(10, 5)
dez_vezes_cinco = multiplica(10, 5)

print(dez_mais_cinco)
print(dez_vezes_cinco)

❎ 168 - Ordem de aplicação dos decoradoras

def parametros_decorador(nome):
def decorador(func):
  print('Decorador:', nome)

  def sua_nova_funcao(*args, **kwargs):
    res = func(*args, **kwargs)
    final = f'{res} {nome}'
    return final
  return sua_nova_funcao
return decorador


@parametros_decorador(nome='5')
@parametros_decorador(nome='4')
@parametros_decorador(nome='3')
@parametros_decorador(nome='2')
@parametros_decorador(nome='1')
def soma(x, y):
return x + y

dez_mais_cinco = soma(10, 5)

print(dez_mais_cinco)

❎ 169 - Exercício - Unir listas

📍 Exercício

'''
->  Crie uma função zipper (como o zíper de roupas)
->  O trabalho dessa função será unir duas listas na ordem.
->  Use todos os valores da menor lista.
    Exe.:

    ['Salvador', 'Ubatuba', 'Belo Horizonte']
    ['BA', 'SP', 'MG', 'RJ']

    Resultado.:

    [('Salvador', 'BA'), ('Ubatuba', 'SP'), ('Belo Horizonte', 'MG')]
'''

❎ 170 - Solução dO Exercício + zip e zip_longest do intertools

🎓 Resposta do professor

def zipper(lista_1,lista_2):
intervalo_max = min(len(lista_1) , len(lista_2))

return [
  (lista_1[i], lista_2[i]) for i in range(intevalo_max)
]

print(zipper(l_1,l_2))


from itertools import zip_longest

l_1 = ['Salvador', 'Ubatuba', 'Belo Horizonte']
l_2 = ['BA', 'SP', 'MG', 'RJ']

print(list(zip(l_1, l_2)))
print(list(zip_longest(l_1, l_2, fillvalue='SEM CIDADE')))

❎ 171 - Exercícios - somando duas listas

📍 Exercício e 🎓 Resposta do professor

'''
->  Considerando duas lista de inteiros ou floats (lista A e lista B)
->  Some os valores nas lista retornando uma nova lista com os valores somados:

->  Se uma lista for mais que a outra, a soma só vai considerar o tamanho menor.

Exemplo:

lista_a = [1, 2, 3, 4, 5, 6, 7]
lista_b = [1, 2, 3, 4]
----------------------------------------
Resultado:

lista_soma = [2, 4, 6, 8]
'''

lista_a = [10, 2, 3, 40, 5, 6, 7]
lista_b = [1, 2, 3, 4]

lista_soma = [x + y for x, y in zip(lista_a, lista_b)]

print(lista_soma)


# |

lista_soma = []

for i in range(len(lista_b)):
lista_soma.append(lista_a[i] + lista_b[i])

print(lista_soma)

# |

lista_soma = []

for i, _ in enumerate(lista_b):
lista_soma.append(lista_a[i] + lista_b[i])

print(lista_soma)

❎ 172 - Exercício solucionado - somando listas

📍 Exercício 🎓 Resposta do professor

🎀 No exercício anterior, fizemos a soma de uas lista usando várias soluções diferentes.

🎀 Uma delas foi usando zip para unir duas lista e utilizar list comprehension para fazer a conta:

lista_a = [10, 2, 3, 4, 5]
lista_b = [12, 2, 3, 6, 50, 60, 70]

lista_soma = [x + y for x, y in zip(lista_a, lista_b)]

print(lista_soma)  # saída: [22, 4, 6, 10, 55]

🎀 O problema é que zip só une as lista até p tamanho da menor lista (como proposto no exercício).

🎀 Uma outra possibilidade é usar zip_longest para capturar os valores da lista maior.

🎀 A ideia é a mesma, veja:

from itertools import zip_longest

lista_a = [10, 2, 3, 4, 5]
lista_b = [12, 2, 3, 6, 50, 60, 70]

lista_soma = [x + y for x, y in zip_longest(lista_a, lista_b, fillvalue=0)]

print(lista_soma) # saída [22, 4, 6, 10, 55, 60, 70]

🎀 Neste caso, usamos o fillvalue como 0 (zero), assim conseguimos capturar os valores restantes da lista maior, realizando conta, sem obter um erro em nosso programa.

❎ 173 - count é um iterador sem fim (intertools)

from itertools import count

c_1 = count(step=8, start=8)
r_1 = range(8, 100, 8)

print('c_1', hasattr(c_1, '__iter__'))
print('c_1', hasattr(c_1, '__next__'))
print('r_1', hasattr(r_1, '__iter__'))
print('r_1', hasattr(r_1, '__next__'))

print('count')

for i in c_1:
  if i >= 100:
    break
  print(i)

print()
print('range')

for i in r_1:
  print(i)

❎ 174 - Combinations, Permutations e Product - intertools

'''

->  Combinação - Ordem não importa - iterável + tamanho do grupo
->  Permutação - Ordem importa
-> Produto - Ordem importa e repete valores únicos
'''

from itertools import combinations , permutations, product

def print_iter(iterador):
  print(*list(interador), sep='\n')
  print()

pessoas = [
  'João', 'Joana', 'Luiz', 'Letícia',
]
camisetas = [
  ['preta', 'branca'],
  ['p', 'm', 'g'],
  ['Masculino', 'feminino', 'unisex'],
  ['algodão', 'poliéster']
]

print_iter(combinations(pessoas, 2))
print_iter(permutations(pessoas, 2))
print_iter(product(*camisetas))

❎ 175 - Groupby - agrupando valores (intertools)

from itertools import groupby

alunos = [
  {'nome': 'Luiz', 'nota': 'A'}
  {'nome': 'Letícia', 'nota': 'B'}
  {'nome': 'Fabrício', 'nota': 'A'}
  {'nome': 'Rosemary', 'nota': 'C'}
  {'nome': 'Joana', 'nota': 'D'}
  {'nome': 'João', 'nota': 'A'}
  {'nome': 'Eduardo', 'nota': 'B'}
  {'nome': 'André', 'nota': 'A'}
  {'nome': 'Anderson', 'nota': 'C'}
]

def ordena(aluno):
  return aluno['nota']

alunos_agrupados = sorted(alunos, key=ordena)
grupos = groupby(alunos_agrupados, key=ordena)

for chave, grupo in grupos:
  print(chave)
for aluno in grupo:
  print(aluno)

❎ 176 - map, partial, GeneratorType e esgotamento de Iterators

from functools import partial
from types import GeneratorType

# map - para mapear dados
def print_iter(iterator):
print(*list(iterator), sep='\n')
print()

produtos = [
  {'nome': 'Produto 5', 'preco': 10.00},
  {'nome': 'Produto 1', 'preco': 22.32},
  {'nome': 'Produto 3', 'preco': 10.11},
  {'nome': 'Produto 2', 'preco': 105.87},
  {'nome': 'Produto 4', 'preco': 69.90},
]


def aumentar_porcentagem(valor, porcentagem):
  return round(valor * porcentagem, 2)

aumentar_dez_porcento = partial(
aumentar_porcentagem,
porcentagem=1.1
)

novos_produtos = [
  {**p, 'preco': aumentar_dez_porcento(p['preco'])}
  for p int produtos
]


def muda_preco_de_produtos(produto):
  return{
    **produto,'preco': aumentar_dez_porcento(produto['preco'])
  }

novos_produtos = list(map(
  muda_preco_de_produtos,
  produtos
))

print_iter(produtos)
print_iter(novos_produtos)

print(list(map(lambda x: x * 3, [1, 2, 3, 4])))


produtos = [
  {'nome': 'Produto 5', 'preco': 10.00},
  {'nome': 'Produto 1', 'preco': 22.32},
  {'nome': 'Produto 3', 'preco': 10.11},
  {'nome': 'Produto 2', 'preco': 105.87},
  {'nome': 'Produto 4', 'preco': 69.90},
]

def filtrar_preco(produto):
  return produto['preco'] > 100



novos_produtos = [
  p for p in produtos
  if p['preco'] > 100
]


novos_produtos = filter(
  # lambda produto: produto[''] > 100,
  filtrar_preco,
  produtos
)

print_iter(produtos)
print_iter(novos_produtos)

❎ 177 - filter é um filtro funcional

def print_iter(iterator):
print(*list(iterator), sep='\n')
print()


produtos = [
  {'nome': 'Produto 5', 'preco': 10.00},
  {'nome': 'Produto 1', 'preco': 22.32},
  {'nome': 'Produto 3', 'preco': 10.11},
  {'nome': 'Produto 2', 'preco': 105.87},
  {'nome': 'Produto 4', 'preco': 69.90},
]


def filtrar_preco(produto):
  return produto['preco'] > 100


novos_produtos = [
  p for p in produtos
  if p['preco'] > 100
]


novos_produtos = filter(
  # lambda produto: produto['preco'] > 100,
  filtrar_preco,
  produtos
)


print_iter(produtos)
print_iter(novos_produtos)

❎ 178 - reduce - faz a redução de um iterável em um valor

from functools import reduce

produtos = [
  {'nome': 'Produto 5', 'preco': 10.00},
  {'nome': 'Produto 1', 'preco': 22.32},
  {'nome': 'Produto 3', 'preco': 10.11},
  {'nome': 'Produto 2', 'preco': 105.87},
  {'nome': 'Produto 4', 'preco': 69.90},
]


def funcao_do_reduce(acumulador, produto):
  print('acumular', acumulador)
  print()
  return acumulador + produto['preco']

total = reduce(
  lambda ac, p: ac + p['preco']
  produtos,
  0
)

print ("total é", total)


total = 0
for p in produtos:
  total += p['preco']

print(total)


print(sum([p['preco'] for p in produtos]))

❎ 179 - Funções recursivas, recursividade e Stack Overflow

'''
->  funções que podem se chamar de volta
->  úteis para dividir problemas grandes em partes menores

->  Toda função recursiva deve ter:
->  Um problema que possa ser dividido em partes menores
->  Um caso recursivo que resolve o pequeno problema
->  Um caso base que para a recursão

fatorial - n! = 5! = 5 * 4 * 3 * 2 * 1

https://brasilescola.uol.com.br/matematica/fatorial.htm
'''

def recursiva(inicio=0, fim=4)

print (inicio, fim)

# caso base
if inicio >= fim:
return fim

# caso recursivo
# contar até chegar ao final

inicio += 1
return recursiva(inicio, fim)

print(recursiva())

❎ 180 - Limite de recursão e cuidados com funções recursivas

...

# import sys

# sys.setrecursionlimit(1004)

# def recursiva(inicio=0, fim=4)

#   print (inicio, fim)

#   # caso base
#   if inicio >= fim:
#     return fim

#   # caso recursivo
#   # contar até chegar ao final

#   inicio += 1
#   return recursiva(inicio, fim)

# print(recursiva(0,1000))

def factorial(n):

if n == 1 or n <=1:
return 1

return n * factorial(n - 1)

❎ 181 - O que são ambientes virtuais?(venv)

'''
->  Um ambiente virtual carrega toda a sua instalação
      do Python para um pasta no caminho escolhido.
->  Ao ativar um ambiente virtual, a instalação do
      ambiente virtual será usada.
->  venv é o módulo que vamos usar para
      criar ambientes virtuais
->  Você pode dar o nome que preferir para um
      ambiente virtual, mas os mais são:
    venv  env   .venv   .env

'''

❎ 182 - Como criar o seu ambiente virtual venv

'''
->  Abra a pasta do seu projeto no terminal
      e digite:

    python -m venv venv

    gmc -Syntax

    which
'''

❎ 183 - Ativando e desativando

'''
->  Windows .\venv\Scripts\ativate
->  Linux e Mac: souce venv/bin/avtivate
->  Desativar: deactivate
'''

❎ 184 - pip - instalando pacotes e bibliotecas

'''
->  Instalando úma versão:
---------------------------------------------
->  pip install nome_pacote
->  Instalar versão precisa
->  (tem outras formas também não mencionadas)
->  pip install nome_pacote==0.0.0
->  Desinstalar pacote
------------------------------------------------
->  pip uninstall nome_pacote
->  Congelar (ver pacotes)
---------------------------------------------------
->  pip freeze

->  pip index version nome_pacote
'''

❎ 185 - Criando e usando um requirements.txt

'''
->  pip freeze > requirements.txt
->  pip install -r requirements.txt

'''

❎ 186 - Criando arquivos com Python + Context Manager with

'''

->  Usando a função open para abrir um arquivo em Python
    (ele pode ou não existir)
  Modos:

->  r (leitura), w (escrita), x (para criação)
->  a (escreve ao final), b (binário)
->  t (modo texto), + (leitura e escrita)

->  Contexto manager - with (abri e fecha)
-----------------------------------------------------
->  Métodos úteis
-----------------------------------------------------
->  write, red (escrever e ler)
->  writelines (escrever e várias linhas)
->  seek (move o cursor)
->  readline (ler linhas)

->  Vamos falar mais sobre o módulo os, mas:
--------------------------------------------------------
->  os.remove() ou unlink - apaga arquivo
->  os.rename() - troca o nome ou move o arquivo

->  Vamos falar mais sobre o módulo json, mas:
---------------------------------------------------------
->  json.dump()   - Gera uma arquivo json
->  json.load

'''

caminho_arquivo = 'aula_116.txt'

arquivo = opem(caminho_arquivo, 'w')
arquivo.close()

with open(caminho_arquivo, 'w') as arquivo:
print('Olá mundo')
print('Aquivo vai ser fechado')

❎ 187 - with open (context manager) e métodos úteis do TextIOWrapper

'''
->  Usando a função open para abrir um arquivo em Python
    (ele pode ou não existir)
  Modos:

->  r (leitura), w (escrita), x (para criação)
->  a (escreve ao final), b (binário)
->  t (modo texto), + (leitura e escrita)

->  Contexto manager - with (abri e fecha)
-----------------------------------------------------
->  Métodos úteis
-----------------------------------------------------
->  write, red (escrever e ler)
->  writelines (escrever e várias linhas)
->  seek (move o cursor)
->  readline (ler linhas)

->  Vamos falar mais sobre o módulo os, mas:
--------------------------------------------------------
->  os.remove() ou unlink - apaga arquivo
->  os.rename() - troca o nome ou move o arquivo

->  Vamos falar mais sobre o módulo json, mas:
---------------------------------------------------------
->  json.dump()   - Gera uma arquivo json
->  json.load
'''
caminho_arquivo = 'aula_116.txt'

# caminho_arquivo = 'aula_116.txt'

# arquivo = opem(caminho_arquivo, 'w')
# arquivo.close()



with open(caminho_arquivo, 'w') as arquivo:
print('Olá mundo')
print('Aquivo vai ser fechado')


with open(caminho_arquivo, 'w+') as arquivo:
arquivo.write('Linha 1\n')
arquivo.write('Linha 2\n')
arquivo.writelines(
('Linha 3\n', 'Linha 4\n')
)
arquivo.seek(0, 0)
print(arquivo.read())
print('Lendo')
arquivo.seek(0, 0)
print(arquivo.readline(), end='')
print(arquivo.readline().strip())
print(arquivo.readline().strip())

print('READLINES')
arquivo.seek(0, 0)

for linha in arquivo.readlines()
print(linha.strip())

print('#' * 10)

with opem(caminho_arquivo, 'r') as arquivo:
print(arquivo.read())

❎ 188 - Modos de abertura de arquivos e encoding com with open

'''
Leia também: https://www.otaviomiranda.com.br/2020/normalizacao-unicode-em-python/

'''

# with open(caminho_arquivo, 'w+') as arquivo:
# arquivo.write('Linha 1\n')
# arquivo.write('Linha 2\n')
# arquivo.writelines(
#   ('Linha 3\n', 'Linha 4\n')
# )
# arquivo.seek(0, 0)
# print(arquivo.read())
# print('Lendo')
# arquivo.seek(0, 0)
# print(arquivo.readline(), end='')
# print(arquivo.readline().strip())
# print(arquivo.readline().strip())

# print('READLINES')
# arquivo.seek(0, 0)

# for linha in arquivo.readlines()
#   print(linha.strip())

# print('#' * 10)

# with opem(caminho_arquivo, 'r') as arquivo:
#   print(arquivo.read())

caminho_arquivo = 'aula_116.txt'

with open(caminho__arquivo, 'w', encoding='utf8') as arquivo:
arquivo.write('Atenção\n')
arquivo.write('Linha 1\n')
arquivo.write('Linha 2\n')
arquivo.writelines(
('Linha 3\n','Linha 4\n')
)

❎ 189 - Os.remove, os.unlink r os.rename - apagando, renomeando e movendo arquivos

'''
  Leia também: https://www.otaviomiranda.com.br/2020/normalizacao-unicode-em-python/

->  with open(context manager) e Métodos úteis do TexrIOWrapper
----------------------------------------------------------------
->  Usando a função open para abrir um arquivo em Python
      (ele pode ou não existir)
    Modos:

->  r (leitura), w (escrita), x (para criação)
->  a (escreve ao final), b (binário)
->  t (modo texto), + (leitura e escrita)

->  Contexto manager - with (abri e fecha)
-----------------------------------------------------
->  Métodos úteis
-----------------------------------------------------
->  write, red (escrever e ler)
->  writelines (escrever e várias linhas)
->  seek (move o cursor)
->  readline (ler linhas)

->  Vamos falar mais sobre o módulo os, mas:
--------------------------------------------------------
->  os.remove() ou unlink - apaga arquivo
->  os.rename() - troca o nome ou move o arquivo

->  Vamos falar mais sobre o módulo json, mas:
---------------------------------------------------------
->  json.dump()   - Gera uma arquivo json
->  json.load
'''
import os

caminho_arquivo = 'aula_116.txt'

with open(caminho__arquivo, 'w', encoding='utf8') as arquivo:
  arquivo.write('Atenção\n')
  arquivo.write('Linha 1\n')
  arquivo.write('Linha 2\n')
  arquivo.writelines(
    ('Linha 3\n','Linha 4\n')
  )

  os.remove(caminho_arquivo) # ou unlink
  os.rename(caminho_arquivo, aula_116-2.txt)

❎ 190 - Salvando dados Python em JSON com módulo json

{
"nome": "Luiz Otávio 2",
"sobrenome": "Miranda",
"enderecos": [
  {"rua": "R1" , "numero": 32},
  {"rua": "R2" , "numero": 55}
  ],
  "altura":1.8,
  "numeros_preferidos":[2, 4, 8, 10],
  "dev": true,
  "nada": null
}
import json

pessoa = {
"nome": "Luiz Otávio 2",
"sobrenome": "Miranda",
"enderecos": [
  {"rua": "R1" , "numero": 32},
  {"rua": "R2" , "numero": 55}
  ],
  "altura":1.8,
  "numeros_preferidos":(2, 4, 8, 10),
  "dev": true,
  "nada": None
}

with open('aula_117.json', 'w', encoding='utf8') as arquivo:
json.dump(
pessoa,
arquivo,
ensure_ascii=False,
indent=2,
)

with open('aula_117.json', 'r', encoding='utf8') as arquivo:
pessoa = json.load(arquivo)
print(pessoa)
print(type(pessoa))
print(pessoa['nome'])

https://www.otaviomiranda.com.br/2020/normalizacao-unicode-em-python/

❎ 191 - Problema dos parâmetros mutáveis em funções Python

def adiciona_clientes(nome, lista=None):
if lista is None:
  lista = []
lista.append(nome)
return lista


cliente_1 = adiciona_clientes('luiz')
adiciona_clientes('Joana', cliente_1)
adiciona_clientes('Fernando', cliente_1)
cliente_1.append('Edu')

  cliente_2 = adiciona_clientes('Helena')
  adiciona_clientes('Maria', cliente_2)

  cliente_3 = adiciona_clientes('Moreira')
  adiciona_clientes('Vivi', cliente_3)

  print(cliente_1)
  print(cliente_2)
  print(cliente_3)

❎ 192 - Exercício - Lista de tarefas com opções de desfazer e refazer

📍 Exercício

  '''
    ->  Música para codar 🙂
    ->  Everybody wants to rule the world - Tears for fears

    ->  todo = [] lista de tarefas
    ->  todo = ['fazer café'] -> Adicionar fazer café
    ->  todo = ['fazer café', 'caminhar'] -> adicionar caminhar
    ->  desfazer = ['fazer café'] -> Refazer ['caminhar']
    ->  desfazer = [] -> Refazer['caminhar', 'fazer café']
    ->  refazer = todo ['fazer café']
    ->  refazer = todo ['fazer café', 'caminhar']

      Rober Duck The Bug 🗣️ 🦆
  '''

❎ 193 - Solução Exercício - Lista de tarefas com opções de desfazer e refazer

🎓 Resposta do professor

  import os

  def listar(tarefas):
    print()
    if not tarefas:
      print('Nenhuma tarefa para listar')
      return

    print('Tarefas:')
    for tarefa in tarefas:
      print(f'\t{tarefa}')
    print()

  def desfazer(tarefas, tarefas_refazer):
    print()
    if not tarefas:
      print('Nenhuma tarefa para desfazer')
      return

    tarefa = tarefas.pop()
    print(f'{tarefa=} removida da lista de tarefas.')
    tarefas_refazer.append(tarefa)
    print()

  def refazer(tarefas, tarefas_refazer):
    print()
    if not tarefas_refazer:
      print('Nenhuma tarefa para refazer')
      return

    tarefa = tarefas_refazer.pop()
    print(f'{tarefa=} removida da lista de tarefas.')
    tarefas.append(tarefa)
    print()

  def adicionar(tarefa, tarefas):
    print()
    tarefa = tarefa.strip()
    if not tarefas_refazer:
      print('Você não digitou nenhuma tarefa')
      return

    print(f'{tarefas=} adicionada na lista de tarefas.')
    tarefas.append(tarefa)
    print()

  tarefas = []
  tarefas_refazer = []

  while True:
    print('Comandos:listar, desfazer refazer')
    tarefa = input('Digite uma tarefa ou comando:')

    if tarefa == 'listar':
      listar(tarefas)
      continue
    elif tarefa == 'desfazer':
      desfazer(tarefas, tarefas_refazer)
      lista(tarefas)
      continue
    elif tarefa == 'clear':
      os.system('cls')
      continue
    else:
      adicionar(tarefa, tarefas)
      listar(tarefas)
      continue

❎ 194 - Evitando uso de condicionais + Guard Clause

...

while True:
  print('Comandos: listar, desfazer, refazer')
  tarefa = input('Digite uma trarefa ou comando:')

  comando = {
    'listar': lambda: listar(tarefas)
    'desfazer': lambda: desfazer(tarefas, tarefas_refazer)
    'refazer': lambda: refazer(tarefas, tarefas_refazer)
    'clear': lambda: os.system('cls')
    'adicionar': lambda: adicionar(tarefa, tarefas)
  }


  comando = comando.get(tarefa) if comando.get(tarefa) is not None else comando['adicionar']

  comando()

❎ 195 - Exercício (+solução) - salvando a lista de tarefas em JSON

  '''
    ->  Música para codar 🙂
    ->  Everybody wants to rule the world - Tears for fears

    ->  todo = [] lista de tarefas
    ->  todo = ['fazer café'] -> Adicionar fazer café
    ->  todo = ['fazer café', 'caminhar'] -> adicionar caminhar
    ->  desfazer = ['fazer café'] -> Refazer ['caminhar']
    ->  desfazer = [] -> Refazer['caminhar', 'fazer café']
    ->  refazer = todo ['fazer café']
    ->  refazer = todo ['fazer café', 'caminhar']
  '''

  import json
  import os

  def listar(tarefas):
      print()
      if not tarefas:
          print('Nenhuma tarefa para listar')
          return

      print('Tarefas:')
      for tarefa in tarefas:
          print(f'\t{tarefa}')
      print()


  def desfazer(tarefas, tarefas_refazer):
      print()
      if not tarefas:
          print('Nenhuma tarefa para desfazer')
          return

      tarefa = tarefas.pop()
      print(f'{tarefa=} removida da lista de tarefas.')
      tarefas_refazer.append(tarefa)
      print()
      listar(tarefas)


  def refazer(tarefas, tarefas_refazer):
      print()
      if not tarefas_refazer:
          print('Nenhuma tarefa para refazer')
          return

      tarefa = tarefas_refazer.pop()
      print(f'{tarefa=} adicionada na lista de tarefas.')
      tarefas.append(tarefa)
      print()
      listar(tarefas)


  def adicionar(tarefa, tarefas):
      print()
      tarefa = tarefa.strip()
      if not tarefa:
          print('Você não digitou uma tarefa.')
          return
      print(f'{tarefa=} adicionada na lista de tarefas.')
      tarefas.append(tarefa)
      print()
      listar(tarefas)

  def ler(tarefas, caminho_arquivo):
    dados = []
    try:
      whith open(caminho_arquivo, 'r' encoding='utf8') as arquivo:
        dados = json.load(arquivo)
    except FileNotFoundError:
      print('Arquivo não existe')
      salvar(tarefas, caminho_arquivo)
    return dados


  def salvar(tarefas, caminho_arquivo):
    dados = tarefas
    with open(caminho_arquivo, 'w', encoding='utf8') as arquivo:
      dados = json.dump(tarefas, arquivo, indent=2, ensure_ascii=False)
    return dados

  CAMINHO_ARQUIVO = 'aula_119.json'
  tarefas = ler([], CAMINHO_ARQUIVO)
  tarefas_refazer = []

  while True:
    print('Comandos: listar, desfazer, refazer')
    trarefa = input('Digite uma trarefa ou comando:')

    comando = {
      'listar': lambda: listar(tarefas)
      'desfazer': lambda: desfazer(tarefas, tarefas_refazer)
      'refazer': lambda: refazer(tarefas, tarefas_refazer)
      'clear': lambda: os.system('cls')
      'adicionar': lambda: adicionar(tarefa, tarefas)
    }


  comando = comando.get(tarefa) if comando.get(tarefa) is not None else comando['adicionar']

  comando()
  salvar(tarefas, CAMINHO_ARQUIVO)

❎ 196 - Positional-Only Parameters (/) e Keyword-Only Arguments(*)

  '''
    ->  *args (ilimitado de argumentos posicionais)
    ->  **kwargs (ilimitado de argumentos nomeados)
    ->  ✨  Positional-only Parameters (/) - Tudo antes das barras deve
              ser ‼ APENAS ‼️ posicional.
    ->  PEP 570 - Python Positional-only Parameters
    ->  https://peps.python.org/pep-0570/
    ->  ✨  Keyword-Only Arquments (*) - * sozinho ‼️ NÃO SUGA ‼️ valores.
    ->  PEP 3102 Keyword-Only Arquments
    ->  https://peps.python.org/pep-3102/
  '''

  def soma(a, b, /, *, c, **kwargs):
    print(kwargs)
    print(a + b + c)


  soma(1, 2, c=3, nome='teste')

🔜 ❎ Seção 5 Introdução à Programação Orientada a Objeto em Python - POO (Classes)⚓︎


❎ 197 - Introdução à seção e livros de refazer

❎ 198 - class - Classes são moldes para criar novos objetos

  '''
    ->  As classes geram novos objetos (instâncias) que
          podem ter seus próprios atributos e métodos.
    ->  Os objetos gerados pela classe podem usar seus dados
          internos para realizar várias ações.
    ->  Por convenção, usamos PascalCase para nomes de
          classes.
    ->  string = 'Luiz' -> str
    print(string.upper())
    print(isinstance(string, str))
  '''

  class Pessoa:
    ...

  p_1 = Pessoa('Luiz', 'Otávio')
  p_1.nome = 'Luiz'
  p_1.sobrenome = 'Otávio'

  p_2 = Pessoa('Maria', 'Joana')
  p_2.nome = 'Maria'
  p_2.sobrenome = 'Joana'

  print(p_1.nome)
  print(p_1.sobrenome)

  print(p_2.nome)
  print(p_2.sobrenome)

❎ 199 - Introdução ao método __init__ (inicializador de atributos)

  '''
    ->  As classes geram novos objeto (instâncias) que podem
          ter seus próprios atributos e métodos.
    ->  Os objetos gerados pela classe de uma usar seus dados
          internos para realizar várias ações.
    ->  Por convenção, iramos PascalCase para nomes de classes.
    ->  string = 'Luiz' -> str
      print(string.upper())
      print(isinstance(string, str))
  '''

  class Pessoa:

    def __init__(self, nome, sobrenome):
      self.nome = nome
      self.sobrenome = sobrenome

  p_1 = Pessoa('Luiz','Otávio')


  p_2 = Pessoa('Maria','Joana')

  print(p_1.nome)
  print(p_1.sobrenome)

  print(p_2.nome)
  print(p_2.sobrenome)

❎ 200 - Métodos em instâncias de classes Python

  '''
    ->  Hard coded - É algo que foi escrito diretamente no código
  '''

  class Carro:

    def __init__(self, nome):
      self.nome = nome


    def acelerar(self):
      print(f'{self.nome} está acelerando...')



  string = 'Luiz'

  print(string.upper())

  fusca = Carro('Fusca')

  print(fusca.nome)

  fusca.acelerar()

  celta = Carro(nome='Celta')
  print(celta.nome)
  celta.acelerar()

❎ 201 - Entendendo self em classes Python

  '''
    ->  Classes - Malde (geralmente sem dados)
    ->  Instância da class (objeto) - Tem os dados
    ->  Uma classe pode gerar várias instâncias.
    ->  Na classe o self é a própria instância.
  '''

  class Carro:

    # 👉 (parameter) self: Self@Carro

    def __init__(self, nome):
      self.nome = nome

  def acelerar(self):
      print(f'{self.nome} está acelerando...')



  string = 'Luiz'

  print(string.upper())

  fusca = Carro('Fusca')

  print(fusca.nome)

  fusca.acelerar()

  celta = Carro(nome='Celta')

  print(celta.nome)
  celta.acelerar()
  Carro.acelerar(celta)

❎ 202 - Escopo da classe e de métodos da classe

  class Animal:

    # nome = 'Leão'

    def __init__(self, nome):
      self.nome = nome

      variavel = 'valor'

      print(variavel)

    def comendo(self, alimento):
      return f'{self.nome} está comendo {alimento}'

    def executar(self, *args, **kwargs):
      return self.comendo(*args, *kwargs)


  leao = Animal(nome='Leão')

  print(leao.nome)
  print(leao.executar('maçã'))

❎ 203 - Mantendo estado dentro da classe

  class Camera:

    def __init__(self, nome, filmando=False):
      self.nome =nome
      self.filmando = filmando


    def filmar(self):
      if self.filmando:
        print(f'{self.nome} JÁ esta filmando...')
        return

      print(f'{self.nome} está filmando...')
      self.filmando = True


    def parar_filmar(self):
      if not self.filmando:
        print(f'{self.nome} NÃO está filmando...')
        return

      print(f'{self.nome} está parando de filmar...')
      self.filmando = False


    def fotografar(self):
      if self.filmando:
        print(f'{self.nome} não pode fotografar filmando')
        return

      print(f'{self.nome} está fotografando')




  c_1 = Camera('Canon')
  c_2 = Camera('Sony')

  c_1.filmar()
  c_1.filmar()
  c_1.fotografar()
  c_1.parar_filmar()
  c_1.fotografar()

  print()

  c_2.filmar()
  c_2.filmar()
  c_2.fotografar()
  c_2.parar_filmar()
  c_2.fotografar()

❎ 204 - Atributos de classe

  class Pessoa:
    ano_atual = 2023

    def __init__(self, nome, idade):
      self.nome = nome
      self.idade = idade


    def get_ano_nascimento(self):
      return Pessoa.ano_atual - self.idade


  p_1 = Pessoa('João', 35)

  p_2 = Pessoa('Helena' 12)

  print(p_1.get_ano_nacimento())

  print(p_2.get_ano_nacimento())

❎ 205 - __dict__ e vars para atributos de instância

  class Pessoa:
    ano_atual = 2023

    def __init__(self, nome, idade):
      self.nome = nome
      self.idade = idade


    def get_ano_nascimento(self):
      return Pessoa.ano_atual - self.idade

  dados = {'nome': 'João', 'idade': 35}

  p_1 = Pessoa(**dados)

  p_1.nome = 'EITA'

  p_1.__dict__['outra'] = 'coisa'
  p_1.__dict__['nome'] = 'Eita'

  del p_1.__dict__['nome']

  print(p_1.__dict__)

  print(vars(p_1))

  print(p_1.outra)
  print(p_1.nome)

  print(vars(p_1))

  print(p_1.nome)

❎ 206 - Exercício - Salve sua classe em JSON

📍 Exercício

  '''
    ->  Salve os dados da sua classe em Json
          e depois crie novamente as instâncias
          da classe com os dados salvos
    ->  Faça em arquivos separados.
  '''

❎ 207 - Solução - Exercício + if __name__ == '__main__'

🎓 Resposta do professor

'''
  ->  arquivo main parte_1
'''
import json

CAMINHO_ARQUIVO = 'aula_127.json'

class Pessoa:

  def __init__(self, nome, idade):
    self.nome = nome
    self.idade = idade



p_1 = Pessoa('João', 33)
p_2 = Pessoa('Helena', 21)
p_3 = Pessoa('Joana', 11)

bd = [vars(p_1), p_2.__dict__, vars(p_3)]


def fazer_dump():
  with open(CAMINHO_ARQUIVO, 'w') as arquivo:
    print('FAZENDO DUMP')

    json.dump(
      bd,
      arquivo,
      ensure_ascii=False,
      indent=2
    )

  if __name__ == '__main__':
    print(ELE É O __main__)

    fazer_dump()

# arquivo parte_2

import json

from aula_127_a import  CAMINHO_ARQUIVO, Pessoa, fazer_dump

# fazer_dump()

with open(CAMINHO_ARQUIVO, 'r') as arquivo:
  pessoas = json.load(arquivo)

  p_1 = Pessoa(**pessoa[0])
  p_2 = Pessoa(**pessoa[1])
  p_3 = Pessoa(**pessoa[2])


  print(p_1.nome, p_1.idade)
  print(p_2.nome, p_2.idade)
  print(p_3.nome, p_3.idade)

❎ 208 - Curiosidade sobre convenções de nomes

🎀 Como você viu na aula anterior, usamos certas convenções para nomes de variáveis, funções, classes e assim por diante. Essas convenções tem um nome que podemos usar para nos referir ao modo como estamos nomeando determinados objetos em nosso programa: PascalCase, camelCase e snake_case.

  • PascalCase - significa que todas as palavras iniciam com letra maiúscula e nada é usando para separá-las, como em: MinhaClasse,Classe,MeuObjeto,MeuProgramaMuitoLegal. Essa á a convenção utilizada para classes em Python;
  • camelCase - a única diferença de camelCase para PascalCase é a primeira letra. Em camelCase a primeira letra sempre será minúscula e o restante das palavras deverá iniciar com a letra maiúscula. Como em: minhaFuncao, funcaoDeSoma, etc... Essa conversão não é usada em Python (apesar de eu confundir as duas às vezes acabar) chamando camelCase de PascalCase ou vice-versa, mas agora você sabe a diferença);
  • snake_case - este é o padrão usado em Python para definir qualquer coisa que não for uma classe. Todas as letras serão minúsculas e separadas por um underline, como em: minha_cariavel, funcao_legal, soma.

🎀 Os padrões usados em Python são: snake_case para qualquer coisa e PascalCase para classe.

❎ 209 - Métodos de classe (@classmethod) + factories methods (métodos fábrica)

  '''
    ->  Métodos de classe + factories (fábricas)
    ->  São métodos ender "self" será "cls", ou seja,
          ao invés de receber a instância no primeiro
          parâmetro, receberemos a própria classe.
  '''

  class Pessoa:
    ano = 2023 # atributo de classe

    def __init__(self, nome, idade):
      self.nome = nome
      self.idade = idade


    @classmethod
    def metodo_de_classe(cls):
      print('Hey')


    @classmethod
    def criar_com_anos(cls, nome):
      return cls(nome, 50)


    @classmethod
    def criar_sem_nome(cls, idade):
      return cls('Anônima', idade)




  p_1 = Pessoa('João', 34)
  p_2 = Pessoa.criar_com_50_anos('Helena')
  p_3 = Pessoa('Anônimo', 23)
  p_4 = Pessoa.criar_sem_nome(25)

  print(p_2.nome, p_2.idade)
  print(p_3.nome, p_3.idade)
  print(p_4.nome, p_4.idade)

  print(Pessoa.ano)

  Pessoa.metodo_de_classe()

❎ 210 - @staticmethod (métodos estáticos) são inúteis em Python 🙂

  '''
    ->  Métodos estáticos são métodos que estão dentro da
          classe, mas tem acesso não ao self nem ao cls.
    ->  Em resumo, são funções que existem dentro da sua classe.
  '''

  class Classe:

    @staticmethod
    def funcao_que_esta_na_classe(*args, **kwargs):
      print('Oi', args, kwargs)


    def funcao(*args, **kwargs):
      print('Oi', args, kwargs)




  c_1 = Classe()
  c_1.funcao_que_esta_na_classe(1, 2, 3)

  funcao(1, 2, 3)
  Classe.funcao_que_esta_na_classe(nomeado=1)
  funcao(nomeado=1)

❎ 211 - method vs @classmethod vs @staticmethod

  '''
    ->  method - self, método de instância
    ->  @classmethod - cls, método de classe
    ->  @staticmethod - método estático (❌self, ❌cls)
  '''

  class Connection:

    def __init__(self, host='localhost'):
      self.host = host
      self.user = None
      self.password = None


    def set_user(self,user):
      self.user = user


    def set_password(self, password):
      self.password = password


    @classmethod
    def create_whith_auth(cls, user, password):
      connection = cls()
      connection.user = user
      connection.password = password
      return connection


    @staticmethod
    def log(msg):
      print('LOG', msg)

    def connection_log(msg):
      print('LOG', msg)



    # c_1 = Connection()
    c_1 = Connercion.create_with_auth('Luiz', '123')
    # c_1.set_user('Luiz')
    # c_1.set_password('123')

    print(Connercion.log('Essa é a mensagem de log'))
    print(c_1.user)
    print(c_1.password)

❎ 212 - @property - um getter no modo Pythônico

  '''
    ->  getter -> um método para obter um atributo
    ->  cor - get_cor()
    ->  modo Pythônico - modo do Python de fazer coisas
    ->  @property é uma propriedade do objeto, ela é
          um método que se comporta como um atributo 🤯🤯🤯
    ->  Geralmente é usado nas seguintes situações:

    ->  como getter
    ->  para evitar quebrar código cliente
    ->  para habilitar setter
    ->  para executar ações ao obter um atributo

    ->  Código cliente - é o código que usa seu código
  '''

  class Caneta:

    def __init__(self, cor):
      self.cor_tinta = cor


    @property
    def cor(self, cor):
      print('PROPERTY')
      return self.cor_tinta

    @property
    def cor_tampa(self):
      return 123456




###################################################################

    coneta = Caneta('Azul')

    print(caneta.cor)
    print(caneta.cor)
    print(caneta.cor)
    print(caneta.cor)
    print(caneta.cor)
    print(caneta.cor)
    print(caneta.cor_tampa)



#########################################################################

    # class Caneta:

    #   def __init__(self,cor):
    #     self.cot_tinta = cor


    #   def get_cor(self):
    #     print('GET COR')
    #     return self.cor_tinta



############################################################################


    # caneta = Caneta('Azul')

    # print(caneta.get_cor())
    # print(caneta.get_cor())
    # print(caneta.get_cor())
    # print(caneta.get_cor())
    # print(caneta.get_cor())

❎ 213 - @property + @setter - getter e setter no modo Pythônico

  '''
    ->  como getter
    ->  para evitar quebrar código cliente
    ->  para habilitar setter
    ->  para executar ações ao obter um atributo
    ->  Atributos que começam com um ou dois underlines
          não devem ser usado fora da classe.
        🐍🤓🤯🤯🤯🤯
  '''

  class Caneta:

    def __init__(self, cor):
      # private protected
      self.cor = cor
      self._cor_tampa = None


    @property
    def cor(self):
      print('ESTOU NO GETTER')
      return self._cor


    @cor.setter
    def cor(self,valor):
      print('ESTOU NO SETTER')
      self._cor = valor


    @property
    def cor_tampa(self):
      return self._cor_tampa


    @cor_tampa.setter
    def cor_tampa(self, valor):
      self._cor_tampa = valor


  caneta = Caneta('Azul')
  caneta.cor = 'Rosa'
  caneta.cor_tampa = 'Azul'

  print(caneta.cor)
  print(caneta.cor_tampa)

❎ 214 - Encapsulamento (modificadores de acesso: public _protected, __private)

  '''
    ->  Python NÃO TEM modificadores de acesso
    ->  Mas podemos seguir as seguintes convenções

    ->  (sem underline) = public
          pode ser usado em qualquer lugar
    ->  _(um underline) = protected
            não DEVE se usando fora da classe
            ou suas subclasses
    ->  __(dois underlines) = private
            "name mangling" (desfiguração de nomes) em Python
            _NomeClasse__nome_attr_ou_method
            só DEVE ser usando na classe em que foi
            declarado.
  '''

  from functools import

  class Foo:

    def __init__(self):
      self.public = 'isso é público'
      self._protected = 'isso é protegido'
      self.__exemplo = 'isso é private'


    def metodo_publico(self):
      # self._metodo_protected()
      # print(self._protected)
      print(self__exemplo)
      self.__metodo_private()
      return 'metodo_público'


    def _metodo_protected(self):
      print('_metodo_protected')
      return '_metodo_protected'


    def __metodo_private(self):
      print('__metodo_private')
      return '__metodo_private'




  f = Foo()

  # print(f.public)
  print(f.metodo_publico())

❎ 215 - Relações entre classes: associação, agregação e composição

  '''
    ->  Associação é um tipo de reação onde os objetos
          estão ligados dentro do sistema.
    ->  Essa é a relação mais comum entre objetos e tem subconjuntos
          como agregação e composição (que veremos depois).
    ->  Geralmente, temos uma associação quando um objeto tem
          um atributo que referencia outro objeto.
    ->  A associação não especifica como um objeto controla
          o ciclo de vida de outro objeto.
  '''

  class Escritor:

    def __init__(self, nome) -> None:
      self.nome = nome
      self._ferramenta = None


    @property
    def ferramenta(self)
      return self._ferramenta


    @ferramenta.setter
    def ferramenta(self, ferramenta):
      self._ferramenta = ferramenta



  class FerramentaDeEscrever:

    def __init__(self, nome):
      self.nome = nome


    def escrever(self);
      return f'{self.nome} está escrevendo'





  escritor = Escritor('Luiz')
  caneta = FerramentaDeEscrever('Caneta Bic')
  maquina_de_escrever = FerramentaDeEscrever('Máquina')
  escritor.ferramenta = maquina_de_escrever


  print(caneta.escrever())
  print(maquina_de_escrever.escrever())
  print(escritor.ferramenta.escrever())

❎ 216 - Agregação - Python Orientado a Objetos

  '''
    ->  Agregação é um forma mais especializada de associação
          entre dois ou mais objetos. Cada objeto terá
          seu ciclo de vida independente.
    ->  Geralmente é uma relação de um para muitos, onde um
          objeto tem um ou mais objetos.
    ->  Os objetos podem viver separadamente, mas pode
          se tratar de uma relação onde um objeto precisa de
          outro para fazer determinada tarefa.
          (existem controvérsias sobre as definições de agregação)
  '''

  class Carrinho:

    def __init__(self):
      self._produto = []


    def total(self):
      return sum([p.preco for p in self._produtos])


    def inserir_produto(self,*produtos):
      # self._produtos.extend(produtos)
      # self._produtos += produtos
      for produto in produtos:
        self._produtos.append(produto)

    def listar_produtos(self):
      print()
      for produto in self._produtos:
        print(produto.nome, produto.preco)
      print()


  class Produto:

    def __init__(self, nome, preco):
      self.nome = nome
      self.produto = preco






carrinho = Carrinho()

p_1, p_2 = Produto('Caneta', 1.20) , Produto('Camiseta', 20)

carrinho.inserir_produtos(p_1, p_2)
carrinho.listar_produtos()

print(carrinho.total())

❎ 217 - Composição - Python Orientado a Objetos

  '''
    ->  Relações entre classes: associação, agregação e composição
    ------------------------------------------------------------------
    ->  Composição é uma especialização da agregação.
    ->  Mas nela, quando o objeto "pai" for apagado, todas
          as referências dos objetos filhos também são apagadas.
  '''

  class Cliente:

    def __init__(self, nome):
      self.nome = nome
      self.endereco = []

    def inserir_endereco(self, rua, numero):
      self.enderecos.append(Endereco(rua, numero))

    def listar_enderecos(self):
      for endereco in self.enderecos:
        print(endereco.rua, endereco.numero)

    def __del__(self)
      print('APAGANDO,', self.nome)




  class Endereco:

    def __init__(self, rua, numero):
      self.rua = rua
      self.numero = numero

    def __del__(self):
      print('APAGANDO,', self.rua, self.numero)




  cliente_1 = Cliente('Maria')

  cliente_1.inserir_endereco('Av Brasil', 54)
  cliente_1.inserir_endereco('Rua B', 6745)

  endereco_externo = Endereco('Av Saudade', 123213)

  cliente_1.inserir_endereco_externo(endereco_externo)
  cliente_1.listar_enderecos()

  del cliente_1

  print(endereco_externo.rua, endereco_externo.numero)
  print(f'#'*10, 'AQUI TERMINA MEU CÓDIGO')

❎ 218 - Exercício (+solução) com classes relações

📍 Exercícios 🎓 Resposta do professor

  '''
    ->  Exercício com classes
    ------------------------------
    ->  1-  Crie uma classe Carro (Nome)
    ->  2-  Crie uma classe Motor (Nome)
    ->  3-  Crie uma classe Fabricante (Nome)
    ->  4-  Faça a ligação entre Carro tem um Motor

    ->  Obs.:
          Um motor pode ser de vários carros
    ->  5-  Faça a ligação entre Carro e um Fabricante
    ->  Obs.:
          Exiba o nome do carro, motor e fabricante na tela
  '''

  class Carro:
    def __init__(self, nome):
      self.nome = nome
      self._motor = None
      self._fabricante = None

    @property
    def motor(self):
      return self._motor

    @motor.setter
    def motor(self)
      self._moto = valor

    @property
    def fabricante(self)
      return self_fabricante

    @fabricante.setter
    def fabricante(self, valor)
      self._fabricante = valor


  class Motor:
    def __init__(self, nome):
      self.nome = nome


  class Fabricante:
    def __init__(self, nome)
      self.nome = nome




fusca = Carro('Fusca')
volkswagen = Fabricante('Volkwagen')
motor_1_0 = Motor('1.0')
fusca.fabricante = volkwagen
fusca.motor = motor_1_0

print(fusca.nome, fusca.fabricante.nome, fusca.motor.nome)

gol = Carro('gol')
gol.fabricante = volkwagen
motor_1_0 = Motor('1.0')
gol.motor = motor_1_0

print(gol.nome, gol.fabricante.nome, gol.motor.nome)

fiat_uno = Carro('fiat_uno')
fiat = Fabricante('Fiat')
motor_1_0 = Motor('1.0')
fiat_uno.fabricante = fiat
fiat_uno.motor = motor_1_0

print(fiat_uno.nome, fiat_uno.fabricante.nome, fiat_uno.motor.nome)

focus = Carro('focus titanium')
ford = Fabricante('ford')
motor_2_0 = Motor('2.0')
focus.fabricante = ford
focus.motor = motor_2_0

print(focus.nome, focus.fabricante.nome, focus.motor.nome)

❎ 219 - TEORIA - Herança, generalização e especialização

-> Herança simples - Relações entre classes

---;

-> Associação - usa Agregação - tem -> Composição - É dono de, Herança É um

-> Herança vs Composição

---;

-> Classe principal (Pessoa) -> super class, base class, parent class -> classes filhas (Cliente) -> sub class, child class, derived class

❎ 220 - Herança Simples - Python Orientado a Objetos

  '''
    ->  Associação - usa Agregação - tem
    ->  Composição - É dono de, Herança É um

    ->  Herança vs Composição
    ----------------------------------------------
    ->  Classe principal (Pessoa)
          ->  super class, base class, parent class
    ->  classes filhas (Cliente)
          ->  sub class, child class, derived class
  '''

  class Pessoa:
    cpf = '1234'

    def __init__(self, nome, sobrenome)
      self.nome = nome
      self.sobrenome = sobrenome

    def falar_nome_classe(self):
      print('Classe PESSOA')
      print(self.nome, self.sobrenome, self.__class__.__name__)



  class Cliente(Pessoa):
    def falar_nome_classe(self):
      print('Eita, nem saí da Classe PESSOA')
      print(self.nome, self.sobrenome, self.__class__.__name__)


  class Aluno(Pessoa):
    cpf = 'cpf aluno'
    ...





  c_1 = Cliente('Luiz', 'Otávio')
  c_1.falar_nome_class()

  a_1 = Aluno('Maria', 'Helena')
  a_1.falar_nome_classe()

  print(a_1.cpf)

  # help(cliente)

❎ 221 - (Parte 1) super ea sobreposição de membros em Python Orientado a Objetos

  '''
    ->  Classe principal (Pessoa)
          ->  super class, base class, parent class
    ->  Classes filhas (Cliente)
          ->  sub class, child class, derived class

    ->  class MinhaString(str):
          def upper(self):
            print('CHAMOU UPPER')
            return - super(MinhaString, self).upper()
            print('DEPOIS DO UPPER')
            return retorno




    string = MinhaString('Luiz')

    print(string.upper())
  '''


  class A(object):
    atributo_a = 'valor a'

    def __init__(self, atributo):
      self.atributo = atributo

    def metodo(self):
      print('A')




  class B(A):
    atributo_b = 'valor b'

    def __init__(self, atributo, outra_coisa):
      super().__init__(atributo)
      self.outra_coisa = outra_coisa

    def metodo(self):
      print('B')




  class C(B):
    atributo_c = 'valor c'

    def __init__(self, *args, **kwargs):
      print('EI , burli o sistema')
      super().__init__(*args, **kwargs)

    def metodo():
      # super().motodo() # B
      # super(B, self).metodo() # A
      # super(A, self).metodo() # object

      A.motodo(self)
      B.motodo(self)
      print('C')



  print(C.mro())
  print(B.mro())
  print(A.mro())

  c = C('Atributo', 'Qualquer')

  print(c.atributo_a)
  print(c.atributo_b)
  print(c.atributo_c)

  c.metodo()

❎ 222 - (Parte 2) super ea sobreposição de membros em Python Orientado a Objetos

✨🎀 Continuação a aula anterior ✨🎀

❎ 223 - Teoria - Herança múltipla - Python Orientado a Objetos

-> Quer dizer que no Python, uma classe pode estender várias ouras classes.

-> Herança simples:

-> Animal => Mamífero => Humano => Pessoa => Cliente

-> Herança múltipla e mixins:

-> Log => FileLog -> Animal => Mamífero => Humano => Pessoa => Cliente -> Cliente(Pessoa, FileLog)

-> A, B, C, D -> D(B, C) - C(A) - B(A) - A

-> Método => falar

1
2
3
4
5
          A
        /   \
        B    C
        \   /
          D

-> Python 3 usa C3 superclass linearization para gera o mro. (method resolution Order) -> Você não precisa estudar isso (é complexo) -> https://en.wikipedia.org/wiki/C3_linearization/

-> Para saber a ordem de chamada dos métodos -> Use o método de classe Classe.mro() -> Ou o atributo __mro__ (Dunder - Double Underscore)

❎ 224 - Herança múltipla - Python Orientado a Objetos

...

class A:
  ...

  def quem_sou(self):
    print('A')





class B(A):
  ...

  # def quem_sou(self):
  #   print('B')





class C(A):
  ...

  def quem_sou(self):
    print('C')





class D(B, C):
  ...

  def quem_sou(self):
    print('D')





d = D()
d.quem_sou()

# print(D.__mrom__)

print(D.mro())

❎ 225 - (Parte 1) Mixins, Abstração e a união de tudo até aqui

  '''
    Leskov.
    ->  Abstração
  '''

  class Log:
    def log(self, msg)
      raise NotImplementedError('Implemente o método log')




  class LogFileMixin(Log)
    def log(self, msg)
      print(msg)






  if __name__ == '__main__':
    l = LogFileMixin()
    l.log('qualquer')




  # outro arquivo


  from log import Log

  ...

❎ 226 - (Parte 2) LogFileMixin, LogPrintMixin e a união de tudo até aqui

  '''
    ->  Herança - é um
    ---------------------------------------------
  '''

  class Log:
    def _log(self, msg):
      raise NotImplementedError('Implemente o método log')

    def log_error(self, msg)
      return self._log(f'Error: {msg}')

    def log_success(self, msg):
      return self._log(f'Success: {msg}')



  class LogFileMixin(Log):
    def _log(self, msg):
      print(msg)




  class LogPrintMixin(log):
    def _log(self, msg):
      print(f'{msg} ({self.__class__.__name__})')






  if __name__ == '__main__':
    l = LogPrintMixin()
    l.log_error('qualquer coisa')
    l.log_success('Que legal')


  from log import Log

...

❎ 227 - (Parte 3) LogFileMixin e a união de tudo até aqui

    '''
    hard coded
    ->  Herança - é um
    ---------------------------------------------
  '''

  import pathlip import Path
  # https://www.youtube.com/watch?v=T17BTNKBeJY


  LOG_FILE = Path(__file__).parent / 'log.txt'

  class Log:
    def _log(self, msg):
      raise NotImplementedError('Implemente o métado log')

    def log_error(self, msg)
      return self._log(f'Error: {msg}')

    def log_success(self, msg):
      return self._log(f'Success: {msg}')



  class LogFileMixin(Log):
    def _log(self, msg):
      msg_fomatada = f'{msg} ({self.__class__.__name__})'

      print('Salvando no log:', msg_formatada)

      with open(LOG_FILE, 'a') as arquivo:
        arquivo.write(msf_fomatada)
        arquivo.write('\n')




  class LogPrintMixin(log):
    def _log(self, msg):
      print(f'{msg} ({self.__class__.__name__})')






  if __name__ == '__main__':
    lp = LogPrintMixin()
    lp.log_error('qualquer coisa')
    lp.log_success('Que legal')

    lf = LogFileMixin()
    lf.log_error('qualquer coisa')
    lf.log_success('Que legal')



  from log import LogFileMixin, LogPrintMixin
...

❎ 228 - (Parte 4) Eletrônico, Smartphone com Mixin e a união de tudo até aqui

from log import LogFlileMixing

class Eletronico:
def __init__(self, nome):
  self._nome = nome
  self._logado = False

  def ligar(self):
    if not self._ligado:
      self._logado = True

  def deligar(self):
    if self._ligado:
      self._ligado = False



class Smartphone(Eletronico, LogFileMixin):
  def ligar(self):
    super().ligar()

    if self._ligado:
      msg = f'{self_nome} está ligado'
      self.log_success(msg)

  def desligar(self):
    super().desligar()

    if not self._logado:
      msg = f'{self._nome} está desligado'
      self.log_error(msg)



# outro arquivo

from log import LogFileMixin, LogPrintMixin
from eletronico import Smartphone

galaxy_s = Smartphone('Galaxy S')
iphone = Smartphone('iPhone')

galaxy_s.logar()
iphone.desligar()

❎ 229 - Classes abstratas - Abstract Base Class (abc) - Python Orientado a Objetos

  '''
    ->  ABCs são usados como contratos para a definição
          de novas classes. Elas podem forçar outras classes
          a criarem métodos concretos. Também podem ter
          métodos concretos por elas mesmas.
    ->  @abstractmethods são métodos que não têm corpo.
    ->  As regras para classes abstratas com métodos
          abstratos é que elas NÃO PODEM ser instânciadas
          diretamente.
    ->  Métodos abstratos DEVEM  ser implementados
          nas subclasses (@abstractmethod).
    ->  Uma classe abstrata em Python tem sua metaclasse
          sendo ABCMeta.
    ->  É possível criar @property @setter @classmethod
          @staticmethod e @method como abstratos, para isso
          use @abstractmethod como decorator mis interno
  '''

  from abc import ABC, astractmethod

  class Log(ABC):
    @abstractmethod
    def _log(self, msg): ...

    def log_error(self, msg):
      return self._log(f'Error: {msg}')

    def log_success(self, msg):
      return self._log(f'Success: {msg}')



  class LogPringMixin(Log):
    def _log(self, msg):
      print(f'{msg} (self.__class__.__name__)')




  l = LogPrintMixin()
  l.log_error('oi')

❎ 230 - abstractmethod para qualquer método já decorado (property e setter)

  '''
    ->  É possível criar @property @property.setter @classmethod
          @staticmethod e métodos normais como abstratos, para isso
          use @abstractmethod como decorator mais interno.
    ->  Foo - Bar são palavras usadas como placeholder
          para palavras que podem mudar na programação
    https://docs.python.org/pt-br/3/library/abc.html
  '''

  from alc import ABC, abstractmethod


  class AbstractFoo(ABC):
    def __init__(self, name):
      self._name = None
      self.name = name

    @property
    def name(self)
      return self._name

    @name.setter
    @abstractmethod
    def name(self, name): ...




class Foo(AbstractFoo):
  def __init__(self, name):
    super().__init__(name)
    # print('Sou inútil')

  @AstractFoo.name.setter
  def name(self, name)
    self._name = name



foo = Foo('Bar')

print(foo.name)

❎ 231 - Teoria: polimorfismo, assinatura de métodos e Liskov Substitution Principle

-> Polimorfismo é o princípio que permite que classes deridavas de uma mesma superclasse tenham métodos iguais (com mesma assinatura) mas comportamentos diferentes.

-> Assinatura de método = Mesmo nome e quantidade de parâmetros (retorno não faz parte da assinatura)

-> Opinião + princípios que contam:

-> Assinatura do método: nome, parâmetros e retorno iguais SO"L"ID

-> S -> Sigle Responsibility Principle -> O -> Open Close Principle -> L -> Liskov substitution Principle -> I -> Interface segregation principle -> D -> Dependency inversion principle

-> Princípio da substituição de liskov -> Objetos de uma superclasse devem ser substituíveis por objetos de uma subclasse sem quebrar a aplicação.

❎ 232 - Na prática - polimorfismo, assinatura de métodos e Liskov Substitution Principle

...
'''
  ->  Sobrecarga de métodos (overload) 🐍 = ❌
  ->  Sobreposição de métodos (override) 🐍 = ✅
'''

from abc import ABC, abstractmethod

class Notificacao(ABC):
  def __init__(self, mensagem):
    self.mensagem = mensagem

  @abstractmethod
  def enviar(self) -> bool: ...




class NotificacaoEmail(Notificacao):
  def enviar(self) -> bool:
    print('E-mail: enviando -', self.mensagem)
    return True





class NotificacaoSMS(Notificacao):
  def enviar(self) -> bool:
    print('SMS enviado - ', self.mensagem)
    return False




def notificar(notificacao: Notificacao):
  notificacao_enviada = notificacao.enviar()

  if notificacao_enviada:
    print('Notificação enviada')
  else:
    print('Notificação NÃo enviada')



notificacao_email = NotificacaoEmail('testando e-mail')
notificar(notificacao_email)

notificacao_sms = NotificacaoSMS('testando SMS')
notificar(notificacao_sms)

❎ 233 - Criando Exception em Python Orientado a Objetos (Exceções)

'''

  ->  Para criar uma Exception em Python, você só
        precisa herdar de alguma exceção da linguagem.
    https://docs.python.org/3/library/exceptions.html
  ->  Criando exceções (comum colocar Error ao final)
  ->  Levando (raise) / Lançando (throw) exceções
  ->  Relançando exceções
  ->  Adicionando notas em exceções (3.11.0)
'''

class MeuError(Exception):
  ...

❎ 234 - Levantando e tratando suas Exceptions (Exceções)

...

class OutroError(Exception):
  ...




def levantar():
  exception_ = MeuError('a', 'b', 'c')
  raise exception_

try:
  levando()
except (MeuError, ZeroDivisionError) as error:
  print(error.__class__.__name__)
  print(error.args)
  print()

  exception_ = OutroError('Vou lançar de novo')
  raise excepton_ from error

❎ 235 - Notas das exceptions em Python 3.11+(add_notes,__notes__)

'''
  https://docs.python.org/3/library/exceptions.html
'''


class OutroError(Exception):
  ...




def levantar():
  exception_ = MeuError('a', 'b', 'c')
  exception_.add_note('olha a nota 1')
  exception_.add_note('você errou isso')
  raise exception_

try:
  levando()
except (MeuError, ZeroDivisionError) as error:
  print(error.__class__.__name__)
  print(error.args)
  print()

  exception_ = OutroError('Vou lançar de novo')
  exception_.__notes__ = error.__notes__.copy()
  exception_.add_note('Mais uma nota')
  raise excepton_ from error

❎ 236 - Teoria - python Special Methods, Magic Methods ou Dunder Methods

-> Dunder = Double Underscore - __dunder__

-> Antigo e úteil https://rszalski.github.io/magicmethods/

-> https://docs.python.org/3/reference/datamodel.html#specialnames/

-> __lt__(self, other) - self < other

-> __le__(self, other) - self <= other

-> __gt__(self, other) - self > other

-> __ge__(self, other) - self >= other

-> __eq__(self, other) - self == other

-> __ne__(self, other) - self != other

-> __add__ (self, other) - self + other

-> __sub_ (self, other) - self - other

-> __mul_ (self, other) - self * other

-> __truedic__ (self, other) - self / other

-> __neg__(self) - self

-> __str__(self) - str

-> __repr__(self) - str

❎ 237 - Python Dunder Methods __repr__ e __str__

...

class Ponto:
  def __init__(self, x, y, z ='String'):
    self.x = x
    self.y = y
    self.z = z

  def __str__(self):
    return f('{self.x}, {self.y}')

  def __repr__(self):
    # class_nome = self.__class__.__name__
    class_name = type(self).__name__
    return f'{class_name} (x={self.x!r}, {y=self.y!r}, z={self.z!r})'




p_1 = Ponto(1, 2)
p_2 = Ponto(978, 876)

print(p_1)
print(repr(_2))
print(f'{p_2!r}')

❎ 238 - Exemplo de uso de dunder methods (métodos mágicos)

...

class Ponto:
  def __init__(self, x, y, z ='String'):
    self.x = x
    self.y = y
    self.z = z

  def __repr__(self):
    # class_nome = self.__class__.__name__
    class_name = type(self).__name__
    return f'{class_name} (x={self.x!r}, {y=self.y!r}, z={self.z!r})'

  def __add__(self, other):
    novo_x = self.x + other.x
    novo_y = self.y + other.y
    return Ponto(novo_x, novo_y)

  def __gt__(self, other):
    resultado_self = self.x + self.y
    resultado_other = other.x + other.y
    return resultado_self > resultado_other





if __name__ == '__main__':
  p_1 = Ponto(4, 2) # 6  
  p_2 = Ponto(6, 4) # 10
  p_3 = p_1 + p_2

  print(p_3)
  print('P_1 é maior que p_2', p_1 > p_2)
  print('P_2 é maior que p_1', p_2 > p_1)

❎ 239 - __new__ e __init__ em classes Python

'''
  ->  __new__ é o método responsável por criar e 
        retornar o novo objeto. Por isso, new recebe cls.
  ->  __new__ ❗DEVE retornar o novo objeto ❗
  ->  __init__ é o método responsável por inicializar
        a instância. Por isso, init recebe self.
  ->  __init__ ❗NÂO DEVE retornar nada (None) ❗
  ->  object é a super classe de uma classe
'''

class A:
  def __new__(cls, *args, **kwargs):
    instancia = super().__new__(cls)
    return instancia

  def __init__(self, x):
    self.x = x
    print('sou o init')

  def __repr__(self):
    return 'A()'



a = A(123)

print(a.x)

❎ 240 - Context Manager com classes - Criando e Usando gerenciadores de contexto

'''
  ->  Você pode implementar seus próprios protocolos
        apenas implementando os dunder methods que o 
        Python vai usar.
  ->  Isso é chamado de Duck typing. Um conceito
        relacionado com tipagem dinâmica onde o Python não
        está interessado no tipo, mas se alguns métodos existem
        no seu objeto para que ele funcione de forma adequada.

  ->  Duck Typing:
  ->  Quando vejo um pássaro que caminha como um pato, nada como
        um patp e grasna cmo um pato, eu chamo aquele pássaro de pato.
  ->  Para criar um context manager, os métodos __enter__ e __exit__
        devem ser implementados.
  ->  O método __exit__ receberá a classe de exceção, e exceção e o
        traceback. Se ele retornar True, exceção no with será
        suprimidas

  ->  Ex:

  with open('aula_149.txt', 'w') as arquivo:
    ...
'''

class MyOpen:
  def __init__(self, caminho_arquivo, modo):
    self.caminho_arquivo = caminho_arquivo
    self.modo = modo
    self._arquivo = None

  def __enter__(self):
    print('ABRINDO ARQUIVO')
      self._arquivo = open(self.caminho_arquivo, self.modo, encoding='utf8')
      return self._arquivo

  def __exit__(self, class_exception, exception_, traceback_):
    print('FECHANDO ARQUIVO')
    self._arquivo.close()





with MyOpen('aula_txt', 'w') as arquivo:
  arquivo.write('Linha 1 \n')
  arquivo.write('Linha 2 \n')
  arquivo.write('Linha 3 \n')

  print('WITH', arquivo)

❎ 241 - Exception em context manager com classes

...
class MyOpen:
  def __init__(self, caminho_arquivo, modo):
    self.caminho_arquivo = caminho_arquivo
    self.modo = modo
    self._arquivo = None

  def __enter__(self):
    print('ABRINDO ARQUIVO')
      self._arquivo = open(self.caminho_arquivo, self.modo, encoding='utf8')
      return self._arquivo

  def __exit__(self, class_exception, exception_, traceback_):
    print('FECHANDO ARQUIVO')
    self._arquivo.close()

    # raise class_exception(*exception_.args).with_traceback(traceback_)

    # print(class_exception)
    # print(exception_)
    # print(traceback_)

    # exception_add_note('Minha nota')

    # return True # Tratei execução


with MyOpen('aula_txt', 'w') as arquivo:
  arquivo.write('Linha 1 \n')
  arquivo.write('Linha 2 \n', 123)
  arquivo.write('Linha 3 \n')

  print('WITH', arquivo)

❎ 242 - Context Manager com contextlib.contextmanager

from contextlib import contextmanager

@contextmanager
def my_open(caminho_arquivo, modo):
  try:
    print('Abrindo arquivo')
    arquivo = open(caminho_arquivo, modo, encoding='utf8')
    yield arquivo
  except Exception as e: 
    print('Ocorreu', e)
  finally:
    print('Fechando arquivo')
    arquivo.close()




with my_open('aula_150.txt', 'w') as arquivo:
  arquivo.write('Linha 1\n')
  arquivo.write('Linha 2\n', 123)
  arquivo.write('Linha 3\n')

  print('WITH', arquivo)

❎ 243 - Funções decoradoras e decoradores com classes

    def meu_repr(self):
      class_name = self.__class__.__name__
      class_dect = self.__dict__
      class_repr = f'{class_name}({class_dict})'
      return class_repr

    def adiciona_repr(cls)
      cls.__repr__ = meu_repr
      return cls


    @adiciona_repr
    class Time:
      def __init__(self, nome):
        self.nome = nome


    class Planeta:
      def __init__(self, name):
        self.nome = nome




    brasil = Time('Brasil')
    portugal = Time('Portugal')

    terra = Planeta('Terra')
    marte = Planeta('Marte')


    print(brasil)
    print(portugal)

    print(terra)
    print(marte)

❎ 244 - Funções decoradoras e decoradores com métodos

    def meu_repr(self):
      class_name = self.__class__.__name__
      class_dect = self.__dict__
      class_repr = f'{class_name}({class_dict})'
      return class_repr

    def adiciona_repr(cls)
      cls.__repr__ = meu_repr
      return cls

    def meu_planeta(metodo):
      def interno(self, *args, **kwargs):
        resultado - metodo(self, *args, **kwargs)

        if 'Terra' in resultado:
          returno 'Você está em casa'
        return resultado
      return interno

    @adiciona_repr
    class Time:
      def __init__(self, nome):
        self.nome = nome


    class Planeta:
      def __init__(self, name):
        self.nome = nome

      @meu_planeta
      def falar_nome(self)
        return f'O planeta é {self.nome}'



    brasil = Time('Brasil')
    portugal = Time('Portugal')

    terra = Planeta('Terra')
    marte = Planeta('Marte')


    print(brasil)
    print(portugal)

    print(terra)
    print(marte)

    print(terra.falar_nome())
    print(marte.falar_nome())

❎ 245 - Métodos especial __call__

    '''
      ->  callable é algo que pode ser executado com parênteses
      ->  Em classes normais, __call__ faz a instância de uma
            classe "callable".
    '''

    class CallMe:
      def __init__(self, phone):
        self.phone = phone

      def __call__(self, nome):
        print(nome, 'está chamando', self.phone)
        return 2134



    call_1 = CallMe('23945876545')
    retorno = call_1('Luiz Otávio')

    print(retorno)

❎ 246 - Classes decoradoras (Decorator classes)

      class Multiplicar:
        def __init__(self, multiplicador):
          self._multiplicador = multiplicador

        def __call__(self, func):
          def interna(*args, **kwargs):
            resultado = func(*args, **kwargs)
            return resultado * self._multiplicador
          return interna



      @Multiplicar(2)
      def soma(x, y):
        return x + y


      dois_mais_quatro = soma(2, 4)
      print(dois_mais_quatro)    

❎ 247 - Teoria - metaclasses são o tipo das classes

-> EM PYTHON, TUDO É UM OBJETO (CLASSES TAMBÉM)

-> Então, qual é o tipo de c uma classe? (type)

-> Seu objeto é uma instância da sua classe

-> Sua classe é uma instância de type (tuple é uma metaclass)

-> type('Name' (Bases,), __dict__)

-> Ao criar uma classe, coisas ocorrem por padrão nessa ordem:

-> __new__ da metaclass é olhado e cria a nova classe

-> __call__ da metaclass é chamado com os argumentos e chama:

-> __new__ da class com os argumentos (cria a instância)

-> __init__ da class com so argumentos

-> __call__ da metaclass termina a execução

-> Métodos importantes da metaclass


-> __new__ (msc, name, bases, dct) (Cria a classe)

-> __call__ (cls, args, *kwargs) (Cria e inicializa a instância)

-> "Metaclasses não magias mais profundas do que 99% dos usuários deveriam se preocupar. Se você saber se precisa delas, não precisa (as pessoas que realmente precisam delas sabem com certeza que precisam delas e não precisam de uma explicação sobre o porquê)."

-> Rim Peters (CPython Core Developer)

object acima

class Foo: ...

    Foo = type('Foo', (object,), {})
    f = Foo()

    # print(isinstance(f, Foo))
    print(type(f))
    print(type(Foo))

❎ 248 - __new__ de uma metaclass cria e retorna a classe em si

  ...

  def meu_repr(self):
    return f'{type(self).__name__}({self.__dict__})'



  class Meta(type):
    def __new__(mcs, name, bases, dct):
      print('METACLASS NEW')
      cls = super().__new__(msc, name, bases, dct)
      cls.attr = 1234
      cls.__repr__ = meu_repr

      if 'falar' not in cls.__dict__ or not callable(cls.__dict__['falar']):
        raise NotImplementedError('Implemente falar')
      return cls

    def __call__(cls, *args, kwargs):
      instancia = super().__call__(*args, **kwargs)
      if 'nome' not in instancia.__dict__:
        raise NotImplementedError('Crie o attr nome')
      return instancia


  class Pessoa(metaclass=Meta):
    # falar = 123

    def __new__(cls, *args, **kwargs):
      print('MEU NEW')
      instancia = super().__new__(cls)
      return instancia

    def __init__(self, nome):
      print('MEU INIT')
      # self.nome = nome

    def falar(self):
      print('FALANDO...')



  p_1 = Pessoa('Luiz')
  p_1.falar()

❎ 249 - __call__ de uma metaclass cria e retorna a instância da classe

🎀 Continuação do anterior

❎ 250 - dir e help + DocString de uma linha (Documentação)

    import uma_linha

    # print(dir(uma_linha))
    # print(uma_linha.__doc__)
    # print(uma_linha.__file__)
    # print(uma_linha.__name__)

    help(uma_linha)

    # arquivo do uma_linha

    """O que seu módulo faz """

    variavel = 'valor'

    def funcao():
      return 1

❎ 251 - DocString de várias linhas (Documentação)

-> Loren ipsum dolor sit amet. Et praesentium nisi non quam mollitia At saepe quisquam qui quae voluptatem. Eum laudantium impedit qui velit quia ea glisum nihil. Sed consequatur culpa qui corrupti offcia eos odit tenetur cum corrupti beatea At provident error cum eveniet consectetur qui perferendis placeat. Non quibusdam blanditiis est rerum repellat aut facere rerum in nihil reiciendis ut maxime galisum qui error accusamus.

-> Ut cumque enim ut pssimus ullam non magni doloribus! -> Ut quasi doloremque aut itaque molestiae eos fugiat deleniti sed voluotates nisi At eveniet quia et quasi vero ea fugiat dekectus! In beatae perferendis in modi possimus eum quaerat maiores ab autem natus ab ullam itaque ic nobis autem ad harum tempre. Qui dolore eius et quase aliquid et praesentium fuga sed deserunt adipisci eos dolor nemo qui animi dolore et odio libere.

❎ 252 - DocString em função (Documentação)

    """
      ->  Este é um módulo de exemplo

      ->  Este médulo contém funções e exemplos de documentação de funções.
      ->  A função sima você já conhece bastante.
    """

    variavel_1 = 1

    # def soma(x, y):

    def soma(x:int | float, y:int | float) -> int | float:
      """
        ->  Soma x e y

        ->  Este módulo contém funções e exemplos de documentação de funções.
        ->  A função soma você já conhece bastante.

        :param x: número 1
        :type x:int or float
        :param y: número 2
        :type y int or float

        return: A soma entre x e y
        :r type int or float
      """
      return x + y

    def multiplica(x: int | float, y: int | float, z: int | float | None = None) -> int | float:
      """
        ->  multiplicar x, y e/ou z

        ->  multiplica x e y. Se z for enviado, multiplica x, y, z.
      """
      if z is None:
        return x * y
      return x * y * z



    variavel_2 = 2 
    variavel_3 = 3 
    variavel_4 = 4 

❎ 253 - DocString em class (Documentação)

    class Foo:
      def soma(x:int | float, y:int | float) -> int | float:
        """
          ->  Soma x e y

          ->  Este módulo contém funções e exemplos de documentação de funções.
          ->  A função soma você já conhece bastante.

          :param x: número 1
          :type x:int or float
          :param y: número 2
          :type y int or float

          return: A soma entre x e y
          :rtype int or float
        """
        return x + y

      def multiplica(x: int | float, y: int | float, z: int | float | None = None) -> int | float:
        """
          ->  multiplicar x, y e/ou z

          ->  multiplica x e y. Se z for enviado, multiplica x, y, z.
        """
        if z is None:
          return x * y
        return x * y * z



      variavel_2 = 2 
      variavel_3 = 3 
      variavel_4 = 4 

❎ 254 - Teoria - enum.Enum (Enumerações)

-> Enumerações na programação, são usando em ocasiões onde temos um determinado número de coisas para escolher.

-> Enums têm membros e seus valores são constes.

-> Enums em python:

-> São um conjunto de nomes simbólicos (membros) ligados a valores únicos

-> podem ser iterados para retornar seus membros canônicos na ordem de definição

-> enum.Enum é a superclasse para enumerações. Mas também pode ser usada diretamente (mesmo assim, Enums não são classes normais em Python).

-> Você poderá usar seu Enum com type annoteions, com isinstance e outras coisas relacionadas com tipo.

-> para obter os dados:

-> membro = Classe(valor), Classe['chave']

-> chave = Classe.chave.nome

-> valor = Classe.chave.value

    def mover(direcao):
      print(f'Movendo para {direcao}')


    mover('esquerda')
    mover('direita')
    mover('acima')
    mover('abaixo')

❎ 255 - Código - enum.Enum (Enumerações)

    ...
    import enum

    class Direcoes(enum.Enum):

      ESQUERDA = enum.auto()
      DIREITA = enum.auto()
      ACIMA = enum.auto()
      ABAIXO = enum.auto()



    print(Direcoes(1), Direcoes['ESQUERDA'], Direcoes.ESQUERDA)
    print(Direcoes(1).name Direcoes.ESQUERDA.value


    def mover(direcao:Direcoes):
      if not isinstance(direcao, Direcoes):
        raise ValueError('Direção não encontrada')
      print(f'Movendo para {direcao.name} ({direcao.value})')



    mover(Direcoes.ESQUERDA)
    mover(Direcoes.DIREITA)
    mover(Direcoes.ACIMA)
    mover(Direcoes.ABAIXO)

❎ 256 - Exercício com Abstração, Herança Encapsulamento e Polimorfismo

📍 Exercícios

    """
      ->  Criar um sistema bancário (extremamente simples) que tem cliente, conta e
            um banco. A ideia é que o cliente tenha uma conta (poupança e corrente)
            e que possa sacar/depositar nessa conta. Contas corrente tem um limite extra.

        Conta (ABC)
          ContaCorrente
          ContaPoupanca

        Pessoa
          Cliente
            Cliente -> Conta

        Banco
          Banco ->  Cliente
          Banco ->  Conta

      ->  Dicas:
      ->  Criar classe Cliente que herda da classe Pessoa (Herança)
            Pessoa tem nome e idade (com getters)
            Cliente TEM conta (Agregação da classe ContaCorrente ou contaPoupanca)
      ->  Criar classes ContaPoupanca e ContaCorrente que herdam de conta
            ContaCorrente deve ter um limite extra
            Contas têm agência, número da conta e saldo
            Contas devem ter método para depósito
            Conta (super classe) deve ter o método secar abstrato (Abstração e 
            polimorfismo - as subclasses que implementam o método sacar)
      ->  Criar classe Banco para AGREGAR classes de clientes e de contas (Agregação)
      ->  Banco será responsável autenticar o cliente e as contas da seguinte maneira:

      ->  Banco tem contas e clientes (Agregação)
            👉  Checar se a agência é daquele banco  
            👉  Checar se o cliente é daquele banco
            👉  Checar se a conta é daquele banco
      ->  Só será possível sacar se passar na autenticação do banco (descrita acima)
      ->  Banco autentica por um método
    """

❎ 257 - Solução - Criando a classe abstrata Conta

🎓 Resposta do professor

Alt text

❎ 258 - Solução - Criando classe ContaPoupanca

🎓 Resposta do professor

🎀 Continuação

❎ 259 - Solução - Criando classe ContaCorrente

🎓 Resposta do professor

🎀 Continuação

❎ 260 - DICA EXTRAS - tipagem, linters e settings.json do VS Code

🎓 Resposta do professor

🎀 Continuação

❎ 261 - Solução - Criando a classe Pessoa

🎓 Resposta do professor

🎀 Continuação

❎ 262 - Solução - Criando a classe Cliente

🎓 Resposta do professor

🎀 Continuação

❎ 263 - Solução - Criando a classe Banco (Parte 1)

🎓 Resposta do professor

🎀 Continuação

❎ 264 - Solução - Criando a classe Banco (Parte 2)

🎓 Resposta do professor

🎀 Continuação

❎ 265 - dataclasses - o que são dataclasses?

    """
      ->  o módulo dataclasses fornece um decorador e funções para criar 
            métodos como __init__(), __repr__(), __eq__() (entre outros) em
            classes definidas pelo usuário.
      ->  Em resumo: dataclasses são syntax sugar para criar classes normais.
      ->  foi descrito na PEP 557 e adicionado n versão 3.7 do Python.

      ->  doc: https://docs.python.org/3/library/dataclasses.html
    """

    from dataclasses import dataclass

    @dataclass
    class Pessoa:
      nome: str
      idade: int


    if __name__ == '__main__':
      p_1 = Pessoa('luiz', 30) 
      p_2 = Pessoa('luiz', 30) 

      print(p_1 == p_2)

❎ 266 - dataclasses com métodos property e setter

    ...

    from dataclasses import dataclass

    @dataclass
    class Pessoa:
      nome: str
      sobrenome: str

      @property
      def nome_completo(self):
        return f'{self.nome} {self.sobrenome}'

      @nome_completo.setter
      def nome_completo(self, valor):
        nome, *sobrenome = valor.split()
        self.nome = nome
        self.sobrenome = ' '.join(sobrenome)


    if __name__ == '__main__':
      p_1 = Pessoa('luiz', 'Otávio') 
      p_2.nome_completo = 'Maria Helena'

      print(p_1)
      print(p_1.nome_completo)

❎ 267 - __init__ e __post_init__ em dataclasses

      from dataclasses import dataclass

    @dataclass(init=False)
    class Pessoa:
      nome: str
      sobrenome: str

      def __init__(self, nome, sobrenome):
        self.nome = nome
        self.sobrenome = sobrenome
        self.nome_completo = f'{self.nome} {self.sobrenome}'

      def __post_init__(self):
        print('POST INIT')


      # @property
      # def nome_completo(self):
      #   return f'{self.nome} {self.sobrenome}'

      # @nome_completo.setter
      # def nome_completo(self, valor):
      #   nome, *sobrenome = valor.split()
      #   self.nome = nome
      #   self.sobrenome = ' '.join(sobrenome)


    if __name__ == '__main__':
      p_1 = Pessoa('luiz', 'Otávio') 

      print(p_1)
      print(p_1.nome_completo)

❎ 268 - Configurações do decorator dataclass

    ...
        from dataclasses import dataclass

    # @dataclass(repr=True, frozen=True, order=True)
    @dataclass(repr=True)
    class Pessoa:
      nome: str
      sobrenome: str



    if __name__ == '__main__':
      lista = [
        Pessoa('A', 'Z'),
        Pessoa('B', 'Y'),
        Pessoa('C', 'X'),
      ]
      ordenadas = sorted(lista, reverse=True, key=lombda p: p.sobrenome)

      print(ordenadas)

❎ 269 - asdict e astuple em dataclass

      from dataclasses import asdict, astuple, dataclass


    @dataclass
    class Pessoa:
      nome: str
      sobrenome: str



    if __name__ == '__main__':
      p_1 = Pessoa('luiz', 'Otávio') 

      print(asdict(p_1).keys())
      print(asdict(p_1).values())
      print(asdict(p_1).items())
      print(astuple(p_1[0]))

❎ 270 - Valores padrão, field e fields em dataclasses

    from dataclasses import dataclass, field



    class Pessoa:
      nome: str = field(
        default='MISSING', repr=False
      )
      sobrenome: str = 'Not sent'
      idade: init = 100
      endereco: list[str] = field(default_factory=list)



    if __name__ == '__main__':
      p_1 = Pessoa() 

      # print(fields(p_1))

      print(p_1)

❎ 271 - namedtuple - tuplas imutáveis com nomes para valores

    """
      ->  Usamos nomedtuples para criar classes de objetos que são apenas um 
            agrupamento de atributos, como classes normais sem métodos, ou registros
            de bases de dados, etc.
      ->  As nomedtuples são imutáveis assim como as tuplas.

      ->  https://docs.python.org/3/library/collections.html#collections.namedtuple

      ->  https://docs.python.org/3/library/typing.html#typing.NamedTuple

      ->  https://brasilescola.uol.com.br/curiosidades/baralho.htm

      from collections import namedtuple
    """
    from typing import NamedTuple

    class Carta(NamedTuple):
      valor: str = 'VALOR'
      naipe: str = 'NAIPE'

      # Carta = Namedtuple(
      #   'carta',['valor', 'naipe'],
      #   defaults=['VALOR', 'NAIPE']
      # )

      as_espadas = Carta('A')

      print(as_espadas._asdict())
      print(as_espadas)
      print(as_espadas[0])
      print(as_espadas.valor)
      print(as_espadas[1])
      print(as_espadas.naipe)

      print()
      print(as_espadas._fields)
      print(as_espadas_field_defaults)

      for valor in as_espadas:
        print(valor)

❎ 272 - Criando sua próprio lista com iterables, iterator e Sequence (collections.abc)

    """

      ->  Essa é apenas uma aula para introduzir os protocolos de collections.abc no
            Python. Qualquer outro protocolo poderá ser implementado segundo a mesma
            estrutura usada nessa aula.

      -> https://docs.python.org/3/library/collections.abc.html
    """
    from collections.abc import Iterator
    from collections.abc import Sequence


    # class MyList(Iterator):
    class MyList(Sequence):
      def __init__(self):
        self._data = {}
        self._index = 0
        self._next_index = 0

      def append(self, *values):
        for value in valuies:
          self._data[self._index] = value
          self._index += 1

      def __len__(self) -> int
        return self._index

      def __getitem__(self, index):
        return self._data[index]

      def __setitem__(self, index, value):
        self._data[index] = value

      def __iter__(self):
        return self

      def __next__(self):
        if self._next_index >= self._index:
          self._next_index = 0
          raise StopIteration

        value = self._data[self._next_index]
        self._next_index += 1
        return value


    if __name__ == '__main__':
      lista = MyList()
      lista.append('Maria', Helena)
      lista[0] = 'João'
      lista.append('Luiz')

      # print(lista[0])
      # print(len(lista))

      for item in lista:
        print(item)

        print('---')

      for item in lista:
        print(item)

        print('---')

🔜 ✅ Seção 6 Módulos Python - os, detatime, sys, json csv, selenium pillow e mais⚓︎


❎ 273 - if __name__==__main__

    def soma(x: float, y: float) -> float:
      return x + y

    # outro arquivo

    from modulo import soma

    if __name__ == '__main__':
      soma(1, 3)

❎ 274 - Criando data e hora em Python com módulo detatime

    """

      ->  datetime(ano, mês, dia)
      ->  datetime(ano, mês, dia, horas, minutos, segundos)
      ->  datetime('DATA'. 'FORMATO')
      ->  datetime.now()

      ->  https://pt.wikipedia.org/wiki/Era_Unix
      ->  datetime.fromtimestamp(Unix Timestamp)

      ->  https://docs.python.org/3/library/datetime.html
      ->  Para timezomes

      ->  https://en.wikipedia.org/wiki/List_of_tz_database_time_zones
      ->  instalando o pytz
      ->  pip install pytz types-pytz
    """

    from datetime import datetime

    data_str_data = '2022/04/20 07:49:23'
    data_str_data = '20/04/2022'
    # data_str_fmt = '%Y-%m-%d %H:%M:%S'
    data_str_fmt = '%d/%m/%y'

    # data = datetime(2022, 4, 20, 7, 49, 23)
    data = date.time.strptime(date_str_data, data_str_fmt)

    print(data)

❎ 275 - Data e hora atual (now), com Unix Timestamp e Timezone diferente (pytz)

    from datetime import datetime
    from pytz import timezone

    data = datetime.now(timezone('America/Sao_Paulo'))

    # data = datetime(2022, 4, 20, 7, 49, 23, tzinfo=timezone('America/Sao_Paulo'))

    print(data.timestamp())
    print(datetime.fromtimestamp(1670849077))

❎ 276 - datetime.timedelta e dateutil.relativedelta (calculando datas)

    """

      ->  Docs:
      ->  https://dateutil.readthedocs.io/en/stable/relativedelta.html
      ->  https://docs.python.org/3/library/datetime.html#timedelta-objects

          pip install python-dateutil types-python-dateutil
    """
    from datetime import datetime

    from dateutil.relativedelta import relativedelta

    fmt = '%d/%m/%Y %S:%M:%S'
    data_inicio = datetime.strptime('20/04/1987 09:30:30', fmt)
    data_fim = datetime.strptime('12/12/2022 08:20:20' fmt)
    # delta = timedelta(days=10, hours=2)
    delta = relativedelta(data_fim, data_inicio)

    print(delta.days, delta.years)

    # print(data_fim - delta)
    # print(data_fim + relativedelta(seconds=60, minutes=10))

    # delta = data_fim - data_inicio

    # print(delta.days, delta.seconds, delta.microseconds)
    # print(delta.total_seconds())
    # print(data_fim > data_inicio)
    # print(data_fim < data_inicio)
    # print(data_fim == data_inicio)

❎ 277 - Formatando datas do datetime com strftime no Python

    """

      ->  datetime.strtime('DATA', FORMATO)

      ->  https://docs.python.org/3/library/datetime.html
    """

    from datetime import datetime

    data = datetime(2022, 12, 13, 7, 59, 23)

    data = datetime.strtime('2022-12-13 07:59:23', '%Y-%m-%d %H:%M:%S')

    print(data.strftime('%d/%m/%Y'))
    print(data.strftime('%d/%m/%Y %H:%M'))
    print(data.strftime('%d/%m/%Y %H:%M:%S'))
    print(data.strftime('%Y'), data.year)
    print(data.strftime('%d'), data.day)
    print(data.strftime('%m'), data.month)
    print(data.strftime('%H'), data.hour)
    print(data.strftime('%M'), data.minute)
    print(data.strftime('%S'), data.second)

❎ 278 - Exercício solucionado - calculando as datas e parcelas de um empréstimo

📍 Resposta Exercício 🎓 Resolução do Professor

    """

    ->  Maria pegou um empréstimo de R$ 1.000.000 para realizar o paramento em 5
          anos.
    ->  A data em que ela pegou o empréstimo foi 20/12/2020 e o vencimento de cada
          parcela é no dia 20 de cada mês.
    ->  Crie a data do empréstimo
    ->  Crie da data do final do empréstimo
    ->  Mostre todas as todas de vencimento e o valor de cada parcela
    """

    from datetime import datetime

    from dateutil.relativedelta import relativedelta

    valor_total = 1_000_000

    data_emprestimo = datetime(2020, 12, 20)
    delta_anos = relativedelta(years=5)
    data_final= data_emprestimo + delta_anos

    data_parcelas = []
    data_parcelas = data_emprestimo

    while data_parcelas < data_final:
      data_parcelas.append(data_parcela)
      data_parcela += relativedelta(months=+1)

    numero_parcelas = len(data_parcelas)
    valor_parcelas = valor_total / numero_parcelas

    for data in data_parcelas
      print(data.strtime('%d/%m/%Y'), f' R$ {valor_parcela:,.2f}')

    print()
    print(f'você pegou R$ {valor_total:,.2f} para pagar'
          f'em {delta_anos.years} anos'
          f'({numero_parcelas} meses) em parcelas de '
          f'R$ {valor_parcelas:,.2f}.'
    )

❎ 279 - Usando calender para calendários e datas

    """

      ->  https://docs.python.org/3/library/calendar.html

      ->  calender é usado para coisas genéricas de calendários e datas.
      ->  Com calendar, você pode saber coisas como:

      ->  Qual o último dia do mês (ex.: monthrange)
      ->  Qual o nome e número do dia de determinada data (ex.: weekday)
      ->  Criar um calendário em si (ex.: monthcalendar)
      ->  Trabalhar com coisas específicas de calendário (ex.: calendar, month)

      ->  Por padrão dia da semana começa em 0 até 6
      ->  0 = segunda-feira | 6 = domingo 
    """
    import calendar

    print(calendar.calendar(2022))
    print(calendar.calendar(2022, 12))

    numero_primeiro_dia, ultimo_dia = calendar.monthrange(2022, 12)

    print(list(enumerate(calendar(day_name)))

    print(calendar.day_name[numero_primero_dia])
    print(calendar.day_name[calendar.weekday(2022, 12, ultimo_dia)])

    for week in calendar.monthcalendar(2022, 12)
      for day in week
        if day == 0
          continue
        print(day)

❎ 280 - locale para internacionalização (tradução)

    """
      ->  https://docs.python.org/3/library/locale.html

      ->  https://learn.microsoft.com/fr-fr/powershell/module/international/get-winsystemlocale?view=windowsserver2022-ps&viewFallbackFrom=win10-ps

      getlocale()

      Windows
      Get-WinSystemLocale
      Unix

      locale - a
    """
    import calendar
    import locale

    locale.setlocale(locale.LOC_ALL, ''

    print(calendar.calendar(2022))

❎ 281 - O módulo os para interação com o sistema

    """

      ->  Doc: https://docs.python.org/3/library/os.html

      ->  O módulo os fornece funções para interagir com o sistema  
           operacional
      ->  Por exemplo, o módulo os.path contém funções para trabalhar com 
            caminhos de arquivos e a função os.listdir() pode ser usada
            para listar arquivos em um diretório. O método os.system()
            permite executar comandos do sistema operacional a partir do
            seu código python.
      ->  Windows 11 (Powershell), Linux, Mac = clear
      ->  Windows (antigo, cmd) = cls
    """

    import os 

    os.system('clear')
    os.system('echo "Hello world"')

    print('a' * 80)
    print('a' * 80)
    print('a' * 80)
    print('a' * 80)
    print('a' * 80)
    print('a' * 80)

❎ 282 - os.path trabalha com caminhos em Windows, Linux e Mac

    """


      -> Doc: https://docs.python.org/3/library/os.path.html#module-os.path

      ->  os.path é um módulo que fornece funções para trabalhar com 
            com caminhos de arquivos em Windows, Mac ou Linux precisar se 
            preocupar com as diferenças entre esses sistemas.

      ->  Exemplos do os.path:

      ->  os.path.join: junta string em um único caminho. Desse modo,
            os.path.join('pasta_1', 'pasta_2', 'arquivo.txt') retornaria
            'pasta_1/pasta_2/arquivo.txt' no Linux ou Mac, e 
            'pasta_1\pasta_2\arquivo.txt' no Windows

      ->  os.pth.split: divide um caminho uma tupla (diretório, arquivo)
      ->  Por exemplo, os.path.split('/home/user/arquivo.txt')            
            retornaria ('/home/user', 'arquivo.txt').

      ->  os.path.xists: verifica se um caminho especificado existe.

      ->  os.path só trabalha com caminhos de arquivos e não faz nenhuma
            operação de entrada/saída (I/O) com arquivos em si.
    """

    import os

    caminho = os.path.join('Desktop', 'curso', 'arquivo.txt')

    # print(caminho)

    diretorio, arquivo = os.path.split(caminho)

    nome_arquivo, arquivo = os.path.splitext(arquivo)

    # print(nome_arquivo, extensao_arquivo)
    # print(os.path.exists('Users/luizotavio/Desktop/curso-python-rep'))
    # print(os.path.abspath('.'))

    print(caminho)
    print(os.path.basename(caminho))
    print(os.path.basename(diretorio))
    print(os.path.dirname(caminho))

❎ 283 - os.listdir para navegar em caminhos

    """
      ->  /Users/luizotavio/Desktop/EXEMPLO
      ->  c: \Users\luizotavio\Desktop\
      ->  caminho = r'C:\\Users\\luizotavio\\Desktop\\EXEMPLO'
    """

    import os

    caminho = os.path.join('/Users', 'luizotavio', 'Desktop', 'EXEMPLO')

    for pasta in os.listdir(caminho):
      caminho_completo_pasta = os.path.join(caminho, pasta)

      print(pasta)

      if not os.path.isdir(caminho_completo_pasta):
        continue

      for imagem in os listdir(caminho_completo_pasta):
        print(' ', imagem)

❎ 284 - os.walk para navegar de caminhos de forma recursiva

    """

      ->  os.walk é uma função qie permite percorrer uma estrutura de
            diretórios de maneira recursiva. Ela gera uma sequência de
            tuplas, onde cada tupla possui três elementos: o diretório
            atual (root), uma lista de subdiretórios (dirs) e uma
            lista dos arquivos do diretório atual (files).
    """

    import os 
    from itertools import count

    caminho = os.path.join('/Users', 'luizotavio', 'Desktop', 'EXEMPLO')
    counter = count()

    for root, dirs, file in os.walk(caminho):
      the_counter = next(counter)
      print(the_counter, 'Pasta atual', root)

      for dir_ in dirs:
        print(' ', the_counter, 'Dir:', dir_)

      for file_ in files:
        caminho_completo_arquivo = os.path.join(root, file_)
        print(' ', the_count, 'FILE:', caminho_completo_arquivo)
        # NÃO FAÇA ISSO (VAI PAGAR TUDO DA PASTA)
        # os.unlink(caminho_caminho_arquivo)

❎ 285 - os.path.getsize e os.stat para dados dos arquivos (tamanho em bytes)

    import math 
    import os 
    from itertools import count

    def formata_tamanho(tamanho_em_bytes: int, base: int =  1000) ->str:
    """ Formata um tamanho para o tamanho apropriado"""

      # Original
      # https://stackoverflow.com/questions/5194057/better-way-to-convert-file-sizes-in-python

      # Se o tamanho for menor ou igual a 0, 0B.
      if tamanho_em_bytes <= 0:
        return "OB"

      # Tupla com os tamanhos
      #                     0    1     2      3   4     5
      abreviacao_tamanho = "B", "KB", "MB","GB", "TB", "PB" 
      # Logaritmo ->  https://brasilescola.uol.com.br/matematica/logaritmo.htm
      # math.log vai retornar o logaritmo do tamanho_em_bytes
      # com o base (1000 por padrão), isso deve bater
      # com o nosso índice na abreviação dos tamanhos
      indice_abreviacao_tamanhos = int(math.log(tamanho_em_bytes, base))
      # Por quanto nosso tamanho deve ser dividido para gerar 
      #     o tamanho correto.
      potencia = base ** indice_abreviacao_tamanhos
      #  Nosso tamanho final
      tamanho_final = tamanho_em_bytes / potencia
      # A abreviação que queremos
      abreviacao_tamanho = abreviacao_tamanho[indice_abreviacao_tamanhos]
      return f'{tamanho_final:.2f} {abreviacao_tamanho}'


    caminho = os.path.join('/Users', 'luizotavio', 'Desktop', 'EXEMPLO')
    counter = count()

    for root, dirs, files in os.walk(caminho):
      the_counter = next(counter)
      print(the_counter, 'Pasta atual', root)

      for dir_ in dirs:
        print(' ', the_counter, 'Dir:', dir_)

      for file_ in files:
        caminho_completo_arquivo = os.path.join(root, file_)
        # tamanho = os.path.getsize(caminho_completo_arquivo)
        stats = os.stat(caminho_completo_arquivo)
        tamanho = stat.st_size
        print(' ', the_counter, 'FILE:' , file_, formata_tamanho(tamanho))
        # NÃO FAÇA ISSO (VAI PAGAR TUDO DA PASTA)
        # os.unlink(caminho_completo_arquivo)

❎ 286 - os + shutil - Copiando arquivos e criando pastas com Python

    """
      ->  Vamos copiar arquivos de uma pasta para outra.
      ->  Copiar -> shutil.copy
      ->  Apagar -> os.unlink
      ->  Apagar diretórios recursivamente -> shutil.rmtree
    """
    import os
    import shutil

    HOME = os.path.expanduser('~')
    DESKTOP = os.path.join(HOME, 'Desktop')
    PASTA_ORIGINAL = os.path.join(DESKTOP, 'EXEMPLO')
    NOVA_PASTA = os.path.join(DESKTOP, 'NOVA_PASTA')

    os.makedir(NOVA_PASTA, exist_os=True)

    for root, dirs, file in os.walk(PASTA_ORIGINAL):
      for dir_ in dirs
        caminho_novo_diretorio = os.path.join(
          root.replace(PASTA_ORIGINAL, NOVA_PASTA), dir_
        )
        os.makedirs(caminho_novo_diretorio, exist_ok=True)

      for file in files:
        caminho_arquivo = os.path.join(root, file)  
        caminho_novo_arquivo = os.path.join(
          root.replace(PASTA_ORIGINAL, NOVA_PASTA), file
        )
        shutil.copy(caminho_arquivo, caminho_novo_arquivo)

❎ 287 - os + shutil - Apagando, copiando movendo e renomeando pastas com Python

    """
    ->  Vamos copiar arquivos de uma pasta para outra.
    ->  Copiar -> shutil.copy  
    ->  Copiar Árvore recursivamente -> shutil.copytree
    ->  Apagar Árvore recursivamente -> shutil.rmtree 
          ❗❗CUIDADO AO USAR ISSO
    ->  Apagar arquivos ->  os.unlink ❗❗CUIDADO AO USAR ISSO
    ->  Renomear/Mover shutil.move ou os.rename
    """

    import os 
    import shutil


    HOME = os.path.expanduser('~')
    DESKTOP = os.path.join(HOME, 'Desktop')
    PASTA_ORIGINAL = os.path.join(DESKTOP, 'EXEMPLO')
    NOVA_PASTA = os.path.join(DESKTOP, 'NOVA_PASTA')

    shutil.rmtree(NOVA_PASTA, ignore_errors=True)
    shutil.copytree(PASTA_ORIGINAL, NOVA_PASTA)
    # shutil.move(NOVA_PASTA, NOVA_PASTA + '_EITA')
    shutil.rmtree(NOVA_PASTA, ignore_errors=True)

    ...

❎ 288 - O que é JSON - JavaScrip Object Notation

🎀 O que é Json?

🎀 JSON - JavaScript Object Notation (extensão.json)

🎀 É uma estrutura de dados que permite a serialização de objetos em texto simples para facilitar a transmissão de dados através da rede, APIs web ou outros meios de comunicação.

🎀 O JSON suporta os seguintes tipo de dados:

👉 Números: podem ser inteiros ou com ponto flutuante como 42 ou 3.14

👉 Strings: são cadeias de caracteres, como "Olá mundo!" ou "1234"

👉 ❗As strings devem ser envolvidas por aspas duplas❗

👉 Booleans: são os valores verdadeiro (true) ou falso (false)

👉 Arrays : são listas ordenadas de valores, como [1, 2, 3] ou ["Oi", "Olá", "Bom dia"]

👉 Objetos: são conjuntos de pares nome/valor -> {"nome": "João", "idade": 30}

👉 null: é um valor especial que representa ausência de valor

🎀 Ao converter Python para JSON:

Python JSON
dict object
list array
tuple array
str string
int number
float number
True true
False false
None null

Exemplo:

  {
    "title": "O Senhor dos Anéis: A Sociedade do Anel",
    "original_title": "The Lord of the Rings: The Fellowship of the Ring",
    "is_movie": true,
    "imdb_rating": 8.8,
    "year": 2001,
    "characters": ["Frodo", "Sam", "Gandalf", "Legolas", "Boromir"],
    "budget": null
  }

❎ 289 - json.dumps e json.laods com strings + typing.TypedDict

    import json
    # from pprint import pprint
    from typing import TypedDict

    class Movie(TypeDict):
      title: str
      original_title: str
      is_movie: bool
      imdb_ranting: float
      year: int
      characters: list[str]
      budget: None | float

      string_json = '''
      {
        "title": "O Senhor dos Anéis: A Sociedade do Anel",
        "original_title": "The Lord of the Rings: The Fellowship of the Ring",
        "is_movie": true,
        "imdb_rating": 8.8,
        "year": 2001,
        "characters": ["Frodo", "Sam", "Gandalf", "Legolas", "Boromir"],
        "budget": null
      }
      '''

      filme: Movie = json.loads(string_json)

      # print(filme, width=40)
      # print(filme['title'])
      # print(filme['characters'][0])
      # print(filme['year'])

      json_string = json.dumps(filme, ensure_ascii=False, indent=2)
      print(json_string)

❎ 290 - json.dump e json.laod com arquivos

🎀 Usando o exemplo de json da aula 288.

    import json
    import os

    NOME_ARQUIVO = 'aula_177.json'
    CAMINHO_ABSOLUTO_ARQUIVO = os.path.abspath(os.path.join(
      os.path.dirname(__file__), NOME_ARQUIVO
    ))

    filme = {

        'title': 'O Senhor dos Anéis: A Sociedade do Anel',
        'original_title': 'The Lord of the Rings: The Fellowship of the Ring',
        'is_movie': true,
        'imdb_rating': 8.8,
        'year': 2001,
        'characters': ['Frodo', 'Sam', 'Gandalf', 'Legolas', 'Boromir'],
        'budget': None
    }

    with open(CAMINHO_ABSOLUTO_ARQUIVO, 'w') as arquivo:
      json.dump(filme, arquivo, ensure_ascii=False, indent=2)

    with open(CAMINHO_ABSOLUTO_ARQUIVO, 'r') as arquivo:
      filme_do_json = json.load(arquivo)
      print(filme_do_json)

❎ 291 - Manipulando caminhos, pastas e arquivos no Python com pathlib(aula externa)

👉 O conteúdo da aula está no video

Alt text

❎ 292 - CSV (Comma Separated Values - Valores separados por vírgulas)

🎀 CSV (Comma Separated Values - Valores separados por vírgulas)

🎀 É um formato de arquivo que armazena dados em forma de tabela, onde cada linha representa um linha da tabela e as colunas são separadas por vírgula.

🎀 Ele é amplamente utilizado para transferir dados entre sistemas de diferentes plataformas, como por exemplo, para importar ou exportar dados para uma planilha (google Sheets, Excel, LibreOffice Calc) ou para um base de dados.

🎀 Um arquivo CSV geralmente tem a extensão ".csv" e pode ser aberto em editor de texto ou uma planilha eletrônica.

👉 Um exemplo de um arquivo CSV pode ser:

    Nome,Idade,Endereço
    Luiz Otávio,32,"Av Brasil, 21, Centro"
    João da Silva,55,"Rua 22, 44, Nova Era"

🎀 A primeira linha do arquivo define os nomes das colunas da, enquanto as linha seguintes contêm os valores das linhas, separadas por vírgulas.

🎀 Regas simples com CSV

👉 1 - Separe os valores das colunas com um delimitador único (,)

👉 2 - Cada registro deve estar em uma linha

👉 3 - Não deixar linhas ou espaços sobrando

👉 4 - Use o caractere de escape (") quando o delimitador aparecer no valor.

❎ 293 - csv.reader e csv.DictReader para escrever em CSV

    """
      ->  csv.reader e csv.DictReader 
      ---------------------------------------
      ->  csv.reader lê o csv em formato de lista
      ->  csv.DictReader lê o CSV em formato de dicionário
    """

    import csv
    from pathlib import Path

    CAMINHO_CSV = Path(__file__).parent/'aula_179.csv'

    with open(CAMINHO_CSV, 'r') as arquivo:
      leitor = csv.DictReader(arquivo)

      for linha in leitor:
        print(linha['Nome'], linha['Idade'], linha['Endereço'])


    # with open(CAMINHO_CSV, 'r') as arquivo:
    #   leitor = csv.reader(arquivo)

    #   for linha in leitor
    #     print(linha)

❎ 294 - csv.writer e csv.DictWrinter para escrever em CSV

    """
      ->  csv.reader lê o csv em formato de lista
      ->  csv.DictReader lê o CSV em formato de dicionário
    """
    import csv 
    from pathlib import Path

    CAMINHO_CSV = Path(__file__).parent/ 'aula_180.csv'

    lista_clientes = [
      {'Nome': 'Luiz Otávio', 'Endereço': 'Av 1, 22' },
      {'Nome': 'João Silva', 'Endereço': 'R. 2, "1"'},
      {'Nome': 'Maria Sol', 'Endereço': 'Av B, 3A'},
    ]

    with open(CAMINHO_CSV 'w') as arquivo:
      nome_colunas = Lista_clientes[0].keys()
      escritor = csv.DictWriter(arquivo, fieldnames=nome_colunas)
      escritor.writeheader()

      for cliente in lista_clientes:
        print(cliente)
        escritor.writerow(cliente)

    #  lista_clientes = [
    #   [ 'Luiz Otávio',  'Av 1, 22' ],
    #   [ 'João Silva',  'R. 2, "1"'],
    #   [ 'Maria Sol',  'Av B, 3A'],
    #  ]

    # with open(CAMINHO_CSV 'w') as arquivo:
    #    nome_colunas ['Nome', 'Endereço']
    #    escritor = csv.writer(arquivo)

    #    for cliente in lista_clientes:
    #     escritor.writerow(cliente)

❎ 295 - random tem geradores de números pseudoaleatóros (randrange, randint, uniform)

    """
      ->  random tem geradores do números pseudoaleatórios
      -------------------------------------------------------
      ->  Obs.: números pseudoaleatórios significa que os números parecem
            ser aleatórios, mas na verdade não são. Portanto, este módulo
            não dee ser usando para segurança ou uso criptográfico.
      ->  O motivo disso é que quando temos uma mesma entrada e um mesmo
            algorítimo, a saída pode ser previsível.

      ->  doc: https://docs.python.org/pt-br/3/library/random.html
    """

    import random

    # Funções:
    # seed
    # ->  Inicializa o gerador de random (por isso "números 
    #       pseudoaleatórios") random.seed(0)

    # random.randrange(início, fim, passo
    # ->  Gera um número inteiro aleatório dentro de um intervalo 
    #       específico
    r_range = random.randrange(10, 20, 2)

    print(r_range)

    # random.randint(inicio, fim)
    # ->  Gera um número inteiro aleatório dentro de um intervalo 
    #     "sem passo"
    r_int = random.randint(10, 20)

    print(r_int)

    # random.uniform(inicio, fim)
    # ->  Gera um número flutuante aleatório dentro de um intervalo 
    #       "sem passo"
    r_uniform = random.uniform(10, 20)

    print(r_uniform)

    # random.shuffle(SequenciaMutável) -> Embaralha a lista original
    nomes = ['Luiz', 'Maria', 'Helena', 'Joana']
    random.shuffle(names)

    print(names)

    # random.samples(Iterável, k=N)
    # ->  Escolhe elementos do iterável e retorna outro iterável 
    #       (não repete)
    novos_nomes = random.sample(nomes, k=3)

    print(nomes)
    print(novos_nomes)

    # random.choices(Iterável, k=N)
    # -> Escolhe elementos do iterável e retorna outro iterável 
    #     (repete o valores )
    novos_nomes = random.choices(nomes, k=3)

    print(nomes)
    print(novos_nomes)

    # random.choice(Iterável) ->  Escolhe um elemento do iterável
    print(random.choice(nomes))

❎ 296 - random tem geradores de números pseudoaleatóros (sample, choices, seed)

🎀 Continuação da aula anterior

❎ 297 - secrets gera números aleatórios seguros

    import secrets

    # import as s
    # from secrets import SystemRandom as Sr

    # print(' '.join(
    #     Sr().choices(
    #       s.ascii_letters + s.digits + s.punctuation, k=64)))

    # python -c "import string as s; from secrets import SystemRandom as Sr;print(' '.join(Sr().choices(s.ascii_letters + punctuation + s.digits,k=12)))"

  random = secrets.SystemRandom()

  # print(secrets.randbelow(100))
  # print(secrets.choice([10, 11, 12]))

  # Funções:
  # seed
  #   ->  NÃO FAZ NADA

  random.seed(10)

  # random.randrange(início, fim, passo
  # ->  Gera um número inteiro aleatório dentro de um intervalo 
  #       específico
  r_range = random.randrange(10, 20, 2)

  print(r_range)

  # random.randint(inicio, fim)
  # ->  Gera um número inteiro aleatório dentro de um intervalo 
  #     "sem passo"
  r_int = random.randint(10, 20)

  print(r_int)

  # random.uniform(inicio, fim)
  # ->  Gera um número flutuante aleatório dentro de um intervalo 
  #       "sem passo"
  r_uniform = random.uniform(10, 20)

  print(r_uniform)

  # random.shuffle(SequenciaMutável) -> Embaralha a lista original
  nomes = ['Luiz', 'Maria', 'Helena', 'Joana']
  random.shuffle(names)

  print(names)

  # random.samples(Iterável, k=N)
  # ->  Escolhe elementos do iterável e retorna outro iterável 
  #       (não repete)
  novos_nomes = random.sample(nomes, k=3)

  print(nomes)
  print(novos_nomes)

  # random.choices(Iterável, k=N)
  # -> Escolhe elementos do iterável e retorna outro iterável 
  #     (repete o valores )
  novos_nomes = random.choices(nomes, k=3)

  print(nomes)
  print(novos_nomes)

  # random.choice(Iterável) ->  Escolhe um elemento do iterável
  print(random.choice(nomes))

❎ 298 - string.template para substituir valores em textos

    """
      ->  doc: https://docs.python.org/3/library/string.html#template-strings

      ->  Métodos:
      ->  substitute: substitui mas gera erros se faltar chaves
      ->  safe_substitute: substitui sem gerar erros
      ->  Você também pode trocar o delimitador e outras coisas cirando
            uma subclasse de template.
    """

    import locale
    import string
    from datetime import datetime
    from pathlib import Path

    CAMINHO_ARQUIVO = Path(__file__).parent/ 'aula_183.txt'

    locale.setlocale(locale.LC_ALL, '')

    def convert_para_brl(numero: float) -> str:
      brl = 'R$' + locale.currency(numero, symbol=False, grouping=True)
      return brl

    data = datetime(2022, 12, 28)
    dados = dict(
      nome='João',
      valor=converte_para_brl(1_234_456),
      data=data.strftime('%d/%m/%Y'),
      empresa='O. M.',
      telefone='+55 (11) 7890-5432'
    )


    class MyTemplate(string.Template):
      delimiter = '%'

      with open(CAMINHO_ARQUIVO, 'r') as arquivo:
        texto = arquivo.read()
        template = MyTemplate(texto)

        print(template.sbstitute(dados))

❎ 299 - (Parte 1) Variáveis de ambiente com os.getenv, os.environ e python-dotenv

    """
      ->  Para variáveis de ambiente
      ->  Windows PS: $env:VARIAVEL="VALOR" | dir env:
      ->  Linux e Mac: export NOME_VARIAVEL="VALOR" | echo $VARIAVEL
      ->  Para obter o valor das variáveis de ambiente
      ->  os.getenv ou os.environ['VARIAVEL']
      ->  Para configurar variáveis de ambiente
      ->  os.environ['VARIAVEL'] = 'valor'
      ->  Ou usando python-dotenv e o arquivo .env
      ->  pip install python-dotenv
      ->  from dotenv import load_dotenv
      ->  laod_dotenv()
      ->  https://pypi.org/project/python-dotenv/
      ->  OBS.: sempre lembre-se de criar um .env-example
    """
    import os 
    from dotenv import load_dotenv # type: ignore

    laod_dotenv()

    # print(os.environ)

    print(os.getenv('BD_PASSWORD'))

❎ 300 - (Parte 2) Variáveis de ambiente com os.getenv, os.environ e python-dotenv(.env)

🎀 Continuação do tema anterior

❎ 301 - python-dotenv explicação simples em texto

🎀 python-dotenv é uma biblioteca Python que permite que você faça uso de arquivos de configuração para armazenar e acessar as suas variáveis de ambiente de forma mais fácil e segura em seus projetos

🎀 As variáveis de ambiente são valores que podem ser usados em seu código e que podem variar dependendo do ambiente em que o seu código está sendo executado (por exemplo, o ambiente de produção ou o ambiente de desenvolvimento).

🎀 Para utilizar o python-dotenv, basta instalá-lo com p pip e, em seguida, adicionar um arquivo chamando .env na raiz do seu projeto

  # Ative seu ambiente virtual
  pip install python-dotenv 
  # ou
  poetry add python-dotenv 

🎀 Esse arquivo deve conter as suas variáveis de ambiente e seguir o seguinte formato:

# .env

VARIAVEL_DE_AMBIENTE_1=valor
VARIAVEL_DE_AMBIENTE_2=valor
VARIAVEL_DE_AMBIENTE_3=valor

🎀 Em seu código, você pode acessar essas variáveis usando o módulo os e a função os.getenv(), por exemplo:

   import os

   valor_da_variavel_1 = os.getenv("VARIAVEL_DE_AMBIENTE_1")

🎀 O python-dotenv funciona lendo o arquivo .env e adicionando as variáveis de ambiente ao ambiente do sistema operacional, de forma que elas fiquem disponíveis para seu código usando a função os.getenv().

🎀 Isso é útil, por exemplo, para não expor senhas ou outras informações confidenciais em seu código ou em repositórios de códigos compartilhados, pois o arquivo .env pode ser adicionado ao .gitignore para não incluir nos commits. Crie um .env-example para exemplificar como usar o seu programa com valores fictícios.

🎀 Além disso, o python-dotenv também permite que você use um arquivo .env para armazenar valores de configuração específicos de cada ambiente, o que pode ser útil quando você estiver trabalhando em um projeto com diferentes ambientes de desenvolvimento, teste e produção.

👉 doc: https://pypi.org/project/python-dotenv/

❎ 302 - Configurando o SMTP e senhas de apps no GMAIL para enviar e-mails com Python

👉 o exemplo está no link abaixo

Alt text

❎ 303 - Enviando E-mails SMTP com Python

🎀 a aplicação foi desenvolvida na aula anterior

❎ 304 - (Parte 1) ZIP - Compactando Descompactando arquivos com zipfile.ZipFile

    import os
    import Shutil
    from pathlib import Path
    from zipfile import ZipFile

    # Caminhos
    CAMINHO_RAIZ = Path(__file__).parent
    CAMINHO_ZIP_DIR = CAMINHO_RAIZ/'aula_186_diretorio_zip'
    CAMINHO_COMPACTADO = CAMINHO_RAIZ/'aula_186_diretorio.zip'
    CAMINHO_DESCOMPACTADO = CAMINHO_RAIZ/'aula_186_descompactado'

    shutil.rmtree(CAMINHO_ZIP_DIR, ignore_errors=True)
    Path.unlink(CAMINHO_COMPACTADO, missing_ok=True)
    shutil.rmtree(str(CAMINHO_COMPACTADO).replace('.zip', ''),ignore_errors=True) 
    shutil.rmtree(CAMINHO_DESCOMPACTADO, ignore_errors=True)

    # raise Exception()

    # Cria o diretório para a aula
    CAMINHO_ZIP_DIR.mkdir(exist_ok=True)

    def criar_aquivos(qtd: int, zip_dir: Path):
      for i in range(qtd):
        texto = 'arquivo_%s' % i
        with open(zip_dir / f'{texto}.txt', 'w') as arquivo:
          arquivo.write(text)



    criar_arquivo(10,CAMINHO_ZIP_DIR)

❎ 305 - (Parte 2) ZIP - Compactando Descompactando arquivos com zipfile.ZipFile

    ...

    with zipFile(CAMINHO_COMPACTADO, 'w') as zip:
      for root, dirs, files in os.walk(CAMINHO_ZIP_DIR):
        for file in files:
          # print(file)
          zip.write(os.path.join(root,file), file)

    # lendo arquivos de um zip
    with Zipfile(CAMINHO_COMPACTADO, 'r') s zip:
      for arquivo in zip.namelist()
        print(arquivo)

    # Extraindo arquivo de um zip
    with ZipFile(caminho),'r' as zip:
      zip.extractall(CAMINHO_DESCOMPACTADO)

❎ 306 - sys.argv - Executando arquivos com argumentos no sistema

    import sys

    argumentos = sys.argv
    qtd_argumentos = len(argumentos)

    if qtd_argumentos <= 1:
      print('Você não passou argumentos')
    else:
      try:
        print(f'Você passou os argumentos {argumentos[1:]}')
        print(f'Faça alguma coisa com {argumentos[1]}')
        print(f'Faça outra coisa com {argumentos[2]}')
      except IndexError:
        print('Faltam Argumentos')

❎ 307 - argparse.ArgumentParser para argumentos mais complexos

    """
      ->  Tutorial Oficial:
      ->  https://docs.python.org/pt-br/3/howto/argparse.html
    """
    from argparse import ArgumentParser

    parser = ArgumentParser()

    parser.add_argument(
      '-b', '--basic',
      help='Mostra "Olá mundo na tela"',
      # type=str, # Tipo do argumento
      metavar='STRING',
      # default='Olá mundo', # valor padrão
      required=False,
      action='append', # Recebe o argumento mais de uma vez
      # nargs='+', Recebe mais de um valor
    )

    parser.add_argument(
      '-v', '--verbose',
      help='Mostra logs',
      action='store_true'
    )

    args = parser.parse_args()

    if args.basic is None:
      print('Você não passou o valor de b.')
      print(args.basic)
    else:
      print('O valor de basic:', args.basic)

    print(ags.verbose)

❎ 308 - (Parte 1) Básico do protocolo HTTP (HyperText Transfer Protocol)

🎀 HTTP (HyperText Transfer Protocol) é um protocolo usado para enviar e receber dados na Internet. Ele funciona no modo cliente/servidor, onde o cliente (seu navegador, por exemplo) faz uma requisição ao servidor (site, por exemplo), que responde com os dados adequados.

🎀 A mensagem de requisição do cliente deve incluir dados como:

👉 O método HTTP

🔱 leitura (safe) - GET, HEAD (cabeçalho), OPTIONS (métodos suportados)

🔱 escrita - POST, PUT (substitui), PATCH (atualização), DELETE

👉 O endereço do recurso a ser acessado (/users/)

👉 Os cabeçalhos HTTP (Content-Type, Authorization)

👉 O Corpo da mensagem (caso necessário, de acordo com o método HTTP)

🎀 A mensagem de resposta do servidor deve incluir dados como:

👉 código de status HTTP (200 success, 404 Not found, 301 Moved Parmanently)

https://developer.mozilla.org/en-US/docs/Web/HTTP/Status

👉 Os cabeçalhos HTTP (Content-Type, Accept)

👉 O corpo da mensagem (Pode estar em vazio em alguns casos)

❎ 309 - (Parte 2) Básico do protocolo HTTP (HyperText Transfer Protocol)

🎀 Continuação da aula anterior

❎ 310 - http.server - servindo arquivos HTML e CSS via HTTP com um comando Python

teste http

🎀 servir um pagina html com python

ports comuns 3333, 8080, 8000, 3000, 3001,443

  python -m http.server -d  + o nome da pasta que esta site port 

❎ 311 - requests para requisições HTTP com Python (entenda request e response)

  """
    ->  Tutorial -> https://youtu.be/Qd8JT0bnJGs

    poetry add requests types-requests
  """
    import requests

    # http:// -> 80
    # https:// -> 443

    url = 'http://localhost:3333'

    response = requests.get(url)

    print(response.status_code)
    print(response.headers)
    print(response.content)
    print(response.json())
    print(response.text)

❎ 312 - (Parte 1) Web Scraping com Python usando requests e bs4 BeautifilSuop

🎀 Web Scraping é o ato de "raspar a web" buscando informações de forma automatizada, com determinada linguagem de programação, para uso posterior.

🎀 O módulo requests consegue carregar dados da Internet para dentro do seu código. Já o bs4.BeautifilSuop é responsável por interpretar os dados HTML em forma de objetos Python para facilitar a vida do desenvolvedor.

🎀 Doc: https://www.crummy.com/software/BeautifulSoup/bs4/doc.ptbr/

❎ 313 - (Parte 2) Web Scraping com Python usando requests e bs4 BeautifilSuop

🎀 continuação da aula anterior

❎ 314 - Adicionando "encoding" no BeautifilSuop 4 para evitar problemas com caracteres

🎀 Uma coisa de ocorrer quando trabalhamos com bs4.BeautifilSuop. é problemas com caracteres. Isso ocorre divido a uma falha na detecção do encoding.

🎀 Caso queria mudar a codificação de caracteres, envie os bytes diretamente para o BeautifilSuop e passe o valor da codificação de caracteres no atributo "from_encoding". Exemplo (para utf-8):

  BeautifilSuop(response.content, 'html.parser', from_encoding='utf-8')

🎀 Perceba que troquei "response.text" para "response.content" para obter os bytes ao invés da string.

🎀 Neste caso, nosso código completo das aulas anteriores ficaria assim

    import re
    import requests
    from bs4 import BeautifulSoup

    url  = 'http://127.0.0.1:3333/'
    response = requests.get(url)
    raw_html = response.text
    parsed_html = BeautifulSoup(raw_html, 'html.parser',  from_encoding='utf-8')
    op_jobs_heading = parsed_html.select_one('#intro > div > div > article > h2')

    if top_jobs_heading is not None:
        article = top_jobs_heading.parent
        if article is not None:
            for p in article.select('p'):
                print(re.sub(r'\s{1,}', ' ', p.text).strip())

🎀 Assumindo que a codificação de caracteres da ágina é utf-8.

🎀 Você pode detectar isso no HTML pela teg meta charset dentro da <head>

  <meta charset="UTF-8" >

❎ 315 - Escolhendo e baixando o chrome drive para o Selenium e Google Chrome

Chromedriver

🎀 foi usado o web driver manager nos projetos

❎ 316 - Selenium - Automatizando tarefas no navegador

Selenium

❎ 317 - Selenium - Selecionando elementos com By, expected_conditions e WebDiverWait

🎀 continuação

❎ 318 - Selenium - Enviando teclas com a classe keys

🎀 continuação

❎ 319 - Selenium - find_element e find_elements By

🎀 continuação

❎ 320 - TEORIA: subprocess para executando programas e comandos externos

🎀 Subprocessos é um módulo do Python para executar processo em comandos esternos no seu programa.

🎀 O método mais simples para atingir o objetivo é usando subprocess.run()

🎀 Argumentos princpais de subprocess.run():

👉 stdout, stdin e stderr -> Redirecionam saída, entrada e erros

👉 capture_output -> captura a saída e erro para uso prosterior

👉 text -> Se True, entradas e saída serão tratadas como text e$$ atomaticamente codifcadas ou decontificadas para o conjunto de caracteres padrão da plataforma (geralmente UTF-8)

👉 shell -> Se True, terá acesso ao shell do sistema. Ao usar shell (True), recomendo enviar o comando e os argumentos juntos.

👉 executeble -> pode ser usado para especificar o caminho do excutável que iniciará o subprocesso.

🎀 Retorno:

👉 stdout, stderr, returncode e args

Importante:

A codificação de caracteres do Windows poder diferente. Tente usar CP1252, CP852, CP850 (outros). Linux e Mac, use utf_8

Dicas:

Coamando de exemplo: Windows: ping 127.0.0.1 Linux/Mac: ping 127.0.0.1 -c 4

❎ 321 - subprocess para executando programas e comandos externos

    import subprocess
    import sys

    # sys.platform - linux, linux2, darwin, win32

    cmd = ['ls -lah /']
    encoding = 'ttf_8'
    system = sys.platform

    if system == 'win32':
      cmd = ['ping', '127.0.0.1']
      encoding = 'cp850'

    proc = subprocess.run(
      cmd,capture_outoug
    )

    print()

    # print(proc.args)
    # print(proc.stderr)
    print(proc.stdout)            
    # print(proc.returncode)

❎ 322 - Jupter NotebooK - Instalação e test

notebook

❎ 323 - Jupter NotebooK - Exemplos

aula exemplo

❎ 324 - (Parte 1) Threads - Executando processamentos em paralelo

Threads

❎ 325 - (Parte 2) Threads - Executando processamentos em paralelo

🎀 continuação

❎ 326 - (Parte 3) Threads - Executando processamentos em paralelo

🎀 continuação

❎ 327 - PyPDF2 para manipular arquivos PDF(instalação)

🎀 PyPDF2 é uma biblioteca de manipulação de arquivos PDF feita em Python puro gratuita e de código aberto. Ela é capaz de ler, manipular, escrever e unir dados de arquivos PDF, assim como adicionar atotações, transformar páginas extrair texto e imagens, manipular metadados, e mais.

🎀 A documentação contém todas as informações necessárias para usar PyPDF2.

🎀 link : https://pypdf2.readthedocs.io/en/3.0.0/

pip install pypdf2 ou poetry add pypdf2

❎ 328 - PyPDF2 para manipular arquivos PDF(PdfReader)

PDF editor python

❎ 329 - PyPDF2 para manipular arquivos PDF(PdfWriter)

🎀 continuação

❎ 330 - PyPDF2 para manipular arquivos PDF(PdfMerger)

🎀 continuação

❎ 331 - Deque - Trabalhando com LIFO e FIFO

🎀 Lifo e e fifo

🎀 pilha e fila

👉 Lifo (Last In First Out)

👉 Pilha (stack)

🎀 Significa que o último item a entrar será o primeiro a sair (list)

🎀 Artigo:

https://www.otaviomiranda.com.br/2020/pilhas-em-python-com-listas-stack/

Vídeo:

video da aula

🎀 Para tirar itens do final: O (1) Tempo constante

🎀 Para tirar itens do início: O(n) Tempo Linear

  from collections impor deque

  lista = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

  # ✅ Legal (LIFO com lista)

  #  0  1  2  3  4  5  6  7  8  9
  # [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
  lista.append(10)
  #  0  1  2  3  4  5  6  7  8  9  10
  # [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
  lista.append(11)
  #  0  1  2  3  4  5  6  7  8  9  10  11
  # [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11]
  ultimo_removido = lista.pop()
  #  0  1  2  3  4  5  6  7  8  9  10
  # [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
  print('ùtomo:', ultimo_removido)
  print('Lista:', lista)
  #  0  1  2  3  4  5  6  7  8  9  10
  # [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
  print()

  lista = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

    # ‼️ 🚫 Ruim (FIFO com lista)
  #  0  1  2  3  4  5  6  7  8  9
  # [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
  lista.insert(0, 10)
  #  0  1  2  3  4  5  6  7  8  9  10
  # [10, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
  lista.insert(0, 11)
  #  0    1  2  3  4  5  6  7  8  9  10 11
  # [11, 10, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
  primeiro_removido = lista.pop(0) #11
  #  0   1  2  3  4  5  6  7  8  9  10 
  # [10, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
  print('Primeiro:', primeiro_removido) #11
  print('Lista', lista) # [10, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
  print()

🎀 FIFO (First In First Out)

👉 Filas (queue)

🎀 Significa que o primeiro item a entrar será o primeiro a sair (deque)

🎀 Artigo

https://www.otaviomiranda.com.br/2020/filas-em-python-com-deque-queue/

Vídeo:

video da aula

🎀 Para tirar itens do final: O(1) Tempo constante

🎀 Para tirar itens do início: O(1) Tempo constante

✅ legal (FIFI com deque)

fila_correta: deque[int] = deque()
fila_correta.append(3) # adciona no final
fila_correta.append(4) # adciona no final
fila_correta.append(5) # adciona no final
fila_correta.appendleft(2) # adiciona no começo
fila_correta.appendleft(1) # adiciona no começo
fila_correta.appendleft(0) # adiciona no começo
print(fila_correta) # deque([0, 1, 2, 3, 4, 5])
fila_correta..pop() #5
fila_correta..popleft() #0
print(fila_correta) # deque([0, 1, 2, 3, 4])

❎ 332 - Dica: remove regras de tipos Unkown do linter do VS Code

🎀 Se você vem tendo problemas com tipagem Unknown no linter do VS Code, talvez desativar essas regras possa facilitar sua vida.

🎀 Os tipos Unkown, geralmente, vêm de bibliotecas com tipagem parcial, e eles constumam dar um certo trabalho para solucionar (ou deastivar)

[...]

❎ 333 - Openpyxl para arquivos Excel xlsx, xlsm, xltx e xltm

🎀 Com essa biblioteca será possível ler e escrever dados em células específicas, formatar células, inserir gráficos, criar fómulas, adicionar imagens e outros elementos gráficos às suas planilhas. Ela é útil para automatizar tarefas envolvido planilhas do Excel, como criação de relatórios e análise de dados e/ou facilitando a manipulação de grandes quantidade de informações.

👉 Instalação necessária pip install openpyxl ou poetry add Openpyxl

Mais Informações da Biblioteca:

Documentação: https://openpyxl.readthedocs.io/en/stable/

❎ 334 - Openpyxl - criando um planilha do Excel (Workbook e Worksheet)

Excel + Python

❎ 335 - Openpyxl - manipulando as planilhas do Workbook

🎀 continuação

❎ 336 - Openpyxl - ler e alterar dados de uma planilha

🎀 continuação

❎ 337 - Pillow: redimensionando imagens com Python

Editar imagens com python


🔜 ❎ Seção 7: PySide6 - Interface gráfica com QT 6 no Python - GUI para Desktop⚓︎


❎ 338 - O que é PySide6 e o motivo da substituição de PyQT5

🎀 Essa biblioteca (PySide e PyQT) usam a biblioteca QT.

🎀 Qt é uma biblioteca usada para a criação de GUI (interface gráficado suário) escrita em C++.

🎀 PySide e PyQt conseguem fazer a ponte (binding) entre o Python e a biblioteca para acriação de interfaces gráficas sem ter que usar outra limguagem de programação.

🎀 PySide6 é uma referencia á versão 6 do Qt (Qt 6)

🎀 Qt é multiplataforma, ou seja, deve funcionar em Windows, Linux e Mac.

🎀 PySide foi desenvolvido pela The Qt Company (da Nokia), como parte do projeto Qt for Python project - https://doc.qt.io/qtforpython/

🎀 Por usarem a mesma biblioteca (Qt), PySide e PyQt são extremamente similares, muitas vezes os códigos são idênticos. Portanto, mesmo que você ainda queira usar PyQt, será muito simples portar os códigos. Muitas vezes basta trocar o nome de PySide para PyQt e vice-versa.

🎀 A maior diferença entre PyQt e PySide está na licença: 👉 PyQt usa GPL ou commercial e PySide usa LGPL. 👉 Em resumo: com PySide, você tem a permissão uso da biblioteca para fins comerciais, sem ter que compartilhar o código escrito por você para o público. 👉 Licenças são tópicos complexos, portanto, se oriente sobre elas antes de usar qualquer software de terceiros. https://tldrlegal.com/license/gnu-lesser-general-public-license-v3-(lgpl-3)

❎ 339 - Download dos vídeos dessa seção antiga com PyQT5

Aula anterior

❎ 340 - Instalando o PySide6 no ambiente virtual

👉 Teste a baixo a versão do PySide6.

  import PySide6.QtCore

  print(PySide6.__version__)

  print(PySide6.QtCore.__version__)

❎ 341 - QApplication e QPushButton de PySide6.QtWidgets

🎀 QApplication -> O Widget principal da aplicação

🎀 QPushButton -> Um botão

🎀 PySide6.QtWidgets -> Onde estão os Widgets do PySide6

  import sys

  from PySide6.QtWidgets import QApplication, QPushButton

  app = QApplication(sys.argv)

  botao = QPushButton('Texto do botão')
  botao.setStyleSheet('font-size: 40px;')
  botao.show()  # adiciona o Widget na hierarquia e exibe a janela


  app.exec()  # o loop da aplicação

❎ 342 - QtWidget e QLayout de PySide6.QtWidgets

🎀 QtWidget -> genérico 🎀 QLayout -> um widget de layout que recebe outros widgets 🎀 QV(H)BoxLayout -> widgets na horisontal e na veritical 🎀 QGridLayout -> coloca o widget em estilo de coodendas de uma tabela

  import sys 

  from PySide6.QtWidgets import QApplication, QGridLayout, QPushButton, QWidget

  app = QApplication(sys.argv) 

  botao = QPushButton('Texto do botão')
  botao.setStyleSheet('font-size: 80px;')


  botao_2 = QPushButton('botão 2')
  botao.setStyleSheet('font-size: 40px;')

  botao_3 = QPushButton('botão 3')
  botao.setStyleSheet('font-size: 40px;')

  central_widget = QWidget()

  layout = QGridLayout()

  central_widget.setLayout(layout)

  layout.addWidget(botao, 1, 1, 1, 1)
  layout.addWidget(botao_2, 1, 2, 1, 1)
  layout.addWidget(botao_3, 3, 1, 1, 2)

  central_widget.show()

  app.exec()

❎ 343 - QMainWindw e centralWidget

🎀---> QApplication (app)
🎀-----> QMainWindow (window->setCentralWidget)
🎀-------> CentralWidget (central_widget)
🎀---------> Layout (layout)
🎀-----------> Widget 1 (botao_1)
🎀-----------> Widget 2 (botao_2)
🎀-----------> Widget 3 (botao_3)
🎀-----> show
🎀--> exec
  import sys 

  from PySide6.QtWidgets import (QApplication, QGridLayout, QMainWindow, QPushButton, QWidget)

  app = QApplication(sys.argv)

  window = QMainWindow()

  central_widget = QWidget()

  window.setCentralWidget(central_widget)
  window.setWindowTitle('Minha janela bonita')

  botao = QPushButton('Texto do botão')
  botao.setStyleSheet('font-size: 80px;')


  botao_2 = QPushButton('botão 2')
  botao.setStyleSheet('font-size: 40px;')

  botao_3 = QPushButton('botão 3')
  botao.setStyleSheet('font-size: 40px;')

  central_widget = QWidget()

  layout = QGridLayout()

  central_widget.setLayout(layout)

  layout.addWidget(botao, 1, 1, 1, 1)
  layout.addWidget(botao_2, 1, 2, 1, 1)
  layout.addWidget(botao_3, 3, 1, 1, 2)

  def slot_example(status_bar):
    status_bar.showMessage('O meu slot foi executado')

  # status bar
  status_bar = window.statusbar()
  status_bar.showMessage('Mostar mensagem na barra')

  # menu bar
  menu = window.menubar()
  primeiro_menu = menu.addMenu('Primeiro menu')
  primeira_acao = primeiro_menu.addAction('primeira ação')
  primeira_acao.triggered.connect( # type:ignore
    lambda: slot.example(status_bar)
  )

  window.show()

  app.exec()

❎ 344 - O básico sobre Signal e slots(eventos e documentação)

  import sys

  from PySide6.QCore import Slot
  from PySide6.QWidgets import (QApplication, QGridLayout, QMainWindow,
                                QPushButton, QUidget)

  app = QApplication(sys.argv)
  window = QMainWindow()
  central_widget = QWidget()
  Window.setCentralWidget(central_widget)
  window.setWindowTitle('Minha janela bonita')

  botao_1 = QPushButton('Texto do botão')
  botao_1-setStyleSheet('font-size: 80px;')

  botao_2 = QPushButton('botão 2')
  botao_2-setStyleSheet('font-size: 40px;')

  botao_3 = QPushButton('botão 3')
  botao_3-setStyleSheet('font-size: 40px;')

  layout = QGridLayout()
  center_widget.setLayout(layout)

  layout.addWidget(botao1, 1, 1, 1, 1)  
  layout.addWidget(botao2, 1, 2, 1, 1)
  layout.addWidget(botao3, 3, 1, 1, 2)

  @Sot()
  def slod_example(status_bar):
    def inner():
      status_bar.showMessage('O meu slot fou executado')
    return inner

  @slot()
  def outro_slot(checked):
    print('Está marcado?', checked)

  @Slot()
  def terceiro_slot(action):
    def inner():
      outro_slot(action.isCheckd())
    return inner


  status_bar = window.statusBar()
  primeiro_menu = menu.addMenu('Primeiro_menu')
  primeira_acao = primeiro_menu.addAtion('Primeira ação')
  primeira_acao.triggred.connect(slot_example(status_bar)) # type:ignore


  segunda_action = primeiro_menu.addAction('segunda ação')
  segunda_action.setCheckable(True)
  segunda_action.toggled.connect(outro_slod) # type:ignore
  segunda_action.havered.connect(terceiro_slot(segunda_action) ) # type:ignore

  botao_1.clicked.connect(terceiro_slot(sgunda_action)) # type:ignore

  window.show()
  app.exec()

❎ 345 - Trabalhando com classes e herança com o PySide6

Exemplo de herança

  import sys

  from PySide6.QtCore import Slot
  from PySide6.QtWidgets import (QApplication, QGridLayout, QMainWindow
                                  QPushButton, QWidget)

  class MyWindow(QMainWindow):
    def __init__(self, parent=None):
      super().__init__(parent)

      self.contral_widget = QWidget()

      self.setCentralWidget(self.central_widget)
      self.setWindowTitle('Minha janela bonita')

      self.botao_1 = self.make_button('Texto do botão')
      self.botao_1.clicked.connect(self.segunda_acao_marcada) # type:ignore

      self.botao_2 = self.mke_button('Botão 2')

      self.botao_3 = self.make_buntton('Terceiro')

      self.grid_layout = QGridLayout()
      self.central_widget.setLayout(set.grid_layout)

      self.grid_layout.addWidget(self.botao_1, 1, 1, 1, 1)
      self.grid_layout.addWidget(self.botao_2, 1, 2, 1, 1)
      self.grid_layout.addWidget(self.botao_3, 3, 1, 1, 2)

      self.status_bar =self.statusBar()
      self.status_bar.showMessage('Mostrar mensage na barra')

      self.menu = self.menuBar()
      self.primeiro_menu = self.menu.addMenu('Primeiro menu')
      self.primeira_acao = self.primeiro_menu.addAction('Primeira ação')
      self.primeira_acao.triggred.connect( # type:ignore
        self.muda_mensagem_da_status_bar)

      self.segunda_action = self.primeiro_menu.addAction('Segunda ação')
      self.segunda_action.sefCheckable(True)
      self.segunda_action.toggled.connect( # type:ignore
        self.segunda_acao_marcada)
      self.segunda_action.hovered.connect( # type:ignore
        self.segunda_acao_marcada)

      @Slot()
      def muda_mensagem_da_stauts_bar(self):
        self.stauts_bar.showMessage('O meu slot foi executado')

      @Slot
      def segunda_acao_marcada(self):
        print('Está marcado?', self.segunda_action.isCheked())

      def make_button(self, text):
        btn = QPushButton(text)
        btn.setStyleSheet('font-size: 80px;')
        return btn


  if __name__ == '__main__':
    app = QApplication(sys.argv)
    window = MyWindow()
    window.show()
    app.exec()

❎ 346 - Calculadora com PySide6 - Introdução

calculadora

❎ 347 - Calaculadora: Criando a janela principal com QMainWindow, QWindget e QVBoxLayout

🎀 o codigo está alocado na introdução ☝️

❎ 348 - Calculadora: ajustes e boas práticas

🎀 o codigo está alocado na introdução ☝️

❎ 349 - Calculadora: PascalCse, camelCase ou snake_case: Qual usar no PySide6?

👉 PascalCase É outro estilo de nomenclatura de texto usado em programação. Ele também combina várias palavras em uma única palavra, mas primeira letra de cada palavra é em maiúscula. Por exemplo, se quisermos nomear uma classe que representa um carro, podemos chamá-la de "Carro" em PascalCase.

👉 Camel case ou snake_case É estilo de nomenclatura de texto usado em programação que combina várias palavras em um única palavra, em que a primeira letra de cada palavra é escrita em minúscula, exceto a primeira palavra. Por exemplo, se quisermos nomear uma variável que represente a idade de uma pessoa, podemos chamá-la de "idadeDaPessoa" em CamelCase.

🎀 o codigo está alocado na introdução ☝️

❎ 350 - Calculadora: QLineEdit e o display

🎀 o codigo está alocado na introdução ☝️

❎ 351 - Calculadora: criando um QLabel para mostrar informações

🎀 o codigo está alocado na introdução ☝️

❎ 352 - Calculadora: configurando o PyQt Dark Theme (qdarktheme) no PySide6

🎀 o codigo está alocado na introdução ☝️

❎ 353 - Calculadora: criando um botão com QPushButton no PySide6

🎀 o codigo está alocado na introdução ☝️

❎ 354 - Calculadora: grid de botões com QGridLayout no PySide6(parte 1)

🎀 o codigo está alocado na introdução ☝️

❎ 355 - Calculadora: preechendo a grid de botões

🎀 o codigo está alocado na introdução ☝️

❎ 356 - Calculadora: criando um Slod com dados para o Sigonal "Clicked" de cada botão

🎀 o codigo está alocado na introdução ☝️

❎ 357 - Calculadora: permitindo apenas que números válidos no display ao pressionar botões

🎀 o codigo está alocado na introdução ☝️

❎ 358 - Calculadora: Info (QLoabel), TYPE_CHECKING, getter e setter e Cicular Import

🎀 o codigo está alocado na introdução ☝️

❎ 359 - Calculadora: iniciando a configuração dos botões especiais

🎀 o codigo está alocado na introdução ☝️

❎ 360 - Calculadora: botões especiais de operação, clear e equation

🎀 o codigo está alocado na introdução ☝️

❎ 361 - Calculadora: botões especiais de operação, clear e equation

🎀 o codigo está alocado na introdução ☝️

❎ 362 - Calculadora: configurando a potenciação com math.pow

🎀 o codigo está alocado na introdução ☝️

❎ 363 - Calculadora: configurando o "bakspace" display no botão "◀️" (D)

🎀 o codigo está alocado na introdução ☝️

❎ 364 - Calculadora: diálogos com o usuário com QMessageBox

🎀 o codigo está alocado na introdução ☝️

❎ 365 - Calculadora: modificando os botões e obtendo o resultado de QMessageBox

🎀 o codigo está alocado na introdução ☝️

❎ 366 - Calculadora: KeyPressEvent do QLineEdit e criando um Signal

🎀 o codigo está alocado na introdução ☝️

❎ 367 - Calculadora: capturando tecla ENTER, Backspace e ESC

🎀 o codigo está alocado na introdução ☝️

❎ 368 - Calculadora: adicionando ações nas teclas C, D e sinal de igual

🎀 o codigo está alocado na introdução ☝️

❎ 369 - Calculadora: emitindo os números digitados no display + dica sobre args e Kwargs

🎀 o codigo está alocado na introdução ☝️

❎ 370 - Calculadora: emitindo os operadores e potenciação

🎀 o codigo está alocado na introdução ☝️

❎ 371 - Calculadora: os Signals de teclas digitadas aos Slots corretos

🎀 o codigo está alocado na introdução ☝️

❎ 372 - Calculadora: e os números negativos? Solução técnica!

🎀 o codigo está alocado na introdução ☝️

❎ 373 - Calculadora: corrigindo bugs introduzindos na aula anteior

🎀 o codigo está alocado na introdução ☝️

❎ 374 - Aula extra (opcional): empacotando a calculadora com Pyinstaller(Geral)

🎀 o codigo está alocado na introdução ☝️

👉 Para a grande maioria dos programas, isso pode ser feito com um comando curto

  pyinstaller <main.py>

❎ 375 - Aula extra (opcional): empacotando a calculadora com Pyinstaller(Windows)

🎀 o codigo está alocado na introdução ☝️

❎ 376 - Aula extra (opcional): aqui está a calculadora já empacotada para Mac e Windows

calculadora

❎ 377 - Execução ou instalação do Qt Designer

Qt Designer

❎ 378 - Criandoe compilando um arquivo UI com o Qt Designer

🎀 Depois de criar a janela no Qt Designer salve e digite o comando do terminal abaixo para gerar o código em python

  pyside6-uic <nome do arquivo salvo pelo qt_designer.ui> -o <endereço de destino >

Qt Designer

❎ 379 - Usando um arquivo UI do Qt Designer com seu código Python

🎀 o codigo está alocado no link anterior ☝️

❎ 380 - Usando eventFiler e installEventFilter com UI do Qt Designer

🎀 o codigo está alocado no link anterior ☝️

❎ 381 - QObject e QThread: criando a janela inicial com UI do Qt Designer

Qt Designer

❎ 382 - QObject e QThread: o problema!

🎀 o codigo está alocado no link anterior ☝️

❎ 383 - QObject e QThread: criando o "Worker"

🎀 o codigo está alocado no link anterior ☝️

❎ 384 - QObject e QThread: movendo "Workers" para threads separadas

🎀 o codigo está alocado no link anterior ☝️

❎ 385 - QObject e QThread: código comentando


🔜 ❎ Seção 8: Base de dados com Python -SQLite(sqlie3) e MySQL(pymysql)⚓︎


❎ 386 - Base de dados com Python - SQLite(sqlite3) e MySQL(pymysql)

🎀 Fazendo a introdução do modulo 8 para começarmos a trabalhar com recursos de SQLite(sqlite3) e MySQL(pymysql) no python

❎ 387 - Criando meu primeiro arquivo do SQLite(sqlite3)

SQLite

import sqlite3
from pathlid import Path

ROOT_DIR = Path(__file__).parent
DB_NAME = 'db.sqlite3'
DB_FILE = ROOT_DIR / BD_NAME

connection = sqlite3.connect(DB_FILE)
cursor = connection.cursor()

  #SQL

cursor.close()
connection.close()

❎ 388 - Criando minha primeira tabela no SQLite3 (DBeaver)

🪟🪟🪟 costomers

⬇️⬇️

id INTEGER
NAME TEXT
WEOGHT REAL
# [...]
TABLE_NAME = 'costomers'


connection = sqlite3.connect(DB_FILE)
cursor = connection.cursor()

cursor.execute(
  f'CREATE TABLE IF NOT EXIESTS {TABLE_NAME}'
  '('
    'id INTEGER PRIMARY KEY AUTOINCREMENT,'
    'name TEXT,'
    'weight REAL '
  ')'
)
connection.commit()

  #SQL

cursor.close()
connection.close()

❎ 389 - Inserindo valores (INSET INTO), DELETE sem WHERE e zerando a sqlite_sequence

# [...]
# 🛑 fazendo delete sem where
# 🛑 SQL injection

cursor.execute('
  f' DELETE FROM {TABLE_NAME}'
  'DELETE FROM sqllite_squence WEHERE name= "{TABLE_NAME}"'
  )
connection.commit()

#  cria a tabela
cursor.execute(
  f'CREATE TABLE IF NOT EXIESTS {TABLE_NAME}'
  '('
    'id INTEGER PRIMARY KEY AUTOINCREMENT,'
    'name TEXT,'
    'weight REAL '
  ')'
)
connection.commit()

  #Registrar valores nas colunas das tabela



cursor.execute(
  f'INSERT INTO {TABLE_NAME } (id, name, weight)'
  'VALUES (NULL,"Luiz Otaávio", 9.9 )'
  )
connection.commit()

cursor.close()
connection.close()

❎ 390 - Usando placeholders para maior segurança (bindings) no SQLite

# [...]

sql=(
  f'INSERT INTO {TABLE_NAME } ( name, weight)'
  'VALUES (?, ?)'
)
cursor.execute(sql,['Joana', 4 ])
connection.commit()

cursor.close()
connection.close()

❎ 391 - excutemany - Inserindo vários valores com placeholders no SQLite

# [...]

sql=(
  f'INSERT INTO {TABLE_NAME } ( name, weight)'
  'VALUES (?, ?)'
)
# cursor.execute (sql,['Joana', 4 ])
cursor.executemany (sql,
    [
      ['Joana', 4 ],
      ['Luiz', 5 ],
    ]
  )
connection.commit()

cursor.close()
connection.close()

❎ 392 - execute e excutemany com dicionários e lista de dicionários no SQLite

# [...]

sql=(
  f'INSERT INTO {TABLE_NAME } ( name, weight)'
  'VALUES (:nome, :weight)'
)
# cursor.execute (sql,['Joana', 4 ])
cursor.executemany (sql,
    { 'name':'Joana','weight':5}
    { 'name':'sem nome','weight':8}
  )
connection.commit()

cursor.close()
connection.close()

❎ 393 - SELECT DO SQL com fetch no SQLite3 do Python

  import sqlite3
  from main import DB_FILE, TABLE_NAME

  connection = sqlite3.connect(BD_FILE)
  cursor = connection.cursor()

  cursor.excute(
    f'SELECT * FROM {TABLE_NAME}'
  )
  connection.commit()
  # row = cursor.fetchone
  for row in corsur.fechall():
    _id, name, weight = row
    print(_id, name, weight)


  cursor.close()
  connection.close()

❎ 394 - O que é CRUD + DELETE com e sem WHERE no SQLite3 do Python

# [...]
cursor.execute('
  f' DELETE FROM {TABLE_NAME}'
  'DELETE FROM sqllite_squence WEHERE name= "{TABLE_NAME}"'
  )

❎ 395 - DELETE no SQLite do Python

# [...]

  cursor.excute(
    f'DELETE FROM {TABLE_NAME} WHERE id = "3"'
  )

  connection.commit()

❎ 396 - UPDATE no SQLite do Python

# [...]

  cursor.excute(
    f'UPDATE  {TABLE_NAME} '
    'SET name="QUALQUER" , weihght=67.87 '
    'WHERE id = 2'
  )

  connection.commit()

❎ 397 - Vamos falar sobre Docker, Containers, MySQL, WSL2 em Windows, macOS e Linux

Docker

❎ 398 - (Não requecendo) Instale o servidor MySQL direto no Windows

Mysql

❎ 399 - Subindo um servidor MySQL com Docker e docker-composer

docker + Mysql

siga as confugurações abaixo para criar a instância do mysql no docker.

  docker-compose up

❎ 400 - Criando nossa base de dados padrão para próximas aulas(base_de_dados)

detalhando as configurações feitas na aula anterior

❎ 401 - Criando um .env para evitar enviar senhas e dados sensíveis para o Github

criar em arquivo de exemplo das configuração de abaixo em um .env-example

MYSQL_ROOT_PASSWORD= 'CHANGE-ME'
MYSQL_DATABASE= 'CHANGE-ME'
MYSQL_USER= 'CHANGE-ME'
MYSQL_PASSWORD= 'CHANGE-ME'
MYSQL_HOST='CHANGE-ME'

❎ 402 - PyMySQL - um cliente MySQL em Python Puro

instalação cliente PyMysql e a descrição da sua documentação

    pip install pymsql

pymysql

❎ 403 - Conectando no seu servidor de base de dados Mysql com PyMySQL

  import pymysql

  connection = pymysql.connect(
    host='localhost',
    user='usuario',
    password='senha',
    database='base_de_dados'
  )

  with connection:
    with connection.cursor() as cursor:

Dicas:

Os gerenciadores de contexto permitem alocar e liberar recursos precisamente quado você deseja. O exemplo mais aplamente utilizado de gerenciamento de contexto é a with declaração. Suponha que você tenha duas operações relacionadas que gostaria de executar em pares, com um blocode código entre elas. Os gerencimento de contexto permitem que você faça isso especicamente.

❎ 404 - Usando python-dotenv e .env com pymysql.connect

  import pymysql
  import dotenv
  import os

  dotenv.load_dotenv()

  connection = pymysql.connect(
    host= os.environ['MYSQL_HOST'],
    user= os.environ['MYSQL_USER'],
    password= os.environ['MYSQL_PASSWORD'],
    database= os.environ['MYSQL_DATABASE']
  )

  with connection:
    with connection.cursor() as cursor:

❎ 405 - CREATE TABLE para criar tabela com PRIMARY KEY no PyMysql

| customers | | --------- | ------- | | nome | varchar | | idade | int |

# [...]
TABLE_NAME='customers'

 with connection:
    with connection.cursor() as cursor:
      curor.execute(
        f'CREATE TABLE IF NOT EXISTE {TABLE_NAME} ('
        'id INT NOT NULL AUTO_INCREMENT,'
        'nome VARCHAR(50) NOT NULL,'
        'idade INT NOT NULL'
        'PRIMARY KEY (id)'
        ')'
      )
      connection.commit()

❎ 406 - TRUNCATE e INSERT p/ limpar e criar valores na tabela com um ou mais cursores

nome idade
Luiz 25
# [...]

 with connection:
    with connection.cursor() as cursor:
       # Limpa a tabela, mas não apaga seus dados!
      curor.execute(f' TRUNCATE TABLE {TABLE_NAME}')
      curor.execute(
        f'INSET INTO {TABLE_NAME} (nome, idade) VALUES '
        '("Luiz", 25 ) '
      )
      connection.commit()

❎ 407 - Evite SQL Injection ao usar placeholders para enviar valores para consulta SQL

# [...]

 with connection:
    with connection.cursor() as cursor:
      sql = f'INSET INTO {TABLE_NAME} (nome, idade) '
        ' VALUES (%s, %s) '
      data = ('luiz', 18)

      curor.execute(sql,data)
      connection.commit()

❎ 408 - Inserindo valores usando dicionários ao invés de iteráveis

# [...]

 with connection:
    with connection.cursor() as cursor:
       sql = f'INSET INTO {TABLE_NAME} (nome, idade) '
        ' VALUES (%(name)s, %(age)s) '
      data = {
        "name": "Le",
        "age": 37,
      }

      curor.execute(sql,data)
      connection.commit()

❎ 409 - Inserindo vários valores com uma consulta só usando interáveis ou dicionários

# [...]
  # insersão de valores com dicionario
 with connection:
    with connection.cursor() as cursor:
       sql = f'INSET INTO {TABLE_NAME} (nome, idade) '
        ' VALUES (%(name)s, %(age)s) '
      data = (
        {"name": "Le","age": 37,},
        {"name": "Julia","age": 74,},
        {"name": "Rose","age": 53,},
        )

      curor.executemany (sql,data)
      connection.commit()
  # insersão de valores com iterável 
 with connection:
    with connection.cursor() as cursor:
       sql = f'INSET INTO {TABLE_NAME} (nome, idade) '
        ' VALUES (%s, %s) '
      data = (
          ( "Siri", 22,),
          ( "Helena", 15,),
        )

      curor.executemany (sql,data)
      connection.commit()

❎ 410 - Lendo valores com SELECT, cursor.execute e cursor.fetchall no PyMySQL

# [...]
 with connection:
    with connection.cursor() as cursor:
       sql = f'SELECT * FROM {TABLE_NAME}'

      curor.execute (sql)

      # dateh = cursor.fetone()
      dateh = cursor.fetchall()

      for row in dateh:
        print(row)

❎ 411 - Lendo valores com WHERE (mais uma vez explico cuidados com SQL Injection)

# [...]
 with connection:
    with connection.cursor() as cursor:
      id_menor = int(input('Digite o menor id:'))
      id_maior = int (input('Digite o maior id:'))

       sql = f'SELECT * FROM {TABLE_NAME} WHERE id BETWEEN %s AND  %s'

      curor.execute (sql, (id_menor, id_maior))

      print(curor.mogrify(sql, (id_menor, id_maior)))

      # dateh = cursor.fetone()
      dateh = cursor.fetchall()

      for row in dateh:
        print(row)

Cuidados com SQL injection

A injeção de SQL é uma vulnerabilidade de sgurança da web que permite que um invasor interfira nas consultas que um aplicativo faz ao seu banco de dados. Geramente. permite que um invasor visualize dados normalmente ele não é capaz recuperar. isso pode incluir pertencentes a outros usuários ou quaisquer outros dados que o próprio aplicativo é capaz de acessar. Em muitos casos, um invasor pode modificar ou excluir esses dados causando alterações peristentes no conteúdo ou comportamento do aplicativo.

❎ 412 - Apagando valores com DELETE, WHERE e placeholder no PyMySQL

# [...]
 with connection:
    with connection.cursor() as cursor:

       sql = f'DELETE FROM {TABLE_NAME} WHERE id = %s'

      curor.execute (sql,(4))
      connection.commit()
      curor.execute (f'SELECT * FROM {TABLE_NAME} ')

      for row in  cursor.fetchall():
        print(row)

❎ 413 - Editando com UPDATE, WHERE e placeholder no PyMySQL

# [...]
 with connection:
    with connection.cursor() as cursor:

       sql = f'UPDATE FROM {TABLE_NAME} SET nome=%s, idade=%s  WHERE id = %s'

      curor.execute (sql,('Eleonor', 102, 4))
      connection.commit()
      curor.execute (f'SELECT * FROM {TABLE_NAME} ')

      for row in  cursor.fetchall():
        print(row)

❎ 414 - Trocando o cursor para retornar dicionários pymysql.cusor.DictCursor

# [...]
  import pymysql
  import pymysql.curors
  import dotenv
  import os

  dotenv.load_dotenv()

  connection = pymysql.connect(
    host= os.environ['MYSQL_HOST'],
    user= os.environ['MYSQL_USER'],
    password= os.environ['MYSQL_PASSWORD'],
    database= os.environ['MYSQL_DATABASE'],
    cursorclass=pymysql.cursors.DictCursor,
  )
 with connection:
    with connection.cursor() as cursor:

       sql = f'UPDATE FROM {TABLE_NAME} SET nome=%s, idade=%s  WHERE id = %s'

      curor.execute (sql,('Eleonor', 102, 4))
      connection.commit()
      curor.execute (f'SELECT * FROM {TABLE_NAME} ')

      for row in  cursor.fetchall():
        print(row)

❎ 415 - SSCurosr, SSDisctCursor e scroll para conjuntos de dados muito grandes no PyMysql

  import pymysql
  import pymysql.curors
  import dotenv
  import os

  dotenv.load_dotenv()

  connection = pymysql.connect(
    host= os.environ['MYSQL_HOST'],
    user= os.environ['MYSQL_USER'],
    password= os.environ['MYSQL_PASSWORD'],
    database= os.environ['MYSQL_DATABASE'],
    cursorclass=pymysql.cursors.DictCursor,
    # cursorclass=pymysql.cursors.SSDictCursor,
  )
 with connection:
    with connection.cursor() as cursor:

       sql = f'UPDATE FROM {TABLE_NAME} SET nome=%s, idade=%s  WHERE id = %s'

      curor.execute (sql,('Eleonor', 102, 4))
      connection.commit()
      curor.execute (f'SELECT * FROM {TABLE_NAME} ')


      # for row in  cursor.fetchall_unbuffered():
      for row in  cursor.fetchall():
        print(row)


      # cursor.scroll(-2)

      cursor.scroll(1, 'absolute')

      for row in  cursor.fetchall(): 
        print(row)

Dicas:

Cursor sem buffer(SSCurosr), útil principalmente para consultas que retornam muitos dados, ou para conexões com servidores remotos em uma rede lenta. Em vez de copiar cada linha de dados em um buffer, isso irá buscar linhas conforme necessário. A vantagem disso é que o cliente usa muito menos memória, e as linhas são retornadas muito mais rapidamente ao viajar em uma rede lenta ou se o conjunto de resultados for muito grande. Existem limitações, no entanto. O protocolo MySQL não suporta retornando o número total de linhas, então a única maneira de saber quantas linhas existe é iterar sobre cada linha retornada. Além disso, atualmente não é possível rolar para trás, pois apenas a linha atual é mantida na memória.

❎ 416 - rowcount, rownumber e lastrowid para detalhes de consultas executadas

  import pymysql
  import pymysql.curors
  import dotenv
  import os

  dotenv.load_dotenv()

  connection = pymysql.connect(
    host= os.environ['MYSQL_HOST'],
    user= os.environ['MYSQL_USER'],
    password= os.environ['MYSQL_PASSWORD'],
    database= os.environ['MYSQL_DATABASE'],
    cursorclass=pymysql.cursors.DictCursor,

  )
 with connection:
    with connection.cursor() as cursor:

      curor.execute (f'SELECT * FROM {TABLE_NAME} ')

      # curor.execute (f'SELECT id FROM {TABLE_NAME} ORDER BY id DESC LIMIT 1 ')

      data = cursor.fetchall()
      for row in  data:
        print(row)
        print(len(data))
        print(cursor.rowcount)
        # print(cursor.lastrowid)
        # print(cursor.rownumber)

🔜 🔲 Seção 9 Django no Python - Básico⚓︎


❎ 417 - Resumo de projetos anteriores

Aula anterior

❎ 418 - Iniciando um projeto com Django com django-admin startproject

Para fazer ativação do django criando um prjeto siga o comando a seguir

  django-admin startproject <project>.

  python manage.py runserver  # iniciar o servidor 

❎ 419 - Projeto movido para a pasta de repositório do curso

django

❎ 420 - O que é o Django? (de modo muito superficial)

django

❎ 421 - Conheça os arquivos do Django, django-admin e manage.py

O django-admin.py é utilitário de linha de comando do Django para tarefas admininstrativas. Este documento descreve tudo que fazer. Além disso, manage.py é criado automaticamente em cada projeto Django. O manage,py é um wrapper simples em volta de django-admin.py que se procura com duas coisas por você antes de deletar tarefas ao django-admin.py:

➡️ Ele coloca o pacote de seu projeto no sys.path. ➡️ Ele define a variável de ambiente ´DJANGO_SETTINGS_MOULE´ que então aponta para o arquivo settings.py do seu projeto.

❎ 422 - Primeira URL e function based view + HttpRequest e HttpResponse + Status Code

❎ 423 - Entenda os motivos de uma View recever uma Request e retornar uma Response

❎ 424 - Criando apps com manage.py startapp do Django

❎ 425 - Movendo as function based views para o arquivo views.py dos novs apps do Django

❎ 426 - Aninhando URLs com path, include e urls.py dos apps do Django

❎ 427 - Renderizando HTML, render, templates INSTALLED_APPS e TamplateDoesNotExist

❎ 428 - Configurando templates globais com DIRS + extends para herança de templantes

❎ 429 - Arquivos parciais e include para separar trechos dos templates (partials)

❎ 430 - Arquivos estáticos (staticfiles) STATIC_URL, STATICFILES_DIRS e load static

❎ 431 - Usando o context para enviar dados para dentro dos templates do Django


🔜 🔲 Seção 10 Django no Python Projeto Agenda⚓︎


🔲 345 - Projeto Agenda - Arquivos

🔲 346 - Projeto Agenda - Criando o projeto

🔲 347 - Projeto Agenda - Criando os Models

🔲 348 - Projeto Agenda - Admin

🔲 349 - Projeto Agenda - Exibindo valores nas views

🔲 350 - Projeto Agenda - Exibindo um único contato

🔲 351 - Levantando erros 404

🔲 352 - Usando condicionais

🔲 353 - Paginação

🔲 354 - Ordenando e filtrando valores

🔲 355 - Campo de pesquisa

🔲 356 - Instalando o pillow

🔲 357 - Campo de imagem

🔲 358 - Mensagens com Django Messages

🔲 359 - Backup do projeto

🔲 360 - Admin em Português do Brasil

🔲 361 - Sistema de login - Preparando tudo

🔲 362 - Cadastro de usuários

🔲 363 - Login, Logout e Dashboard

🔲 364 - Verificando usuários logados

🔲 365 - Formulário para Models


🔜 🔲 Seção 11 Django com Python - Primeiro Deploy (Linux)⚓︎


🔲 366 - Deploy - Criando um servidor no GCP

🔲 367 - Assista essa aula apenas se NÂO conseguindo usar o Google Cloud Platform

🔲 368 - Deploy - Preparando o ambiente

🔲 369 - Deploy - Linux Movendo os arquivos para o servidor

🔲 370 - Deploy - Windows Movendo os arquivos para o servidor

🔲 371 - Deploy - Nginx & Gunicorm

🔲 372 - Deploy - HTTPS e Segurança

🔲 373 - Deploy - Migrando para MySQL / MariaDB

🔲 374 - Git e local_settings.py - Editando localmente

🔲 375 - Customizando a área Admin


🔜 🔲 Seção 12 Django com Python - Projeto Blog⚓︎


🔲 376 - Projeto Blog - Parte 1

🔲 377 - Projeto Blog - Parte 2

🔲 378 - Atualização Django e Summernote

🔲 379 - Projeto Blog - Parte 3

🔲 380 - Projeto Blog - Parte 4

🔲 381 - Projeto Blog - Parte 5

🔲 382 - Projeto Blog - Parte 6

🔲 383 - Projeto Blog - Deploy

🔲 384 - Criando seus próprios filtros

🔲 385 - Select_related - Otimizando as consultas relacionais

🔲 386 - Adicionando campos do formulários manualmente

🔲 387 -

🔲 388 -