Article image
João Nascimento
João Nascimento19/04/2024 15:04
Compartilhe

O Ultimo Mestre do Python

  • #Python

Aqui estão dez truques avançados de Python para elevar seu jogo e escrever código mais rápido e limpo. Você pode conhecer alguns desses, mas tenho certeza de que todos têm algo a aprender aqui.

1 Usando classes com slots

Em uma classe com slots, definimos explicitamente os campos que nossa classe pode ter usando o nome mágico slots. Isso tem algumas vantagens:

  • Objetos criados a partir da classe ocuparão um pouco menos de memória
  • É mais rápido acessar atributos da classe
  • Você não pode adicionar novos atributos aleatoriamente a objetos de uma classe com slots

Aqui está um exemplo de como definir uma classe com slots:

class Carta:
  __slots__ = 'rank', 'naipe'
  def __init__(self, rank, naipe):
      self.rank = rank
      self.naipe = naipe

qh = Carta('dama', 'copas')

Para mim, a maior vantagem é que você não pode adicionar novos atributos aleatoriamente a uma classe com slots. Por exemplo, o seguinte erro de digitação gerará um erro em vez de Python criar silenciosamente um novo atributo:

qh.rank = 'dama'

Isso pode evitar erros mais custosos!

Para classes pequenas sem herança complexa, usar slots pode ser uma vantagem. Especialmente quando você precisa criar muitas instâncias de tal classe, as economias de memória e acesso mais rápido aos atributos podem fazer diferença também.

2. Obter o tamanho de um objeto

Com sys.getsizeof(), você pode verificar o uso de memória de um objeto Python. Vamos tentar isso:

import sys
mylist = range(0, 10000) 
print(sys.getsizeof(mylist)) #saida: 48 

Espera aí... por que essa lista enorme tem apenas 48 bytes?

É porque a função range retorna um objeto que se comporta apenas como uma lista. Um range, que é um objeto gerador, é muito mais eficiente em termos de memória do que usar uma lista real de números. Ele gera um novo número se solicitado em vez de armazenar todos os números.

Você pode ver por si mesmo usando uma compreensão de lista para criar uma lista real de números a partir do mesmo range:

import sys 
myreallist = [x for x in range(0, 10000)] 
print(sys.getsizeof(myreallist)) # 87632 

Então, brincando com sys.getsizeof(), você pode aprender mais sobre Python e seu uso de memória.

Infelizmente, sys.getsizeof() funciona bem apenas para tipos de dados integrados. Para tipos de dados personalizados e mais ferramentas de perfilamento, você deve verificar um pacote chamado Pympler, que oferece uma função chamada asizeof(). Para mais informações, leia a documentação do Pympler.(https://pympler.readthedocs.io/en/latest/)

3. Usando tuplas ao invés de listas

Uma tupla Python é um dos três tipos de dados de sequência integrados do Python, os outros sendo listas e objetos range. Ela compartilha muitas propriedades com uma lista:

  • Pode conter vários valores em uma única variável
  • É ordenada: a ordem dos itens é preservada
  • Uma tupla pode ter valores duplicados
  • É indexada: você pode acessar itens numericamente
  • Uma tupla pode ter um comprimento arbitrário
  • Há diferenças, porém:
  • Uma tupla é imutável; não pode ser alterada depois de definida.
  • Uma tupla é definida usando parênteses opcionais () em vez de colchetes []
  • Como as tuplas são imutáveis e, portanto, hasháveis, elas podem atuar como chave em um dicionário.
  • Se você não precisa modificar sua lista, considere uma tupla. Elas têm várias vantagens. Para começar, são mais rápidas de criar. Mas também podem evitar comportamentos inesperados porque não podem ser modificadas. Isso pode ajudar especialmente quando você está trabalhando com concorrência, em que threads ou processos acessam a mesma lista simultaneamente.
  • Finalmente, elas requerem menos memória. Para verificar essa última afirmação, vamos fazer um pequeno experimento:
import sys


l = [1, 2, 3]
t = (1, 2, 3)
print(sys.getsizeof(l))
print(sys.getsizeof(t))

4. Declarações condicionais (if/else inline)

Você pode 'inserir' construções if... else curtas para obter código conciso, mas ainda muito legível:

def get_status(erro):
  return 'OK' if not erro else 'Temos um problema'

print(get_status(erro=True))
# 'Temos um problema'
print(get_status(erro=False))
# 'OK'

5. Declarações elses em loops

Uma declaração de loop Python pode ter uma declaração else anexada a ela. A sintaxe básica:

for x in <algum iterável>: ... else: .... 

Ou, ao usar uma declaração while:

while <alguma condição>: ... else: ... 

A parte else é executada por padrão (quando o iterável é esgotado) a menos que o loop seja 'interrompido' com uma instrução break.

Qual é a vantagem de usar uma declaração else?

Muitas vezes, você está fazendo algo dentro do loop até que uma determinada condição seja atendida. Se essa condição for atendida, você define algum tipo de sinalizador, como encontrado = True, e interrompe o loop. No código que segue, você precisa verificar se o sinalizador foi definido (if encontrado ...), ou seja, a menos que você use uma declaração else!

Este exemplo vem diretamente da documentação do Python. Se um número primo for encontrado, tudo está bem. Se não, a cláusula else será alcançada:

for n in range(2, 10): 
   for x in range(2, n):
         if n % x == 0: print(n, 'equals', x, '*', n//x) 
         break else: # o loop terminou sem encontrar um fator print(n, 'é um número primo') 

Qual é a desvantagem?

Muitos programadores não esperarão uma declaração else após um loop, especialmente ao virem de outras linguagens. Portanto, a 'maneira antiga' de fazer as coisas pode ser realmente mais legível para a maioria.

6 Ordenando objetos por múltiplas chaves

Suponha que queremos classificar a seguinte lista de dicionários:

pessoas = [ 
 { 'nome': 'João', "idade": 64 },
 { 'nome': 'Janete', "idade": 34 },
 { 'nome': 'Ed', "idade": 24 }, 
 { 'nome': 'Sara', "idade": 64 },
 { 'nome': 'João', "idade": 32 }, 
 { 'nome': 'Joana', "idade": 34 }, 
 { 'nome': 'João', "idade": 99 }, ] 

Mas não queremos apenas classificá-la por nome ou idade; queremos classificá-la por ambos os campos. Em SQL, isso seria feito com uma consulta como:

SELECT * FROM pessoas ORDER by nome, idade 

Graças à garantia do Python de que as funções de classificação oferecem uma ordem de classificação estável, na verdade há uma solução direta para esse problema. Isso significa que itens que são comparados igualmente mantêm sua ordem original.

Para conseguir classificar por nome e idade, podemos fazer isso:

import operator 
pessoas.sort(key=operator.itemgetter('idade')) 
pessoas.sort(key=operator.itemgetter('nome')) 

Note como eu inverti a ordem. Primeiro classificamos por idade e depois por nome. Com operator.itemgetter() obtemos os campos de idade e nome de cada dicionário dentro da lista de uma forma concisa.

Isso nos dá o resultado que estávamos procurando:

[ {'nome': 'Ed', 'idade': 24}, {'nome': 'Joana', 'idade': 34}, 
{'nome': 'Janete','idade': 34}, {'nome': 'João', 'idade': 32}, {'nome': 'João', 'idade': 64}, {'nome': 'João', 'idade': 99}, {'nome': 'Sara', 'idade': 64} ] 

Os nomes são classificados primeiramente, e as idades são classificadas se o nome for o mesmo. Então todos os Joãos são agrupados juntos, classificados por idade.

7 Compreensões de lista

Uma compreensão de lista é uma maneira elegante de preencher uma lista sem usar um loop for. A sintaxe básica para uma compreensão de lista é:

[ expressão for item in lista if condicional ] 

Um exemplo básico para preencher uma lista com uma sequência de números:

minhalista = [i for i in range(10)] print(minhalista) # [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] 

E porque você pode usar uma expressão, também pode fazer algumas operações matemáticas:

quadrados = [x**2 for x in range(10)] print(quadrados) # [0, 1, 4, 9, 16, 25, 36, 49, 64, 81] 

Ou até mesmo chamar uma função externa:

def alguma_funcao(a):
 return (a + 5) / 2 minha_formula = [alguma_funcao(i) for i in range(10)] 
print(minha_formula) # [2.5, 3.0, 3.5, 4.0, 4.5, 5.0, 5.5, 6.0, 6.5, 7.0] 

E finalmente, você pode usar o 'if' para filtrar a lista. Neste caso, mantemos apenas os valores que são divisíveis por 2:

filtrado = [i for i in range(20) if i%2==0] print(filtrado) # [0, 2, 4, 6, 8, 10, 12, 14, 16, 18] 

Menos conhecido, mas tão útil, isso também funciona para conjuntos e dicionários. Alguns exemplos podem ser encontrados neste artigo sobre compreensões.

8 Classes de dados

Desde a versão 3.7, o Python oferece nativamente classes de dados. Existem várias vantagens sobre classes regulares ou outras alternativas como retornar múltiplos valores ou dicionários:

  • uma classe de dados requer uma quantidade mínima de código
  • você pode comparar classes de dados porque eq é implementado para você
  • você pode imprimir facilmente uma classe de dados para depuração porque repr também é implementado
  • classes de dados requerem dicas de tipo, reduzindo as chances de erros
  • Aqui está um exemplo de uma classe de dados em ação:
from dataclasses import dataclass 
@dataclass 
class Carta: rank: str naipe: str carta = Carta("Q", "copas") print(carta == carta) # True print(carta.rank) # 'Q' print(carta) # Carta(rank='Q', naipe='copas') 

Você pode combinar a técnica de classes com slots do #1 com classes de dados para criar uma classe de dados com um conjunto fixo de campos.

Se você quiser aprender mais, confira este tutorial detalhado sobre classes de dados.

9. Mesclando dicionários

Desde o Python 3.5, tornou-se mais fácil mesclar dicionários do que antes. Mas desde o Python 3.9, as coisas até melhoraram:

dict1 = { 'a': 1, 'b': 2 } 
dict2 = { 'b': 3, 'c': 4 } 
mesclado = { **dict1, **dict2 } 
print(mesclado) # {'a': 1, 'b': 3, 'c': 4} 
# Apenas Python >= 3.9 mesclado = dict1 | dict2 print(mesclado) # {'a': 1, 'b': 3, 'c': 4} 

Se houver chaves sobrepostas, as chaves do primeiro dicionário serão sobrescritas.

10 . Usando e conhecendo itertools

Itertools é uma biblioteca integrada que oferece vários blocos de construção de iteradores. Esses blocos podem ser usados para realizar algumas operações comuns de forma eficiente. Provavelmente, você escreveu algumas das funções que itertools oferece à mão, então vale a pena conhecer esta biblioteca.

Aqui estão apenas alguns exemplos:

# Calcular todos os produtos de uma entrada 
list(itertools.product('abc', repeat=2)) 
# Calcular todas as permutações
list(itertools.permutations('abc')) 
# Pegar elementos do iterador enquanto o predicado for verdadeiro 
list(itertools.takewhile(lambda x: x<5, [1,4,6,4,1])) 

Estes são apenas alguns dos blocos básicos de construção. Certifique-se de verificar as receitas do Itertools na documentação, pois é uma excelente demonstração do que você pode construir usando esses blocos básicos de construção. E se você quiser aprender mais, recomendo este excelente tutorial sobre Itertools no Real Python.

É isso. Espero que tenha aprendido algo novo! Se você tiver algo a acrescentar, não hesite em deixar um comentário.

Compartilhe
Comentários (1)
Gilberto Vecchi
Gilberto Vecchi - 19/04/2024 16:03

Muito bom! Obrigado por compartilhar o conhecimento!