# ET Cetera

## Set

Um conjunto (set) é uma estrutura de dados que armazena elementos <mark>únicos e não ordenados</mark>. Ele é representado por chaves🔑 `{ }` ou pela palavra `set ( )` e não permite duplicatas, ou seja, cada elemento em um conjunto é único, bora ver um exemplo:

```python
estudantes = [
    {"nome":"Harry","casa":"grifinória"},
    {"nome":"Hermione", "casa":"grifinória"},
    {"nome":"Draco", "casa":"sonserina"}
] #uma lista com dicionarios

def main():
    casas = set()  #set vazio
    for estudante in estudantes:
        casas.add(estudante["casa"])  #adiciona a casa para o set
    print(type(casas))  #mostra o tipo
    print(casas)

if __name__ == "__main__":
    main()
```

```plaintext
<class 'set'>
{'grifinória', 'sonserina'}
```

<div data-node-type="callout">
<div data-node-type="callout-emoji">💡</div>
<div data-node-type="callout-text">Conjuntos são úteis quando precisamos lidar com coleções de elementos e não nos importamos com a ordem em que estão armazenados.</div>
</div>

## Global

A palavra `global` é utilizada para indicar que uma variável está sendo referenciada no escopo global, ou seja, fora de uma função, por padrão é considerada local a essa função. No entanto, se você precisa <mark>modificar</mark> uma variável global de *dentro de uma função*, vc deve usar a palavra-chave `global` para informar ao interpretador que a variável pertence ao escopo global, por exemplo:

```python
saldo = 0

def main():
    print("Saldo:", saldo)
    deposito(100)
    saque(50)
    print("Saldo:", saldo)

def deposito(n):
    global saldo #sem 'global' não modifica o saldo
    saldo += n

def saque(n):
    global saldo
    saldo -= n

if __name__=="__main__":
    main()
```

## Constantes

As constantes em python deveriam de ser imutáveis, mas elas são mutáveis, por que? não sei. Constantes em outras línguas de programação são imutáveis. Para representar uma constante (constant) escreve-se a variável toda em maiúscula, e ela serve como indicador para programadores, veja os exemplos 👇🏻:

```python
MIADO = 3
MIADO = 4
for _ in range(MIADO):
    print("Miau")
#quantos miados mostrara?
```

## Type Hints

👉🏻 [Type Hints](https://docs.python.org/pt-br/3/library/typing.html) ou "tipos de dicas" são anotações opcionais que você pode adicionar ao código para indicar o tipo de dados que uma variável espera ou que uma função retorna. Essas anotações não afetam o comportamento do programa em tempo de execução, mas fornecem informações úteis para desenvolvedores, documentar, ⚙️ ferramentas de análise estática e IDEs. A continuação utilizarei uma ferramenta de análise estática, chamada "`mypy`", para instalar é só:

`pip install mypy`

```python
#nome do arquivo: meuarquivo.py

def miado(n: int):  # :int (dica)
    for _ in range(n):
        print("miau")
```

Nesse exemplo básico ☝🏻, observa-se a dica `:int` que indica que recebe como parâmetro um <mark>número inteiro</mark> e para usar a ferramenta mypy, desde o terminal usa-se : `mypy meuarquivo.py`

Outros tipos de dicas 👇🏻:

```python
#nome do arquivo: meuarquivo.py

def miado(n: int) -> None:  # :int (dica)  -> None (dica)
    for _ in range(n):
        print("miau")
```

Neste código ☝🏻, `-> None` indica que retorna None (nada), porque a função esta fazendo um print e não retornando um valor.

```python
def miado(n: int) -> str:
    return "miau\n" * n
```

Você está certo, `-> str` indica que retorna uma string (palavra), desta vez a função está retornando um valor e não fazendo print.

### Docstrings

São string de 👉🏻 [documentação](https://peps.python.org/pep-0257/) incorporadas diretamente no código-fonte para descrever o propósito, funcionamento e uso de módulos, classes, funções ou métodos, bora ver alguns exemplos 👇🏻:

```python
def miado(n: int) -> str:
    """ Mia n vezes """  #docstring
    return "miau\n" * n
```

```python
def miado(n: int) -> str:
    """
    Mia n vezes
    :param n: Numero de vezes que mia
    :type n: int
    :raise TypeError: Se n não é um int
    :return : Uma string de n miados, um por linha
    :rtype : str
    """
    return "miau\n" * n
```

Essa template de documentação aqui não 🙅🏻‍♂️ é obrigatória, mas é muito usada por desenvolvedores ✅, é bom pegar essas dicas e fazer um hábito delas no seu código, pega visão! 🚀

## Argparse

É um 👉🏻[módulo](https://docs.python.org/pt-br/3/library/argparse.html) padrão que <mark>facilita o processamento de argumentos</mark> da linha de comando, ou seja, ao usar o terminal. Ele fornece uma maneira flexível e poderosa de analisar argumentos passados quando um script é executado. Com o `argparse` é possível definir argumentos, opções e até mesmo argumento posicionais, associando valores a esses elementos. Ele gera mensagens de ajuda, tornando a utilização do script mais intuitiva para os usuários. bora ver uns exemplos👇🏻:

```python
#nome do arquivo: meuarquivo.py
import sys

if len(sys.argv) == 1:
    print("E ai, blz?")
elif len(sys.argv) == 3 and sys.argv[1] == "-n":
    n = int(sys.argv[2])
    for _ in range(n):
        print("E ai, blz?")
else:
    print("Uso: arquivo.py")
```

Nesse exemplo não tem o uso do argparse ainda, mas normalmente o uso no terminal seria assim: `python3 meuarquivo.py -n 3` o número pode ser qualquer número inteiro, que seria o número de vezes que mostrar a frase "E ai, blz?"

<div data-node-type="callout">
<div data-node-type="callout-emoji">💡</div>
<div data-node-type="callout-text">Dica!! O uso de um traço (<code> - </code>) está associado à números, e o uso de dois traços (<code> -- </code>) está associado à palavras.</div>
</div>

Bora usar o argparse e repare que ele vai facilitar a vida 🙌:

```python
import argparse
 
parser = argparse.ArgumentParser(description="Comprimento Br")
parser.add_argument("-n", default=1,help="Número de vezes que comprimenta", type=int)
args = parser.parse_args() 

for _ in range(int(args.n)):
    print("E ai, blz?")

#arg.n contem um inteiro que é digitado depois do -n
```

Na documentação pode encontrar mais utilidades sobre argparse, mas nesse código, inicia o argparse com `parser` a opção **description** é opcional, mas serve para dar um título, depois no `parser.add_argument` é adicionado a funcionalidade utilizando a letra que quiser neste caso foi (<mark>-n</mark>), deixando `default=1` como *padrão* caso não indique com o -n, em `args` é analisado o parser e tem o método **<mark>.n</mark>** para devolver um inteiro,o argparse vem com funções por padrão, como em qualquer outro módulo que usa argparse, mostrar um exemplo ao digitar errado ou pedir ajuda como comando <mark>--help</mark>:

```bash
usuario@terminal:~$ python3 meuarquivo.py --help
usage: teste.py [-h] [-n N]

Comprimento Br

options:
  -h, --help  show this help message and exit
  -n N        Número de vezes que comprimenta
```

## Descompactar

Unpacking ou desempacotar 📦elementos de uma sequência (como uma lista, tupla ou dicionário) e atribuí-los a variáveis individuais de forma simultânea. Isso é feito usando a sintaxe de desempacotamento, sendo para tuplas as mais simples, bora ver exemplos 👇🏻:

```python
# desempacotar tupla
first, _ = input("Qual é o seu nome:").split(" ")
print(f"Olá, {first}")
```

Este código ☝🏻irá pegar só o primeiro nome, por exemplo: João Careca 🧔🏻‍♂️

```python
# desempacotar lista
def total(galeao, sicle, nuque):
    return (galeao * 17 + sicle) * 29 + nuque

def main():
    moedas = [100, 25, 12]
    print(total(*moedas), "nuques")  #desempacotar

if __name__ == "__main__":
    main()
```

```python
# desempacotar dicionario
def total(galeao, sicle, nuque):
    return (galeao * 17 + sicle) * 29 + nuque

def main():
    moedas = {"galeao":100, "sicle":25, "nuque":72}
    print(total(**moedas), "nuques") #desempacotar

if __name__ == "__main__":
    main()
```

Perceba que para desempacotar uma lista basta usar um asterisco ( `*` ) e para desempacotar um dicionário basta usar dois asterisco ( `**` )

<div data-node-type="callout">
<div data-node-type="callout-emoji">💡</div>
<div data-node-type="callout-text">Para desempacotar ou descompactar, o número de itens numa lista ou dicionário devem de ser iguais ao parâmetros que a função pede.</div>
</div>

## Args / Kwargs

É o contrário de unpacking ou desempacotamento, não precisar ser esse nomes , `*args` ou `**kwargs` simplesmente foi estabelecido como acordo, \*args significa que os parâmetros vão ser colocados ou compactados numa <mark>lista</mark>, e \*\*kwargs num <mark>dicionário</mark>, veja os exemplos 👇🏻:

```python
def por_lista(*args):
    print("Posição:", args)

por_lista(10,2,12,45,5,55,21,3,8)
```

`> Posição: (10, 2, 12, 45, 5, 55, 21, 3, 8)`

```python
def por_dic(**kwargs):
    print("Posição: ", kwargs)

por_dic(dolar=100, ien=1500,real=5 , euro=15)
```

`> Posição: {'dolar': 100, 'ien': 1500, 'real': 5, 'euro': 15}`

## Map

É uma 👉🏻[função integrada](https://docs.python.org/pt-br/3/library/functions.html#map) que permite aplicar uma função a cada elemento de um iterável (como uma lista) e retorna um iterável com os resultados. A sintaxe básica é `map(função, iterável)` onde função é a função que você deseja aplicar e iterável é a sequência de dados a ser processada, bora ver um exemplo 👇🏻:

```python
def main():
    yell(["Isto", "é","python","do", "zero","ao", "arrojado"])  #lista

def yell(palavras):
    maiuscula = []
    for palavra in palavras:
        maiuscula.append(palavra.upper())
    print(*maiuscula)

if __name__=="__main__":
    main()
```

Neste código ☝🏻convertemos cada palavra da lista em maiúscula criando uma função, agora veja o uso do map no mesmo contexto 👇🏻:

```python
def main():
    yell("Isto", "é","python","do", "zero","ao", "arrojado") 

def yell(*palavras):
    maiusculas = map(str.upper, palavras)
    print(*maiusculas)

if __name__=="__main__":
    main()
```

## Compreensão da lista

"List comprehension" é uma construção sintática que permite criar listar de maneira concisa e expressiva. Em outras palavras, uma forma mais compacta de escrever código que muito chamam de 💪🏻"Pythonista" ( o jeitinho python ), a continuação uma estrutura básica de construção 🚧 :

```python
nova_lista = [expressao for elemento in iteravel]
```

```python
def main():
    yell("Isto", "é","python","do", "zero","ao", "arrojado") 

def yell(*palavras):
    maiusculas = []
    for palavra in palavras:
        maiusculas.append(palavra.upper())
    print(*maiusculas)

if __name__=="__main__":
    main()
```

Neste exemplo , pode-se aplicar a compreensão da lista da seguinte forma 👇🏻:

```python
def yell(*palavras):
    maiusculas = [palavra.upper() for palavra in palavras]
    print(*maiusculas)
```

Veja um outro exemplo utilizando validação dentro👇🏻:

```python
estudantes = [
    {"nome":"harry", "casa":"grifinória"},
    {"nome":"draco", "casa":"sonserina"},
    {"nome": "ron", "casa":"grifinória"}
]

grifinoria = [
    estudante["nome"] for estudante in estudantes if estudante["casa"] == "grifinória"
]
```

## Compreensão do Dicionário

"Dictionary comprehension" também é uma construção 🦺 sintática que permite criar um dicionário de maneira concisa e eficiente. Similar à list comprehension, a continuação uma estrutura básica de construção 🏗️ :

```python
novo_dicionario = {chave: valor for elemento in iteravel}
```

Primeiro um exemplo do código sem a compreensão do dicionário e depois com:

```python
# sem o dictionary comprehension
estudantes = ["hermione","harry", "ron"]

grifinoria = []
for estudante in estudantes:
    grifinoria.append({"nome":estudante, "casa":"grifinória"})
```

```python
# com o dictionary comprehension
estudantes = ["hermione","harry", "ron"]

grifinoria = [{"nome":estudante, "casa":"grifinória"} for estudante in estudantes]
```

## Geradores

👉🏻[Generators](https://docs.python.org/pt-br/3/howto/functional.html#generators) (geradores) são estruturas que permitem a criação de iteradores de forma eficiente e econômica em termos de memória. Eles são definidos por funções que utilizam a palavra-chave yield para produzir valores de maneira preguiçosa 🥱, ou seja, gerando cada valor sob demanda, veja um exemplo sem o gerador e no final com o gerador:

```python
def main():
    n = int(input("Valor para n:"))
    for contar in ovelha(n):
        print(contar)

def ovelha(n):
    rebanho = []
    for i in range(n):
        rebanho.append("🐑" * i)
    return rebanho

if __name__=="__main__":
    main()
```

Nesse exemplo: foi testado com valor de 15, 30 e 1000000 🤯

```python
Valor para n:15

🐑
🐑🐑
🐑🐑🐑
🐑🐑🐑🐑
🐑🐑🐑🐑🐑
🐑🐑🐑🐑🐑🐑
🐑🐑🐑🐑🐑🐑🐑
🐑🐑🐑🐑🐑🐑🐑🐑
🐑🐑🐑🐑🐑🐑🐑🐑🐑
🐑🐑🐑🐑🐑🐑🐑🐑🐑🐑
🐑🐑🐑🐑🐑🐑🐑🐑🐑🐑🐑
🐑🐑🐑🐑🐑🐑🐑🐑🐑🐑🐑🐑
🐑🐑🐑🐑🐑🐑🐑🐑🐑🐑🐑🐑🐑
🐑🐑🐑🐑🐑🐑🐑🐑🐑🐑🐑🐑🐑🐑
```

```python
Valor para n:30

🐑
🐑🐑
🐑🐑🐑
🐑🐑🐑🐑
🐑🐑🐑🐑🐑
🐑🐑🐑🐑🐑🐑
🐑🐑🐑🐑🐑🐑🐑
🐑🐑🐑🐑🐑🐑🐑🐑
🐑🐑🐑🐑🐑🐑🐑🐑🐑
🐑🐑🐑🐑🐑🐑🐑🐑🐑🐑
🐑🐑🐑🐑🐑🐑🐑🐑🐑🐑🐑
🐑🐑🐑🐑🐑🐑🐑🐑🐑🐑🐑🐑
🐑🐑🐑🐑🐑🐑🐑🐑🐑🐑🐑🐑🐑
🐑🐑🐑🐑🐑🐑🐑🐑🐑🐑🐑🐑🐑🐑
🐑🐑🐑🐑🐑🐑🐑🐑🐑🐑🐑🐑🐑🐑🐑
🐑🐑🐑🐑🐑🐑🐑🐑🐑🐑🐑🐑🐑🐑🐑🐑
🐑🐑🐑🐑🐑🐑🐑🐑🐑🐑🐑🐑🐑🐑🐑🐑🐑
🐑🐑🐑🐑🐑🐑🐑🐑🐑🐑🐑🐑🐑🐑🐑🐑🐑🐑
🐑🐑🐑🐑🐑🐑🐑🐑🐑🐑🐑🐑🐑🐑🐑🐑🐑🐑🐑
🐑🐑🐑🐑🐑🐑🐑🐑🐑🐑🐑🐑🐑🐑🐑🐑🐑🐑🐑🐑
🐑🐑🐑🐑🐑🐑🐑🐑🐑🐑🐑🐑🐑🐑🐑🐑🐑🐑🐑🐑🐑
🐑🐑🐑🐑🐑🐑🐑🐑🐑🐑🐑🐑🐑🐑🐑🐑🐑🐑🐑🐑🐑🐑
🐑🐑🐑🐑🐑🐑🐑🐑🐑🐑🐑🐑🐑🐑🐑🐑🐑🐑🐑🐑🐑🐑🐑
🐑🐑🐑🐑🐑🐑🐑🐑🐑🐑🐑🐑🐑🐑🐑🐑🐑🐑🐑🐑🐑🐑🐑🐑
🐑🐑🐑🐑🐑🐑🐑🐑🐑🐑🐑🐑🐑🐑🐑🐑🐑🐑🐑🐑🐑🐑🐑🐑🐑
🐑🐑🐑🐑🐑🐑🐑🐑🐑🐑🐑🐑🐑🐑🐑🐑🐑🐑🐑🐑🐑🐑🐑🐑🐑🐑
🐑🐑🐑🐑🐑🐑🐑🐑🐑🐑🐑🐑🐑🐑🐑🐑🐑🐑🐑🐑🐑🐑🐑🐑🐑🐑🐑
🐑🐑🐑🐑🐑🐑🐑🐑🐑🐑🐑🐑🐑🐑🐑🐑🐑🐑🐑🐑🐑🐑🐑🐑🐑🐑🐑🐑
🐑🐑🐑🐑🐑🐑🐑🐑🐑🐑🐑🐑🐑🐑🐑🐑🐑🐑🐑🐑🐑🐑🐑🐑🐑🐑🐑🐑🐑
```

```plaintext
O valor de 1 milhão 😱 simplesmente parou o computador ! 
Não há capacidade para fazer tudo isso.
```

Agora o mesmo exemplo com o gerador 👇🏻:

```python
def main():
    n = int(input("Valor para n:"))
    for contar in ovelha(n):
        print(contar)

def ovelha(n):
    for i in range(n):
        yield "🐑" * i  #uso do gerador

if __name__=="__main__":
    main()
```

Dessa vez ele da conta de fazer para 1 milhão, não acredita??? faz no seu computador 🫣. Por que acontece essa sobrecarga?, porque 🙄com o gerador, mostra o resultado por partes , segundo ele vai gerando a tarefa. Sem o gerador, ele mostra tudo ao mesmo tempo.
