Ir para o conteúdo

Atualizações e Recursos Recentes do Python

Introdução

A linguagem Python continua evoluindo, com novas versões trazendo recursos, melhorias de desempenho e correções de segurança. Nesta aula, vamos explorar as principais características e funcionalidades introduzidas nas versões recentes do Python, além de discutir o processo de atualização e migração entre versões.

Objetivos de Aprendizado

  • Conhecer os principais recursos das versões recentes do Python
  • Entender as melhorias de sintaxe e funcionalidades adicionadas
  • Aprender como usar os novos recursos em projetos práticos
  • Compreender o ciclo de lançamento e suporte das versões do Python
  • Descobrir como migrar projetos para versões mais recentes

Python 3.8

Python 3.8 foi lançado em outubro de 2019 e trouxe várias melhorias significativas para a linguagem.

# Antes do Python 3.8
command = input("Digite um comando: ")
while command != "quit":
    # Processar o comando
    print(f"Executando: {command}")
    command = input("Digite um comando: ")

# Com Python 3.8 - Operador Walrus (:=)
while (command := input("Digite um comando: ")) != "quit":
    print(f"Executando: {command}")

# Outro exemplo útil
# Antes
import re
text = "A idade de João é 25 anos"
match = re.search(r'(\d+)', text)
if match:
    age = int(match.group(1))
    print(f"Idade encontrada: {age}")

# Depois
if match := re.search(r'(\d+)', text):
    print(f"Idade encontrada: {int(match.group(1))}")

# Em compreensões de lista
data = [1, 2, 3, 4, 5]
[x for x in data if (doubled := x * 2) > 5]  # [3, 4, 5]
# Python 3.8 introduziu uma sintaxe para especificar
# parâmetros exclusivamente posicionais

# A barra (/) indica que todos os parâmetros à esquerda 
# são exclusivamente posicionais
def calcular_potencia(base, expoente, /, multiplicador=1):
    return (base ** expoente) * multiplicador

# Chamadas válidas
calcular_potencia(2, 3)           # 2³ = 8
calcular_potencia(2, 3, 2)        # 2³ * 2 = 16
calcular_potencia(2, 3, multiplicador=2)  # 2³ * 2 = 16

# Chamadas inválidas
# calcular_potencia(base=2, expoente=3)  # Erro! base e expoente são posicionais

# Combinando com parâmetros somente por nome (*)
def funcao(pos1, pos2, /, pos_ou_kw, *, kw1, kw2):
    # pos1 e pos2: somente por posição
    # pos_ou_kw: por posição ou nome
    # kw1 e kw2: somente por nome
    pass

# Exemplo prático
def buscar_dados(tabela, id, /, *, campos=None, ordenar_por=None):
    # tabela e id são posicionais
    # campos e ordenar_por são somente por nome
    campos = campos or ["*"]
    ordem = f"ORDER BY {ordenar_por}" if ordenar_por else ""
    query = f"SELECT {', '.join(campos)} FROM {tabela} WHERE id = {id} {ordem}"
    return query

# Uso
buscar_dados("usuarios", 123, campos=["nome", "email"])
# SELECT nome, email FROM usuarios WHERE id = 123 
# Python 3.8 melhorou as f-strings para depuração
x = 10
y = 20

# Antes
print(f"x = {x}, y = {y}")

# Python 3.8+
print(f"{x=}, {y=}")  # x=10, y=20

# Funciona com expressões
print(f"{x+y=}")  # x+y=30

# Formato personalizado
import math
print(f"{math.pi=:.2f}")  # math.pi=3.14

# Útil para depuração rápida
nome = "Maria"
idade = 30
print(f"{nome=} tem {idade=} anos")  # nome='Maria' tem idade=30 anos
# importlib.metadata (PEP 566)
# Permite acessar os metadados dos pacotes instalados
from importlib import metadata

# Versão de um pacote instalado
version = metadata.version("pip")
print(f"Versão do pip: {version}")

# Lista de pacotes instalados
installed = metadata.distributions()
print(f"Total de pacotes instalados: {len(list(installed))}")

# TypedDict (PEP 589)
# Dicionários com tipos de valor específicos para cada chave
from typing import TypedDict

class Pessoa(TypedDict):
    nome: str
    idade: int
    ativo: bool

# Cria um dicionário do tipo Pessoa
usuario: Pessoa = {
    "nome": "João", 
    "idade": 25, 
    "ativo": True
}

# Ferramentas de análise estática como mypy podem verificar
# se os tipos estão corretos
# Melhoria no módulo typing
from typing import Literal, Final, Protocol

# Literal para especificar valores exatos
def mover(direcao: Literal["norte", "sul", "leste", "oeste"]) -> None:
    print(f"Movendo para {direcao}")

mover("norte")  # OK
# mover("nordeste")  # Erro de tipo

# Final para declarar constantes
MAX_CONEXOES: Final = 100
# MAX_CONEXOES = 200  # Erro, não pode ser reatribuído

# Protocol para tipagem estrutural (similar a interfaces)
class Printable(Protocol):
    def __str__(self) -> str: ...

def imprimir(obj: Printable) -> None:
    print(str(obj))

# Qualquer objeto que implemente __str__ é compatível com Printable

# Outros recursos:
# - multiprocessing compartilhado agora usa memória compartilhada
# - pickle suporta objetos maiores que 4GiB
# - PYTHONIOENCODING não afeta mais stderr
# - zoneinfo adicionado (PEP 615) para lidar com fusos horários

Python 3.9

Python 3.9 foi lançado em outubro de 2020 e trouxe várias melhorias, incluindo novos operadores para dicionários e funcionalidades para strings.

# Antes do Python 3.9
x = {"a": 1, "b": 2}
y = {"b": 3, "c": 4}

# Criando a união com método update (modifica x)
z1 = x.copy()
z1.update(y)
print(z1)  # {'a': 1, 'b': 3, 'c': 4}

# Criando a união com desempacotamento
z2 = {**x, **y}
print(z2)  # {'a': 1, 'b': 3, 'c': 4}

# Com Python 3.9 - Operador |
# União (novo dicionário)
z3 = x | y
print(z3)  # {'a': 1, 'b': 3, 'c': 4}

# Atualização (modifica x)
x |= y
print(x)  # {'a': 1, 'b': 3, 'c': 4}

# Casos de uso práticos
defaults = {"timeout": 30, "retries": 3, "backoff": 1.5}
user_settings = {"timeout": 10}

# Configuração final é a união de padrões e personalizações
config = defaults | user_settings
print(config)  # {'timeout': 10, 'retries': 3, 'backoff': 1.5}
# Python 3.9 adiciona métodos para remover prefixos e sufixos

# removeprefix() - Remove prefixo
url = "https://codaqui.dev/python/aulas"

# Antes
if url.startswith("https://"):
    url_sem_protocolo = url[8:]  # Remove os primeiros 8 caracteres

# Python 3.9
url_sem_protocolo = url.removeprefix("https://")
print(url_sem_protocolo)  # codaqui.dev/python/aulas

# Se o prefixo não existir, retorna a string original
print("Python".removeprefix("Java"))  # Python

# removesuffix() - Remove sufixo
arquivo = "documento.txt"

# Antes
if arquivo.endswith(".txt"):
    nome_sem_extensao = arquivo[:-4]  # Remove os últimos 4 caracteres

# Python 3.9
nome_sem_extensao = arquivo.removesuffix(".txt")
print(nome_sem_extensao)  # documento

# Exemplos adicionais
caminho = "/home/usuario/arquivo.py"
nome_arquivo = caminho.split("/")[-1].removesuffix(".py")
print(nome_arquivo)  # arquivo

# Limpando formatação de número de telefone
telefone = "+55 (11) 98765-4321"
digitos = telefone.removeprefix("+55 ").removeprefix("(").removesuffix("-4321")
print(digitos)  # (11) 98765
# O módulo typing foi melhorado no Python 3.9

# Antes do Python 3.9
from typing import List, Dict, Tuple, Set

def processar_dados(valores: List[int]) -> Dict[str, int]:
    return {"total": sum(valores)}

# Python 3.9 - tipos genéricos diretamente dos tipos built-in
def processar_dados_new(valores: list[int]) -> dict[str, int]:
    return {"total": sum(valores)}

# Outros exemplos
coordenadas: tuple[float, float] = (10.5, 20.3)
nomes: set[str] = {"Alice", "Bob", "Charlie"}
matriz: list[list[int]] = [[1, 2], [3, 4]]

# Também funciona com tipos mais complexos
mapa: dict[str, list[tuple[int, int]]] = {
    "pontos": [(0, 0), (1, 1), (2, 2)]
}

# O mesmo se aplica a palavras-chave especiais
from typing import Optional, Union

# Antes
def funcao(valor: Optional[int] = None) -> Union[str, int]:
    if valor is None:
        return "Sem valor"
    return valor * 2

# Obs: Optional e Union ainda são necessários
# Apenas os contêineres genéricos foram simplificados
# O parser PEG mais rápido e mais flexível
# Substitui o antigo parser LL(1)

import ast

# Análise de código como string
código = """
def soma(a, b):
    return a + b

resultado = soma(10, 20)
print(f"A soma é {resultado}")
"""

# Constrói uma árvore de sintaxe abstrata
arvore = ast.parse(código)

# Navega pelos nós da árvore
for node in ast.walk(arvore):
    if isinstance(node, ast.Call) and isinstance(node.func, ast.Name):
        if node.func.id == 'soma':
            print(f"Chamada para soma encontrada com argumentos:")
            for arg in node.args:
                if isinstance(arg, ast.Constant):
                    print(f"- {arg.value}")

# Saída:
# Chamada para soma encontrada com argumentos:
# - 10
# - 20

# O novo parser permite futuras melhorias na sintaxe
# e melhor tratamento de erros
# time.zoneinfo() - Suporte a fuso horário
from zoneinfo import ZoneInfo
from datetime import datetime

# Criando data/hora com informação de fuso horário
dt_tokyo = datetime(2023, 1, 1, 12, 0, tzinfo=ZoneInfo("Asia/Tokyo"))
dt_paris = datetime(2023, 1, 1, 12, 0, tzinfo=ZoneInfo("Europe/Paris"))

print(dt_tokyo)  # 2023-01-01 12:00:00+09:00
print(dt_paris)  # 2023-01-01 12:00:00+01:00

# Convertendo entre fusos horários
dt_tokyo_em_paris = dt_tokyo.astimezone(ZoneInfo("Europe/Paris"))
print(dt_tokyo_em_paris)  # 2023-01-01 04:00:00+01:00

# graphlib.TopologicalSorter - Ordenação topológica
from graphlib import TopologicalSorter

# Definindo dependências (quem depende de quem)
dependencias = {
    "setup.py": {"README.md"},
    "pacote": {"setup.py"},
    "testes": {"pacote"},
    "build": {"pacote", "testes"},
    "deploy": {"build"}
}

# Criar o ordenador topológico
ts = TopologicalSorter(dependencias)

# Obter a ordem de dependências
ordem = list(ts.static_order())
print(f"Ordem de execução: {ordem}")
# Ordem de execução: ['README.md', 'setup.py', 'pacote', 'testes', 'build', 'deploy']

Python 3.10

Python 3.10 foi lançado em outubro de 2021 e introduziu vários recursos importantes, incluindo a correspondência de padrões estruturais.

# O Python 3.10 introduziu pattern matching estrutural
# Semelhante a switch/case em outras linguagens, mas muito mais poderoso

# Exemplo básico
def analisar_status(status):
    match status:
        case 200:
            return "OK"
        case 404:
            return "Não encontrado"
        case 500:
            return "Erro interno do servidor"
        case _:  # Caso padrão (default)
            return f"Status desconhecido: {status}"

print(analisar_status(200))  # OK
print(analisar_status(418))  # Status desconhecido: 418

# Pattern matching com estruturas
def processar_comando(comando):
    match comando.split():
        case ["sair"]:
            return "Saindo do programa"
        case ["ajuda"]:
            return "Mostrando ajuda"
        case ["criar", nome]:
            return f"Criando {nome}"
        case ["abrir", nome, "como", modo]:
            return f"Abrindo {nome} no modo {modo}"
        case ["mover", origem, destino]:
            return f"Movendo de {origem} para {destino}"
        case _:
            return "Comando desconhecido"

print(processar_comando("sair"))  # Saindo do programa
print(processar_comando("criar projeto"))  # Criando projeto
print(processar_comando("abrir arquivo.txt como leitura"))  # Abrindo arquivo.txt no modo leitura
print(processar_comando("algo desconhecido"))  # Comando desconhecido
# Pattern matching com objetos e desempacotamento

# Definindo algumas classes para o exemplo
class Ponto:
    def __init__(self, x, y):
        self.x = x
        self.y = y

class Círculo:
    def __init__(self, centro, raio):
        self.centro = centro
        self.raio = raio

class Retângulo:
    def __init__(self, p1, p2):
        self.p1 = p1  # Canto superior esquerdo
        self.p2 = p2  # Canto inferior direito

# Função que usa pattern matching para formas
def descrever_forma(forma):
    match forma:
        case Ponto(x=0, y=0):
            return "Ponto na origem"
        case Ponto(x=x, y=y) if x == y:
            return f"Ponto na diagonal principal ({x}, {y})"
        case Ponto(x=x, y=y):
            return f"Ponto em ({x}, {y})"
        case Círculo(centro=Ponto(x=0, y=0), raio=r):
            return f"Círculo centrado na origem com raio {r}"
        case Círculo(centro=c, raio=r):
            return f"Círculo centrado em ({c.x}, {c.y}) com raio {r}"
        case Retângulo(p1=Ponto(x=x1, y=y1), p2=Ponto(x=x2, y=y2)):
            largura = abs(x2 - x1)
            altura = abs(y2 - y1)
            return f"Retângulo de {largura}x{altura}"
        case _:
            return "Forma desconhecida"

# Testando com diferentes formas
p1 = Ponto(0, 0)
p2 = Ponto(3, 3)
p3 = Ponto(5, 10)

c1 = Círculo(p1, 5)
c2 = Círculo(p3, 2)

r1 = Retângulo(Ponto(0, 0), Ponto(10, 20))

print(descrever_forma(p1))  # Ponto na origem
print(descrever_forma(p2))  # Ponto na diagonal principal (3, 3)
print(descrever_forma(p3))  # Ponto em (5, 10)
print(descrever_forma(c1))  # Círculo centrado na origem com raio 5
print(descrever_forma(c2))  # Círculo centrado em (5, 10) com raio 2
print(descrever_forma(r1))  # Retângulo de 10x20
# Pattern matching também funciona com dicionários e listas

def processar_dados(dados):
    match dados:
        case {"tipo": "usuario", "nome": nome, "idade": idade}:
            return f"Usuário {nome}, {idade} anos"

        case {"tipo": "produto", "nome": nome, "preço": preco}:
            return f"Produto {nome}, R$ {preco:.2f}"

        case {"tipo": "erro", "código": codigo, "mensagem": mensagem}:
            return f"Erro {codigo}: {mensagem}"

        case {"tipo": tipo, **resto}:
            return f"Tipo desconhecido: {tipo}, dados extras: {resto}"

        case _:
            return "Formato de dados inválido"

# Testando com diferentes dados
print(processar_dados({"tipo": "usuario", "nome": "Ana", "idade": 30}))
# Usuário Ana, 30 anos

print(processar_dados({"tipo": "produto", "nome": "Laptop", "preço": 3500}))
# Produto Laptop, R$ 3500.00

print(processar_dados({"tipo": "erro", "código": 404, "mensagem": "Não encontrado"}))
# Erro 404: Não encontrado

print(processar_dados({"tipo": "configuração", "modo": "debug", "ativo": True}))
# Tipo desconhecido: configuração, dados extras: {'modo': 'debug', 'ativo': True}

# Desempacotamento de listas com OR e número variável de elementos
def analisar_pontos(pontos):
    match pontos:
        case []:
            return "Lista vazia"
        case [x, y]:
            return f"Um ponto: ({x}, {y})"
        case [x, y, z]:
            return f"Um ponto 3D: ({x}, {y}, {z})"
        case [x, y, *resto] if len(resto) < 3:
            return f"Ponto com extras: ({x}, {y}) + {resto}"
        case [1, 2, *_] | [0, 0, *_]:
            return "Lista começa com [1, 2] ou [0, 0]"
        case _:
            return f"Lista muito longa ou formato desconhecido"

print(analisar_pontos([]))  # Lista vazia
print(analisar_pontos([10, 20]))  # Um ponto: (10, 20)
print(analisar_pontos([10, 20, 30]))  # Um ponto 3D: (10, 20, 30)
print(analisar_pontos([5, 5, 1, 2]))  # Ponto com extras: (5, 5) + [1, 2]
print(analisar_pontos([1, 2, 3, 4, 5]))  # Lista começa com [1, 2] ou [0, 0]
# Python 3.10 oferece mensagens de erro mais precisas e úteis

# Antes do Python 3.10
# Erro de sintaxe em uma chamada aninhada:
# 
# func(arg1, arg2, arg3, name=value
#
# SyntaxError: invalid syntax

# Python 3.10 mostra mensagens mais claras:
#
# func(arg1, arg2, arg3, name=value
#                             ^
# SyntaxError: ',' expected after dictionary key and value

# Mensagens melhoradas para tipos incompatíveis

# Antes do Python 3.10:
# TypeError: can only concatenate str (not "int") to str

# Python 3.10:
# TypeError: can only concatenate str (not "int") to str. 
# Did you mean to convert the int to a str first?

# Exemplo com erros de tipo
def soma_strings(a, b):
    return a + b

try:
    resultado = soma_strings("Python", 10)
except TypeError as e:
    print(f"Erro: {e}")
    # No Python 3.10: Erro: can only concatenate str (not "int") to str. 
    # Did you mean to convert the int to a str first?

# As mensagens de erro agora mostram a cadeia completa de exceções
try:
    try:
        1 / 0
    except Exception as e:
        raise ValueError("Ocorreu um erro de cálculo") from e
except ValueError as e:
    print(f"Erro capturado: {e}")
    print(f"Causa original: {e.__cause__}")
# Python 3.10 permite parênteses em gerenciadores de contexto

# Antes, para múltiplos gerenciadores de contexto:
# Opção 1: aninhamento (difícil de ler)
with open('arquivo1.txt') as f1:
    with open('arquivo2.txt') as f2:
        conteudo1 = f1.read()
        conteudo2 = f2.read()

# Opção 2: em uma linha (pode ficar muito longa)
with open('arquivo1.txt') as f1, open('arquivo2.txt') as f2:
    conteudo1 = f1.read()
    conteudo2 = f2.read()

# Python 3.10: parênteses para quebrar em múltiplas linhas
with (
    open('arquivo1.txt') as f1,
    open('arquivo2.txt') as f2,
    open('arquivo3.txt') as f3
):
    # Agora o código é mais claro e legível
    conteudo1 = f1.read()
    conteudo2 = f2.read()
    conteudo3 = f3.read()

# Útil para contextos complexos
import threading
import contextlib

# Exemplo mais complexo
with (
    open('log.txt', 'w') as log,
    contextlib.redirect_stdout(log),
    contextlib.redirect_stderr(log),
    threading.Lock()
):
    print("Esta saída vai para o arquivo log.txt")
    # Todas as saídas e erros são redirecionados para log.txt
    # e o código é executado com um lock de thread
# Anotações de tipo em declarações de variáveis
# Agora, variáveis podem ter anotações de tipo sem valor inicial

# Python 3.10
from typing import Optional, List

# Variáveis com anotações de tipo, sem inicialização
nome: str
idade: int
ativo: bool

# Em funções
def processar_usuario():
    global nome, idade
    nome = "Alice"
    idade = 30

# Isso facilita a definição de tipos em classes
class Usuario:
    nome: str
    email: str
    ativo: bool = True
    tentativas_login: int = 0
    detalhes: Optional[dict] = None
    permissoes: List[str] = []

# Operador | para unions de tipos
# Python 3.9:
from typing import Union
def func(param: Union[int, str]): pass

# Python 3.10:
def func(param: int | str): pass

# Funções de substring mais precisas
texto = "Python é uma linguagem de programação"

# Melhor detecção de substrings
if "Python" in texto:
    print("Encontrou 'Python'")

if "python" in texto.lower():
    print("Encontrou 'python' (case insensitive)")

# Melhor desempenho para operações de string

Python 3.11 e 3.12

Python 3.11 (lançado em outubro de 2022) e Python 3.12 (lançado em outubro de 2023) trouxeram melhorias significativas de desempenho e novos recursos.

# Python 3.11 é até 60% mais rápido que Python 3.10 em alguns benchmarks
# Essa melhoria vem do projeto "Faster CPython"

# Melhoria em loops e chamadas de função
import time

def testar_desempenho(n=10_000_000):
    inicio = time.time()

    soma = 0
    for i in range(n):
        soma += i

    fim = time.time()
    return fim - inicio

# Execute isso no Python 3.10 e 3.11 para ver a diferença
resultado = testar_desempenho()
print(f"Tempo de execução: {resultado:.3f} segundos")

# Também houve melhorias em:
# - Geração e rastreamento de exceções
# - Inicialização do interpretador
# - Manipulação de dicionários
# - Compreensões de lista, dict, set
# - Importação de módulos
# Python 3.11 introduziu grupos de exceções
# Útil para operações paralelas onde várias exceções podem ocorrer

from exceptiongroup import ExceptionGroup  # Python 3.11+

# Criando um grupo de exceções
def operacoes_paralelas():
    erros = []

    try:
        # Operação 1
        x = 1 / 0
    except Exception as e:
        erros.append(e)

    try:
        # Operação 2
        lista = [1, 2]
        item = lista[10]
    except Exception as e:
        erros.append(e)

    try:
        # Operação 3
        int("não é um número")
    except Exception as e:
        erros.append(e)

    # Se houver erros, lança um grupo de exceções
    if erros:
        raise ExceptionGroup("Múltiplos erros ocorreram", erros)

# Usando o operador except*
try:
    operacoes_paralelas()
except* ZeroDivisionError as e:
    print(f"Erro de divisão por zero: {e.exceptions}")
except* IndexError as e:
    print(f"Erro de índice: {e.exceptions}")
except* ValueError as e:
    print(f"Erro de valor: {e.exceptions}")
except* Exception as e:
    print(f"Outros erros: {e.exceptions}")

# Saída:
# Erro de divisão por zero: (ZeroDivisionError('division by zero'),)
# Erro de índice: (IndexError('list index out of range'),)
# Erro de valor: (ValueError("invalid literal for int() with base 10: 'não é um número'"),)
# Python 3.11 introduziu Self para anotações de tipo
from typing import Self  # Python 3.11+

class Builder:
    def __init__(self, value: int = 0) -> None:
        self.value = value

    def add(self, x: int) -> Self:
        self.value += x
        return self

    def multiply(self, x: int) -> Self:
        self.value *= x
        return self

    def as_string(self) -> str:
        return str(self.value)

# Permite chamadas encadeadas com verificação de tipo
resultado = Builder(10).add(5).multiply(2).as_string()
print(resultado)  # '30'

# Útil para classes derivadas
class AdvancedBuilder(Builder):
    def subtract(self, x: int) -> Self:
        self.value -= x
        return self

# O tipo Self garante que o método retorna o tipo correto
# mesmo quando herdado por uma subclasse
avancado = AdvancedBuilder(20).add(5).subtract(3).multiply(2)
print(avancado.value)  # 44
# Python 3.12 introduziu melhorias no sistema de tipagem

# TypedDict com items() tipados
from typing import TypedDict, Unpack

class Usuário(TypedDict):
    nome: str
    idade: int
    admin: bool

def processar_usuario(**kwargs: Unpack[Usuário]) -> None:
    print(f"Nome: {kwargs['nome']}")
    print(f"Idade: {kwargs['idade']}")
    print(f"Admin: {kwargs['admin']}")

# A chamada agora é verificada corretamente
processar_usuario(nome="Alice", idade=30, admin=True)

# Novos genéricos
from typing import TypeVar, Generic, assert_type

T = TypeVar('T')

class Box(Generic[T]):
    def __init__(self, content: T) -> None:
        self.content = content

    def get(self) -> T:
        return self.content

    def map(self, f: 'Callable[[T], U]') -> 'Box[U]':
        return Box(f(self.content))

# Type aliases genéricos
from typing import TypeVar, List, Dict

T = TypeVar('T')
MeuMapa = Dict[str, List[T]]  # Tipo genérico parametrizado

nomes: MeuMapa[str] = {"grupos": ["Alice", "Bob"]}
idades: MeuMapa[int] = {"grupos": [25, 30]}
# Python 3.12 simplifica a sintaxe de f-strings

# Antes, duplicação de chaves era necessária 
# para usar chaves literais
nome = "Alice"

# Python 3.11 e anteriores
print(f"Nome: {nome} {{não avaliado}}")  # Nome: Alice {não avaliado}

dicionário = {"chave": "valor"}
print(f"Acesso a dicionário: {dicionário['chave']}")

# Python 3.12 - expressões mais flexíveis
# Chaves literais não precisam ser duplicadas em alguns casos
print(f"Nome: {nome} {'não avaliado'}")
print(f"Cálculo: {(lambda x: x**2)(3)}")
print(f"{dicionário=}")

# Aspas dentro de f-strings são mais flexíveis
mensagem = f"Ela disse: "Olá, {nome}!""
print(mensagem)  # Ela disse: "Olá, Alice!"
# Python 3.12 formalizou o formato Wheel de distribuição de pacotes

# O formato wheel (.whl) é um formato de distribuição de pacotes Python
# que permite instalação mais rápida e confiável do que o formato fonte.

# Isso não muda o código Python, mas sim como os pacotes são distribuídos:

# Exemplo de uso do pip para instalar um wheel
# pip install package-1.0-py3-none-any.whl

# Exemplo de criação de um wheel para seu projeto
# python setup.py bdist_wheel

# Benefícios:
# - Instalação mais rápida (não precisa compilar durante a instalação)
# - Não requer um compilador na máquina alvo
# - Verifica assinaturas e hashes automaticamente
# - Melhor suporte para dependências nativas de sistema

# Outras melhorias:
# - Implementação otimizada de sum()
# - As tuplas e os módulos agora são subclasses de collections.abc
# - Suporte para vários interpretadores em módulos de extensão
# - Removidos muitos recursos obsoletos e deprecados

Migração entre Versões

Atualizar código Python para versões mais recentes requer atenção a mudanças e recursos obsoletos.

# Boas práticas para migração entre versões do Python

# 1. Teste seu código com ferramentas de análise
# Ferramentas como pyupgrade e pylint podem identificar padrões obsoletos

# 2. Use verificação de tipos como mypy

# 3. Mantenha testes automatizados robustos

# 4. Verifique as dependências do projeto
# Certifique-se de que as bibliotecas usadas são compatíveis

# 5. Use codemod ou ferramentas de migração automatizada

# 6. Aproveite a chance para modernizar seu código
# Exemplo: atualizar de Python 3.8 para 3.10 - usar pattern matching

# Código antigo
def processar_status(status_code):
    if status_code == 200:
        return "OK"
    elif status_code == 404:
        return "Não encontrado"
    elif status_code == 500:
        return "Erro interno"
    else:
        return f"Status desconhecido: {status_code}"

# Código modernizado (Python 3.10)
def processar_status(status_code):
    match status_code:
        case 200:
            return "OK"
        case 404:
            return "Não encontrado"
        case 500:
            return "Erro interno"
        case _:
            return f"Status desconhecido: {status_code}"
# Ferramentas úteis para migração:

# - pyupgrade: atualiza a sintaxe automaticamente para novas versões
# pip install pyupgrade
# pyupgrade --py310-plus arquivo.py

# - pylint: verifica problemas de estilo, bugs e recursos obsoletos
# pip install pylint
# pylint arquivo.py

# - mypy: verifica tipos estáticos
# pip install mypy
# mypy arquivo.py

# - pytest: executa testes automatizados
# pip install pytest
# pytest

# - pip-audit: verifica vulnerabilidades em dependências
# pip install pip-audit
# pip-audit

# Estratégia recomendada:
# 1. Faça backup ou garanta que o código está em controle de versão
# 2. Execute verificadores automáticos
# 3. Corrija os problemas encontrados
# 4. Execute testes e valide as alterações
# 5. Aproveite novos recursos da versão alvo
# Dicas para uma migração suave:

# 1. Conheça as mudanças entre versões
# - Leia a documentação "What's New" no site oficial Python
# - https://docs.python.org/3/whatsnew/

# 2. Use ambientes virtuais para testar
# python -m venv venv-3.10
# source venv-3.10/bin/activate  # Linux/Mac
# venv-3.10\Scripts\activate      # Windows

# 3. Preste atenção a recursos removidos
# Ex: Python 3.11 removeu várias funcionalidades obsoletas:
# - distutils (substituído por setuptools)
# - wstr em unicodedata (sem substituição)
# - vários módulos 'as' da importlib

# 4. Atualize uma versão por vez para projetos grandes
# Python 3.8 -> 3.9 -> 3.10 -> 3.11 -> 3.12

# 5. Fique atento ao ciclo de vida das versões Python
# Em geral, cada versão recebe suporte por 5 anos

# Estado atual (2023):
# 3.7 - Fim do suporte: 27 junho 2023
# 3.8 - Suporte até outubro 2024
# 3.9 - Suporte até outubro 2025
# 3.10 - Suporte até outubro 2026
# 3.11 - Suporte até outubro 2027
# 3.12 - Suporte até outubro 2028

Resumo

Nesta aula, você aprendeu sobre os recursos e melhorias introduzidos nas versões recentes do Python:

  • Python 3.8: Operador de atribuição (walrus), parâmetros posicionais, f-strings melhoradas
  • Python 3.9: Operadores de união para dicionários, métodos de string para prefixos/sufixos
  • Python 3.10: Pattern matching, mensagens de erro melhoradas, parênteses em gerenciadores de contexto
  • Python 3.11 e 3.12: Melhorias significativas de desempenho, grupos de exceções, anotações de tipo aprimoradas

Também discutimos estratégias para migração entre versões e as melhores práticas para manter seu código atualizado.

Próximos Passos na Jornada Python

Parabéns por concluir esta trilha de aprendizado Python! Para continuar sua jornada de aprendizado, considere:

  1. Aprofundar-se em áreas específicas:
  2. Desenvolvimento web com Django ou Flask
  3. Ciência de dados com pandas, numpy e matplotlib
  4. Machine Learning com scikit-learn, TensorFlow ou PyTorch
  5. Automação e scripting

  6. Participar da comunidade:

  7. Contribuir para projetos de código aberto
  8. Participar de grupos de usuários e conferências Python

  9. Desenvolver projetos pessoais:

  10. Aplicar os conhecimentos adquiridos em projetos reais
  11. Construir um portfólio para demonstrar suas habilidades

  12. Continuar estudando:

  13. Arquitetura de software e padrões de design
  14. Práticas de DevOps e CI/CD com Python
  15. Segurança em aplicações Python

← Voltar para Testes em Python