Como otimizar suas classes em Python?
Quando criamos um objeto de uma classe python, os atributos desse objeto são armazenados em um dicionários chamado __dict__
. Esse dicionário é utilizado para definir e obter os atributos do objeto e também te dá a liberdade de criar novos atributos dinamicamente a partir do objeto.
Para exemplificar, vou criar uma classe chamada Pessoa
que irá receber 2 parâmetros, nome
e idade
. Após criar a instância da classe, vou adicionar um novo parâmetro para essa instância chamado sobrenome
.
Essa é uma das coisas bem legais em python. Pode parecer estranho para quem não está acostumado com linguagens dinâmicas, mas essa liberdade permite você fazer coisas incríveis com metaprogramação.
Dicionários são uma ferramenta extremamente poderosa em python, mas ela tem um custo e é importante você entender os problemas que eles podem causar.
Os problemas do dicionário
- Dicionários precisam de memória! Imagina que sua aplicação precise criar milhões de objetos, você vai consumir bastante RAM!
- Dicionários são hash maps e o pior cenário de complexidade para fazer um get/set em uma propriedade é O(n).
Como otimizar nossas classes então?
A resposta para essa questão está na palavra __slots__
!
O que são __slots__
? Segue uma tradução livre da documentação do python.
Vamos criar agora a classe pessoa definindo os __slots__
. Perceba que quando nós definimos os __slots__
a instância da classe não possuiu mais o __dict__
e se tentamos criar um novo atributo para a classe também recebemos um erro como resposta.
Vamos ver na prática, os dois benefícios que conseguimos ao definir os __slots__
!
Primeiro benefício: Velocidade⚡️
Como dito anteriormente, o dicionário é um hash map e seu pior cenário de acesso a um atribruto é O(n). Quando temos __slots__
o python faz uso de uma lista
para mapear os atributos. Uma lista possui uma complexidade O(1) no seu pior cenário de acesso a um atributo, em poucas palavras isso quer dizer que a lista
é mais performática que o dict
.
Nesse link é possível comparar o tempo de complexidade da implementação padrão de python dos tipos dict
, list
, set
e collections.deque
Nesse exemplo podemos observar que uma instância de uma classe com __slots__
ao realizar a tarefa de atribuir, acessar e excluir um atributo de uma instância é mais rápida!
Segundo benefício: Economia de memória 🪶
Para medir o consumo de memória vamos utilizar uma biblioteca chamada pympler. O python possui nativamente uma função chamada getsizeof
do módulo sys
, mas ele não inclui na sua contabilização os objetos de referenciados ou seja, o __dict__
é um objeto refenciado e seria ignorado pelo getsizeof()
.
Nesse exemplo conseguimos um ganho de mais de 50% de espaço em memória!!!!
Conclusão
Da noite para o dia você não precisa adicionar __slots__
em todas as suas classes, em alguns cenários a liberdade que o __dict__
te proporciona é mais vantajoso que o ganho de memória e velocidade!
Analise seu cenário antes de fazer qualquer otimização prematura!
Obrigado!