Python 3.10: novidades da versão

Olá Pythonista!

Hoje você vai ficar por dentro da tão aguardada versão 3.10 do Python!

Vamos falar sobre as novas funcionalidades, as novidades, as inovações e o que há de mais interessante nessa nova versão do Python.

Está ansioso para testar o poderoso Pattern Matching (“Correspondência ou Casamento de Padrões”)?

Estão dizendo por aí que é o Switch/Case com esteróides que o Python nunca teve :laughing:

Ou está ansioso para ver como as mensagens de erros vão ser incrementadas na versão 3.10?

Neste post, você aprenderá sobre:

  • Debugar código com mensagens de erro mais úteis e precisas
  • Usar Pattern Matching
  • Iterar sobre estruturas de forma mais segura com zip() utilizando o parâmetro strict

Então vem com a gente que vamos te explicar TUDO!

Vá Direto ao Assunto…

Versão 3.10 do Python

Pyhthon 3.10

Python 3.10 foi lançado!

O grupo de desenvolvedores têm trabalhado na nova versão desde Maio de 2020 para trazer a você uma melhor, mais rápida e mais segura versão do Python.

Em 4 de outubro de 2021, a primeira versão 3.10 oficial foi disponibilizada!.

Cada nova versão do Python traz uma série de mudanças que estão disponíveis na documentação.

Aqui, você aprenderá sobre os novos recursos mais legais e empolgantes. :wink:

Para experimentar os novos recursos, você precisa executar o Python 3.10!

Você pode obtê-lo na página inicial do Python. Como alternativa, você pode usar o Docker com a imagem Python mais recente.

E agora vamos às NOVIDADES!

Melhores Mensagens de Erro

Apesar de Python ser uma linguagem conhecida por ser amigável ao Desenvolvedor, algumas partes deixam a desejar.

Uma das maiores reclamações quanto à isso eram as mensagens de erro!

O Python 3.10 vem com uma série de mensagens de erro mais precisas e construtivas para tentar contornar isso.

Veja o exemplo clássico do seu primeiro “Hello World”. Mas vamos supor que você tenha se esquecido de fechar a string com aspas.

1
print("Hello World)

Veja como o erro não ajuda muito:

1
2
3
4
File "<stdin>", line 1
    print("Hello World)
                      ^
SyntaxError: EOL while scanning string literal

Havia um SyntaxError no código!

EOL, o que isso realmente significa? :thinking:

Você volta ao seu código e, depois de olhar e pesquisar um pouco, percebe que está faltando aspas no final da String.

Uma das melhorias mais impactantes no Python 3.10 são mensagens de erro melhores e mais precisas para muitos problemas comuns!

Ufa!

Veja agora a mensagem de erro no Python 3.10:

1
2
3
4
File "<stdin>", line 1
    print("Hello World!)
          ^
SyntaxError: unterminated string literal (detected at line 3)

A mensagem de erro ainda é um pouco técnica, mas o misterioso EOL se foi e ainda adicionaram a linha onde o erro ocorreu!

Agora, a mensagem informa que você precisa encerrar sua string!

Existem melhorias semelhantes para muitas mensagens de erro diferentes. Vamos ver outro exemplo!

Imagine o seguinte cenário, em que temos um dict que não foi fechado!

Ainda não é um EXPERT nos Dicionários do Python? Então já clique no link para acessar nosso Post COMPLETO sobre Dicionários do Python em seguida!

Este é o código (com erro):

1
2
3
4
5
6
7
8
vendas = {
    'João': 12, 
    'Clara': 10, 
    'Ana': 21, 
    'Carlos': 14

for nome, vendas in vendas.items():
    print(f'{nome} vendeu {vendas} items este mês')

Observação: Nesse post teremos diversos exemplos utilizando f-strings. Se você ainda não domina esse conceito, já clica aqui e dá uma lida nesse post completo sobre F-Strings no Python

E agora perceba que a mensagem de erro não nos diz ABSOLUTAMENTE NADA:

1
2
3
4
File "<stdin>", line 7
    for nome, vendas in vendas.items():
      ^
SyntaxError: invalid syntax

Pô, time Guido! :trollface:

Agora veja como as coisas mudam na versão 3.10 do Python:

1
2
3
4
File "<stdin>", line 7
    vendas = {
             ^
    SyntaxError: '{' was never closed

Tradução do Erro:

SyntaxError: ‘{‘ não foi fechado

Aí sim, #TeamPython! :thumbsup:

Nesse mesmo código, imagine que tenhamos esquecido uma vírgula entre os itens do Dicionário, dessa forma:

1
2
3
4
5
6
7
8
vendas = {
    'João': 12 
    'Clara': 10, 
    'Ana': 21, 
    'Carlos': 14

for nome, vendas in vendas.items():
    print(f'{nome} vendeu {vendas} items este mês')

Olha o erro, que nada a ver:

1
2
3
4
File "<stdin>", line 1
    'Carlos': 14
    ^
IndentationError: unexpected indent

Aí não, galera! :trollface:

1
2
3
4
File "<stdin>", line 1
    'João': 12 
            ^^
SyntaxError: invalid syntax. Perhaps you forgot a comma?

Tradução do Erro:

SyntaxError: sintaxe inválida. Talvez você tenha esquecido uma vírgula?

Ponto pro time Python! :thumbsup:

Último exemplo, o uso incorreto de = e == em comparações:

1
2
3
4
numeros = [1, 5, 6, 8, 48, 55]

if len(numero) = 6:  # Aqui deveria ser ==
    print(f'Número máximo de apostas')

O erro:

1
2
3
4
File "<stdin>", line 1
    if len(numero) = 6:  # Aqui deveria ser ==
                   ^
SyntaxError: invalid syntax

Até que vai, time! :trollface:

A mensagem de erro até ajuda nesse caso, mas a melhoraram para:

1
2
3
4
File "<stdin>", line 1
    if len(numero) = 6:  # Aqui deveria ser ==
                   ^
SyntaxError: cannot assign to attribute here. Maybe you meant '==' instead of '='?

Tradução do Erro (com modificações):

SyntaxError: Esse local não permite atribuição. Talvez você quis dizer '==' em vez de '='?

Pattern Matching ou “Correspondência de Padrões”

Sua introdução à linguagem às vezes é chamada de switch ... case do Python, mas você verá que o conceito de Pattern Matching é muito mais poderoso que isso!

Esse recurso da linguagem pode ser usado para detectar e desconstruir diferentes estruturas em seus dados e também realizar a correspondência literal de padrões.

Confuso né? Mas calma que vamos destrinchar esse carinha

Primeiro, vamos falar sobre a sintaxe básica do match/case do Python:

1
2
3
4
5
6
7
8
9
10
match {elemento}:
    case {padrão 1}:
        {ação 1}
    case {padrão 2}:
        {ação 2}
    case {padrão 3}:
        {ação 3}
    # Outros padrões...
    case _:
        {ação padrão ou ação default}

Vamos entender:

  • Primeiro, temos a adição das novas Palavras Reservadas (Keywords) match e case. A keyword match inicia um novo bloco para realizar a correspondência de padrões.
  • Em seguida temos o {elemento} que pode ser um inteiro, um decimal, uma lista, um objeto e assim por diante: é ele quem vai ser “testado”.
  • Depois, temos os blocos case {padrão}: {ação} que definem: dado que tal {padrão} foi correspondido, execute tal {ação}
  • Caso não haja a correspondência de nenhum padrão acima (case _:), execute a {ação padrão}.

Agora vamos para parte legal: o exemplo!

1
2
3
4
5
6
7
8
9
10
11
parentesco = 'mãe'

match parentesco:
    case 'pai':
        print("O parentesco encontrado é 'Pai'")
    case 'mãe':
        print("O parentesco encontrado é 'Mãe'")
    case 'filho(a)':
        print("O parentesco encontrado é 'Filho(a)'")
    case _:
        print('Parentesco encontrado não mapeado')

Se colocarmos esse código em execução, veremos que haverá uma correspondência no case 'mãe' e a saída será:

1
O parentesco encontrado é 'Mãe'

Legal né?! :heart_eyes: Mas calma que esse é apenas o exemplo básico!

Vamos ver outro exemplo mais interessante!

Será que é possível utilizar esse tal de Correspondência de Padrões em Estruturas de Repetição “FOR”? :thinking:

Com certeza!

Veja o exemplo a seguir:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
notas_alunos = {
    'João': [0, 5, 10],
    'Clara': [7, 9, 8],
    'Maciel': "",
    'Jonas': [0, 1],
    'Maria': [5, 7, 10],
    'Marcelo': [],
    'Artur': [1]    
}

# dict.items retorna uma tupla contendo a chave na primeira posição e o valor na segunda posição
for aluno, notas in notas_alunos.items():
    # Casamento de padrão com chave, valor
    match aluno, notas:
        case aluno,[x, y, z]:
            print(f'Aluno {aluno} fez três provas. Média = {(x + y + z)/3:.1f}')

        case aluno,[x, y]:   
            print(f'Aluno {aluno} fez duas provas. Média = {(x + y)/2:.1f}')

        case aluno,[x]:   
            print(f'Aluno {aluno} fez uma prova. Média = {x:.1f}')

        case aluno,[]:   
            print(f'Aluno {aluno} não fez nenhuma prova. REPROVADO')

        case _:
            print('>>> Formato inválido')

A saída será:

1
2
3
4
5
6
7
Aluno João fez três provas. Média = 5.0
Aluno Clara fez três provas. Média = 8.0
>>> Formato inválido
Aluno Jonas fez duas provas. Média = 0.5
Aluno Maria fez três provas. Média = 7.3
Aluno Marcelo não fez nenhuma prova. REPROVADO
Aluno Artur fez uma prova. Média = 1.0

E agora, vamos a explicação:

  • Temos uma iteração simples de dicionário, utilizando o dict.items() que retorna uma tupla contendo (chave, valor)
  • Em cada case temos a correspondência da chave que é aluno e valor que é uma Lista.
  • Caso notas tenha uma correspondência com [x, y, z], cairá no primeiro case.
  • Caso notas tenha uma correspondência com [x, y], cairá no segundo case.
  • Caso notas tenha uma correspondência com [x], cairá no terceiro case.
  • Caso notas tenha uma correspondência com [] (lista vazia), cairá no quarto case.
  • Caso não tenha correspondência com nenhum case acima, o case _ será executado!

Ainda podemos refatorar o código da seguinte forma, ficando mais genérico e mais conciso.

Preste atenção no primeiro case! Veja que é possível especificar o tipo de objeto que queremos fazer a correspondência:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
notas_alunos = {
    'João': [0, 5, 10],
    'Clara': [7, 9, 8],
    'Maciel': "",
    'Jonas': [0, 1],
    'Maria': [5, 7, 10],
    'Marcelo': [],
    'Artur': [1]    
}

for aluno, notas in notas_alunos.items():
    match aluno, notas:
        case aluno, [int(0) | float(0.0), *resto]:
            print(f'Aluno {aluno} zerou a primeira prova. ' 
                  f'Suas outras notas foram {resto}')

        case aluno, list(notas_aluno):            
            print(f'Aluno {aluno} fez {len(notas_aluno)} prova(s). '
                  f'Média = {sum(notas_aluno)/3:.1f}')

        case _:
            print('>>> Formato inválido')

E a saída, com pequenas alterações:

1
2
3
4
5
6
7
Aluno João fez 3 prova(s). Média = 5.0
Aluno Clara fez 3 prova(s). Média = 8.0
Aluno Maciel fez 0 prova(s). Média = 0.0
Aluno Jonas fez 2 prova(s). Média = 0.3
Aluno Maria fez 3 prova(s). Média = 7.3
Aluno Marcelo fez 0 prova(s). Média = 0.0
Aluno Artur fez 1 prova(s). Média = 0.3

Também podemos usar correspondência de padrões “OR”, utilizando o símbolo pipe |.

Dessa forma, é possível passar dois (ou mais) possíveis padrões para testar.

Vamos supor que seja necessário verificar quem tirou 0 (Inteiro) ou 0.0 (Float) na primeira prova.

Podemos fazer isso da seguinte maneira:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
notas_alunos = {
    'João': [0, 5, 10],
    'Clara': [7, 9, 8],
    'Maciel': "",
    'Jonas': [0, 1],
    'Maria': [5, 7, 10],
    'Marcelo': [],
    'Artur': [1]    
}

for aluno, notas in notas_alunos.items():
    match aluno, notas:
        case aluno, [int(0) | float(0.0), *resto]:
            print(f'Aluno {aluno} zerou a primeira prova. '
                  f'Suas outras notas foram {resto}')

        case aluno, list(notas_aluno):            
            print(f'Aluno {aluno} fez {len(notas_aluno)} prova(s). '
                  f'Média = {sum(notas_aluno)/3:.1f}')

        case _:
            print('>>> Formato inválido')

Veja a saída:

1
2
3
4
5
6
7
Aluno João zerou a primeira prova. Suas outras notas foram [5, 10]
Aluno Clara fez 3 prova(s). Média = 8.0
>>> Formato inválido
Aluno Jonas zerou a primeira prova. Suas outras notas foram [1]
Aluno Maria fez 3 prova(s). Média = 7.3
Aluno Marcelo fez 0 prova(s). Média = 0.0
Aluno Artur fez 1 prova(s). Média = 0.3

Vamos entender aquele primeiro case muito louco:

1
case aluno, [int(0) | float(0.0), *resto]:
  • Primeiro fazemos a correspondência de aluno com a chave do dicionário, até aí nada de novo.
  • Em seguida, fazemos uma correspondência com o primeiro elemento da lista que pode ser int(0) ou float(0.0).
  • Se o padrão acima tiver correspondência, pegamos o resto da lista com o padrão *resto

Bom, acho que já deu para perceber o poder da Correspondência de Padrões ou Pattern Matching do Python 3.10 né?!

Com certeza esse é um assunto que renderá um Post completo sobre Pattern Matching!

Quer ficar por dentro de quando sair esse e outros conteúdo aqui na Python Academy?! Então já APROVEITA A OPORTUNIDADE! :point_down:

Adição do parâmetro strict ao método zip()

O método zip() percorre diversos iteráveis ao mesmo tempo, parando de iterar na menor sequência.

Isto é: se tivermos duas listas para iterar e uma tiver 4 elementos e outra tiver 3 elementos, zip() vai iterar apenas sobre os 3 primeiros elementos da primeira lista.

Vamos ver um exemplo:

1
2
3
4
5
usuarios = ['2021245', '2021859', '2021522', '2021636']
acessos = [5, 4, 8]

for u, a in zip(usuarios, acessos):
    print(f'Usuário {u} = {a} acessos')

Veja que a saída possui apenas 3 elementos, como esperado:

1
2
3
Usuário 2021245 = 5 acessos
Usuário 2021859 = 4 acessos
Usuário 2021522 = 8 acessos

A novidade trazida pela versão 3.10 do Python é a inclusão do parâmetro strict.

Caso esse parâmetro seja setado como True e a função zip detecte iteráveis de tamanhos diferentes, um erro ValueError será lançado.

Vamos ver o exemplo:

1
2
3
4
5
usuarios = ['2021245', '2021859', '2021522', '2021636']
acessos = [5, 4, 8]

for u, a in zip(usuarios, acessos, strict=True):
    print(f'Usuário {u} = {a} acessos')

Veja o erro:

1
2
3
4
5
6
Usuário 2021245 = 5 acessos
Usuário 2021859 = 4 acessos
Usuário 2021522 = 8 acessos
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
ValueError: zip() argument 2 is shorter than argument 1

Tradução do Erro: ValueError: argumento 2 da zip() é menor que o argumento 1

Adição de Parentêses à Context Managers

Context Managers permitem que possamos gerenciar melhor recursos computacionais como arquivos, locks ou interfaces de rede.

Eles são definidos com a palavra reservada with.

Para quebrar longas linhas do with antes do Python 3.10, era necessário utilizar a barra \, da seguinte forma:

1
2
with open('arquivo.txt', 'r', encoding='utf-8') as arquivo_leitura, \
     open('arquivo_saida.txt', 'w', encoding='utf-8') as arquivo_saida:

Agora, é possível utilizar Context Managers com parentêses, da seguinte maneira:

1
2
3
4
with (
    open('arquivo.txt', 'r', encoding='utf-8') as arquivo_leitura,
    open('arquivo_saida.txt', 'w', encoding='utf-8') as arquivo_saida    
):

Uma pequena alteração, mas que melhora a legibilidade do código.

Conclusão

É sempre interessante ficarmos ligados nas novas funcionalidades que cada versão do Python nos traz, se quisermos nos manter relevantes no mercado de trabalho!

Neste tutorial, você viu novos recursos como:

  • Mensagens de erro mais amigáveis
  • O poder do Pattern Matching ou Correspondência de Padrões!
  • Iterações mais seguras de sequências com zip() e o parâmetro strict

Divirta-se experimentando os novos recursos!

Se ficou com alguma dúvida, fique à vontade para deixar um comentário no box aqui embaixo! Será um prazer te responder! :wink:

Gostou do conteúdo? Compartilha aí!