Como funciona o núcleo do Python e como podemos nos aproveitar disso

Alexander Hugo
AHRS.io
Published in
7 min readMay 24, 2020

Estamos na década de 1980. Guido van Rossum acaba de conceber o Python como sucessor da linguagem ABC. O que ele não imaginava era a popularidade que esta linguagem de programação viria a ter nos dias atuais, mas algumas razões podem explicar essa ascensão, e com certeza a facilidade de entendimento e desenvolvimento para quem inicia nesse mundo é uma das mais importantes.

Para entender o motivo dessa facilidade, é necessário falarmos sobre o protocolo de metaobjetos. No passado, as linguagens de programação viviam uma dualidade. De um lado, existiam as linguagens de mais alto nível que traziam elegância mas eram consideradas ineficientes, sendo até então mais utilizadas em pesquisas acadêmicas. Do outro, as linguagens que dominavam os ambientes de produção eram o inverso, possuíam estruturas que acoplavam maior complexidade ao entendimento, mas davam conta do recado em termos de eficiência.

A partir desse contexto, Gregor, Jim e Daniel criaram o paradigma de protocolo de metaobjetos com a proposta de aliar elegância e eficiência em um mesmo lugar, trazendo ao Python de hoje a habilidade que o destacou dentre tantas linguagens. Mas como isso funciona?

O MOP (Metaobject Protocol) funciona como um núcleo da linguagem, uma interface na qual os próprios elementos da linguagem implementam para construir objetos de alto nível e que também nos permite estender e criar novas funcionalidades a partir de nossas necessidades. No python, os métodos que compõem esse modelo ficaram conhecidos como dunder methods. Porém, a melhor forma de enterdemos como esses métodos funcionam é na prática, então vamos lá.

Dados socioeconômicos do USA Census Bureau

Para exemplificar, criaremos uma classe que terá a responsabilidade de tratar dados socioeconômicos do USA Census Bureau, comuns em projetos de Data Science. Mas antes, vamos utilizar um arquivo JSON para guardar as informações dos três arquivos que serão utilizados: social_features, economic_features e demographic/housing_features.

Para deixar o projeto estruturado, colocaremos os arquivos baixados na pasta Sources e criaremos dois arquivos de desenvolvimento: CensusClass.py, responsável pelo desenvolvimento da classe e o arquivo Main.py que compilará a execução.

Estrutura de diretórios do exemplo

Carregando e preparando os arquivos

Para cada instância presente no JSON, vamos carregar o conteúdo arquivo .CSV presente dentro de cada .ZIP. Como o Census adiciona três arquivos em cada .zip na hora de baixar, o arquivo selecionado será o que possuir o padrão ‘_data_’ no nome. Como uma forma de tornar o código mais genérico, vamos usar o valor de cada instância de content no JSON para nomear os DataFrames.

Ao final, teremos os três arquivos carregados, o que nos possibilita visualizar a forma de cada um deles utilizando o Pandas.

Com os arquivos carregados, estamos prontos para falar do foco da noite: os dunder methods.

Dunder Methods

Os dunder methods (double underscore methods) são métodos invocados pelo interpretador do Python para realizar operações básicas em objetos. Logo, toda vez que você soma dois elementos, a linguagem invoca um método (nesse caso, o __sum__) e você recebe o resultado da operação. Caso você já tenha tido qualquer interação com Programação Orientada a Objetos em Python, você já usou o __init__, um dunder method para invocar o inicializador de um classe.

A mágica acontece a partir do momento em que conseguimos implementar esses métodos em nossas classes, possibilitando ao interpretador o entendimento de que um determinada operação da linguagem foi personalizada e, com isso, ao executarmos funções de alto nível relacionadas, os seus métodos específicos são chamados automaticamente. A ideia é que não precisemos explicitar a chamada desses métodos, exceto no caso do próprio __init__. Vamos ver como funciona a partir do nosso exemplo.

__init__

Vamos começar a construção da classe criando o método __init__ que nos permitirá instanciar novos objetos e passar parâmetros nesta construção. Nós receberemos três parâmetros: df, o DataFrame com os dados do censo, index_name, coluna que será utilizada como index do DataFrame, e features_description, uma variável que receberá a descrição de todas as features presentes no df, já que os arquivos do Census utilizam códigos para cada feature e a primeira linha do conjunto de dados contêm a explicação destas. Ao final, podemos remover essa linha descritiva de df.

Cada arquivo gerado pelo Census possui diversas colunas de Margem de Erro e Percentual, e por não estarmos interessados nessas colunas, iremos remover do DataFrame usando a variável cols_to_drop que criamos para selecionar todas as colunas que possuem esse padrão.

Até aqui, já podemos instânciar novos objetos e verificar os atributos, entretanto, vamos prosseguir um pouco antes de executar o nosso código. Está na hora de apresentar dois dunder methods que são correlacionados: o __repr__ e o __str__.

__repr__ e __str__

Esses dois métodos são utilizados para representar Strings. O __repr__ é usado quando chamamos a função repr() incorporada no Python ou quando não há uma implementação do __str__ e tentamos mostrar um elemento na tela utilizando o print(), por exemplo. Na ausência de uma implementação para o __repr__, o seu método de alto nível repr() exibe a posição de memória do elemento. Já o __str__ é chamado sempre que str() é acionado. Logo, a cada print que você faz na tela, há um __str__ dunder method por debaixo dos panos. Abaixo segue a nossa implementação de ambos.

Quando a função repr for chamada, mostraremos informações do dataset através da função info(). Com o __str__, mostraremos o número de linhas e colunas. Para testar, instanciaremos a classe CensusDataFile com o dataframe relacionado aos social_features para testar os métodos que implementamos.

Com os métodos de String funcionando, podemos conhecer outros dois dunder methods: o __call__ e o __len__.

__call__ e __len__

Toda vez que quisermos mostrar o conteúdo do dataframe, precisaremos invocar o atributo df da instância da classe criada. Para facilitar, podemos implementar o método __call__, que emula a chamada de uma instância de classe, ou seja, basta chamar nosso objeto tal como uma função que o retorno será o conteúdo do atributo df.

Outro método interessante é o __len__. Ao tentar visualizar o número de linhas de um dataframe usando algo como len(df), você receberá uma mensagem de erro, afinal, o Pandas utiliza outras formas para iterar sobre seus registros. Porém, podemos fazer o len() funcionar no nosso exemplo sobrescrevendo o seu respectivo dunder method e mapeando para uma função do Pandas que nos mostre o número de linhas. Além desses dois métodos, também iremos criar um getter para pegar o atributo features_description que será usado em breve.

Ao chamar seu objeto, a maǵica acontece. Agora, toda vez que quisermos mostrar o conteúdo de um elemento do tipo CensusDataFile, só precisaremos utilizar os parênteses.

__add__

Para finalizar, chegamos ao Grand Finale. Como os dunder methods também nos permitem sobrescrever operações matemáticas básicas, nos utilizaremos disso para transformar a operação de adição em uma forma de fazer o merge de elementos do tipo CensusDataFile. Os três arquivos baixados possuem o mesmo índice, o que nos facilita a nossa vida na hora de unir os dataframes.

O __add__ espera que um parâmetro seja passado para efetuar a operação. Como estaremos realizando operações entre objetos do tipo CensusDataFile, o resultado precisa ser um objeto do mesmo tipo, então retornamos uma instância com um merge entre os atributos df do próprio objeto e do objeto other, e o atributo features_description será o resultado de um append entre os mesmos objetos. Com isso, estamos habilitados a somar objetos do tipo CensusDataFile no Main.py para visualizar o resultado da junção.

Agora, o novo objeto CensusDataFile possui 381 colunas, resultado da junção dos três arquivos. Pela forma que o __add__ foi implementado, ele só permite somas de elementos dois a dois, mas uma alternativa seria generalizar a passagem de parâmetros utilizando o *args, o merge dos objetos e o append de features_description. Um ótimo dever de casa para os interessados no assunto.

Existem diversos outros dunder methods que não foram comentados aqui e que podem ser encontrados na parte da documentação do Python referente ao Data Model.

Quase sempre usamos o Python sem ter consciência de todo o potencial que poderíamos estar aproveitando se entendêssemos como certas coisas funcionam. Os dunder methods são exemplos disso e podem ser aliados importantes na criação de uma estrutura de código mais “Pythonica”.

Referências

--

--