Python é maravilhoso.

Surpreendentemente, esta é uma afirmação bastante ambígua. O que quero dizer por ‘Python’? Quero dizer Python a interface abstrata? Quero dizer CPython, a implementação comum do Python (a não ser confundida com o similarmente nomeado Cython)? Ou quero dizer algo totalmente diferente? Talvez eu esteja obliquamente me referindo ao Jython, ou IronPython, ou PyPy. Ou talvez eu realmente tenha chegado ao fundo do poço e esteja falando do RPython ou RubyPython (que são coisas muito, muito diferentes).

Enquanto as tecnologias mencionadas acima são comumente nomeadas e comumente referenciadas, algumas delas servem propósitos completamente diferentes (ou, pelo menos, operam de maneiras completamente diferentes).

Ao longo de meu tempo trabalhando com Python, me deparei com uma grande quantidade destas ferramentas .*ython. Mas só recentemente eu parei para entender o que elas são, como funcionam, e porque elas são necessárias (cada uma de sua própria maneira).

Neste post, vou começar do zero e passar pelas várias implementações Python, concluindo com uma introdução minuciosa ao PyPy, que eu acredito ser o futuro da linguagem.

Tudo começa com um entendimento do que ‘Python’ é na verdade.

Se você tem um bom entendimento de código de máquina, máquinas virtuais, e similares, sinta-se à vontade para pular para adiante.

“Python é interpretado ou compilado?”

Este é um ponto comum de confusão para iniciantes em Python.

A primeira coisa a perceber é que ‘Python’ é uma interface. Há uma especificação do que Python deveria fazer e como deveria se comportar (como qualquer interface). E há múltiplas implementações (como com qualquer interface).

A segunda coisa a perceber é que ‘interpretado’ e ‘compilado’ são propriedades de uma implementação, não de uma interface.

Então a questão em si não está bem formulada.

Python é interpretado ou compilado? A questão não está realmente bem formulada.

Dito isso, para a implementação mais comum (CPython: escrita em C, frequentemente referenciada simplesmente como ‘Python’, e certamente o que você está usando se você não tem ideia do que estou falando), a resposta é: interpretada, com alguma compilação. CPython compila** código fonte Python para bytecode, e depois *interpreta este bytecode, executando conforme progride.

* Nota: isto não é ‘compilação’ no sentido tradicional da palavra. Tipicamente, diríamos que ‘compilação’ é pegar uma linguagem de alto nível e convertê-la para código de máquina. Mas é uma certa ‘compilação’.

Bytecode vs. Código de Máquina

É muito importante entender a diferença entre bytecode e código de máquina (ou nativo), talvez melhor ilustrado pelo exemplo:

  • C compila para código de máquina, que é depois executado diretamente no seu processador. Cada instrução instrui sua CPU a mover coisas por toda parte.
  • Java compila para bytecode, que depois é executado na Java Virtual Machine (JVM), uma abstração de um computador que executa programas. Cada instrução é então tratada pela JVM, que interage com seu computador.

Em termos breves: código de máquina é muito mais rápido, mas bytecode é mais portável e seguro.

Código de máquina parece diferente dependendo de sua máquina, mas bytecode se parece igual em todas as máquinas. Alguém pode dizer que código de máquina é otimizado para sua configuração.

Retornando ao CPython, o processo de toolchain é como segue:

  1. CPython compila seu código fonte Python para bytecode.
  2. Este bytecode é então executado na Máquina Virtual CPython.

Iniciantes assumem que Python é compilado por causa dos arquivos .pyc. Há alguma verdade nisto: o arquivo .pyc é bytecode compilado, que é depois interpretado. Então se você rodou seu código Python antes e tem o arquivo .pyc disponível, ele vai rodar mais rápido na segunda vez, já que não precisará recompilar o bytecode.

VMs alternativas: Jython, IronPython, e Mais

Como eu mencionei mais cedo, Python tem várias implementações. Novamente, como mencionei mais cedo, a mais comum é a CPython. Esta implementação Python é escrita em C e considerada a implementação ‘padrão’.

Mas e quanto às alternativas? Uma das mais proeminentes é o Jython, a implementação Python escrita em Java que utiliza a JVM. Enquanto CPython produz bytecode para rodar na VM CPython VM, Jython produz bytecode Java para rodar na JVM (isto é a mesma coisa que é produzida quando você compila um programa Java).

“Por que você usaria uma implementação alternativa?”, você poderia perguntar. Bem, uma razão, é que estas diferentes implementações funcionam bem com diferentes stacks de tecnologia.

CPython torna muito fácil escrever extensões C para seu código Python porque no final ele é executado por um interpretador C. Jython, por outro lado, torna muito fácil trabalhar com outros programas Java: você pode importar qualquer classe Java sem esforço adicional, chamando e utilizando suas classes Java de dentro de seus programas Jython. (obs.: se você não pensou sobre isso bem de perto, isto é loucura. Nós estamos num ponto onde você pode misturar e fazer uma miscelanea de diferentes linguagens e compilar todas elas para a mesma substância. Obs.: Como mencionado por Rostin, programas que misturam código Fortran e código C tem estado por aí faz algum tempo. Então, claro, isto não é necessariamente novo. Mas ainda assim é legal.)

Como exemplo, este é código Jython válido:

[Java HotSpot(TM) 64-Bit Server VM (Apple Inc.)] on java1.6.0_51
>>> from java.util import HashSet
>>> s = HashSet(5)
>>> s.add("Foo")
>>> s.add("Bar")
>>> s
[Foo, Bar]

IronPython é outra popular implementação Python, escrita totalmente em C# e focando na stack .NET. Em particular, ela roda no que você chamaria de Máquina Virtual .NET, Common Language Runtime (CLR) da Microsoft, comparável à JVM.

Você poderia dizer que Jython : Java :: IronPython : C#. Elas rodam nas mesmas VMs respectivas, você pode importar classes C# a partir de seu código IronPython e classes Java a partir de seu código Jython, etc.

É totalmente possível sobreviver sem jamais tocar em uma implementação Python não-CPython. Mas há vantagens a se obter ao mudar, sendo a maioria dependentes de sua stack de tecnologia. Usando muitas linguagens baseadas na JVM? Jython pode ser pra você. Tudo na stack .NET? Talvez você devesse tentar o IronPython (e talvez você já tenha tentado).

A propósito: apesar de esta não ser uma razão para usar uma implementação diferente, note que estas implementações se diferem em comportamento além de como elas tratam seu código fonte Python. Entretanto, estas diferenças são tipicamente pequenas, e se dissolvem ou emergem ao longo do tempo já que estas implementações estão sob ativo desenvolvimento. Por exemplo, IronPython usa strings Unicode por padrão; CPython, entretanto, padroniza em ASCII para versões 2.x (falhando com um UnicodeEncodeError para caracteres não-ASCII), mas suporta strings Unicode por padrão para 3.x.

Compilação Just-in-Time: PyPy, e o Futuro

Então nós temos uma implementação Python escrita em C, uma em Java, e uma em C#. O próximo passo lógico: uma implementação Python escrita em… Python (o leitor não-leigo notará que isto é um tanto enganoso).

Aqui está onde as coisas podem se tornar confusas. Primeiro, vamos discutir compilação just-in-time (JIT).

JIT: O Por Que e o Como

Lembre-se que código de máquina nativo é muito mais rápido que bytecode. *Bem, e se pudéssemos compilar parte de nosso bytecode e depois rodá-lo como código nativo**? Teríamos que pagar um certo preço para compilar o bytecode (isto é, tempo), mas se o resultado final fosse mais rápido, seria ótimo! Esta é a motivação da compilação JIT, uma técnica híbrida que mistura os benefícios de interpretadores e compiladores. Em termos básicos, JIT quer utilizar compilação para acelerar um sistema interpretado.

Por exemplo, uma abordagem tomada pelos JITs:

  1. Identificar bytecode que é executado frequentemente.
  2. Compilá-lo para código de máquina nativo.
  3. Fazer cache do resultado.
  4. Sempre que o mesmo bytecode estiver para ser executado, ao invés disso usar o código de máquina pré-compilado e colher os frutos (isto é, ganhos de velocidade).

Isto é o que o PyPy faz: traz o JIT para o Python (veja o Apêndice para saber sobre esforços prévios). Existem, claro, outros objetivos: PyPy busca ser multiplataforma, leve em uso de memória, e com suporte a Stackless. Mas JIT é realmente o argumento de venda. Numa média após execução de diversos testes de tempo, é dito que melhora a performance por um fator de 6.27. Para uma quebra destes dados, veja o gráfico do PyPy Speed Center:

PyPy é Difícil de Entender

PyPy tem enorme potencial, e a esta altura é altamente compatível com CPython (portanto pode executar Flask, Django, etc.).

Mas há muita confusão ao redor de PyPy (veja, por exemplo, esta proposta sem sentido para criar um PyPyPy…). Na minha opinião, isso se deve primariamente por PyPy ser na realidade duas coisas:

  1. Um interpretador Python escrito em RPython (não Python (eu menti antes)). RPython é um subconjunto do Python com tipamento estático. Em Python, é “praticamente impossível” racionalizar rigorosamente sobre tipos (por que é tão difícil? Bem, considere o fato que:

     x = random.choice([1, "foo"])
    

    seria código Python válido (crédito para Ademan). Qual é o tipo de x? Como podemos racionalizar sobre tipos de variáveis quando os tipos não são sequer estritamente forçados?). Com RPython, você sacrifica alguma flexibilidade, mas ao invés disto torna muito, muito mais fácil racionalizar sobre gerenciamento de memória e outros, o que permite algumas otimizações.

  2. Um compilador que compila código RPython para vários alvos e adiciona JIT. A plataforma padrão é C, isto é, um compilador RPython-para-C, mas você também poderia ter JVM e outros como alvo.

Unicamente para esclarecimento, me referirei a estas como PyPy (1) e PyPy (2).

Por que você precisaria destas duas coisas, e por que sob o mesmo teto? Pense desta maneira: PyPy (1) é um interpretador escrito em RPython. Então ele pega o código Python do usuário e compila para bytecode. Mas o interpretador em si (escrito em RPython) precisa ser interpretado por outra implementação Python para que execute, certo?

Bem, nós poderíamos simplesmente usar CPython para executar o interpretador. Mas isto não seria muito rápido.

Ao invés, a ideia é que usemos PyPy (2) (referido como RPython Toolchain) para compilar o interpretador PyPy em código para outra plataforma (por exemplo, C, JVM, ou CLI) para executar em nossa máquina, adicionando JIT também. É mágico: PyPy dinamicamente adiciona JIT a um interpretador, gerando seu próprio compilador! Obs.: novamente, isto é loucura: estamos compilando um interpretador, adicionando outro compilador independente, separado.

No final, o resultado é um executável independente que interpreta código fonte Python e explora otimizações JIT. Que é tudo o que queríamos! É um bocado de coisa, mas talvez este diagrama ajude:

Para reiterar, a beleza real do PyPy é que nós mesmos poderíamos escrever um monte de interpretadores Python diferentes em RPython sem se preocupar sobre JIT (exceto algumas dicas). PyPy então implementaria JIT para nós usando o RPython Toolchain/PyPy (2).

De fato, se formos ainda mais abstratos, você poderia teoricamente escrever um interpretador para qualquer linguagem, suprí-lo ao PyPy, e receber um JIT para a dada linguagem. Isto porque o PyPy foca na otimização do interpretador em si, ao invés dos detalhes da linguagem que está interpretando.

Você poderia teoricamente escrever um interpretador para qualquer linguagem, suprí-lo ao PyPy, e receber um JIT para a dada linguagem.

Como uma breve divagação, gostaria de mencionar que o JIT em si é absolutamente fascinante. Ele usa uma técnica chamada tracing, que executa como segue:

  1. Executar o interpretador e interpretar tudo (sem adicionar qualquer JIT).
  2. Fazer um profiling leve do código interpretado.
  3. Identificar operações que você executou antes.
  4. Compilar estes bits de código para código de máquina.

Para mais detalhes, este artigo é bem acessível e muito interessante.

Para finalizar: usamos o compilador RPython-para-C do PyPy (ou outra plataforma alvo) para compilar o interpretador do PyPy implementado em RPython.

Finalizando

Por que isto é tão grandioso? Por que esta ideia louca vale a pena ser perseguida? Eu acho que Alex Gaynor definiu bem em seu blog: “[PyPy é o futuro] porque [ele] oferece melhor velocidade, mais flexibilidade, e é a melhor plataforma para o crescimento do Python.”

Em resumo:

  • É rápido porque compila código fonte para código nativo (usando JIT).
  • É flexível porque adiciona JIT ao seu interpretador com muito pouco trabalho adicional.
  • É flexível (novamente) porque você pode escrever seus próprios interpretadores em RPython, que é mais fácil de extender do que, digamos, C (de fato, é tão fácil que há um tutorial para escrever seus próprios interpretadores).

Apêndice: Outros Nomes Que Você Pode Ter Ouvido Falar

  • Python 3000 (Py3k): uma nomenclatura alternativa para Python 3.0, um lançamento destaque do Python, incompatível com versões anteriores que entrou no palco em 2008. O time do Py3k previu que levaria cinco anos para esta nova versão ser totalmente adotada. E apesar da maioria (alerta: reivindicação anedótica) dos desenvolvedores Python continuar a usar Python 2.x, as pessoas estão cada vez mais conscientes do Py3k.

  • Cython: um superconjunto do Python que inclui bindings para chamar funções C.
    • Objetivo: permitir que você escreva extensões C para seu código Python.
    • Também permite que você adicione tipagem estática a seu código Python existente, permitindo que seja compilado e alcance performance parecida com a de C.
    • Isto é similar ao PyPy, mas não o mesmo. Neste caso, você está forçando a tipagem no código do usuário antes de passá-lo para um compilador. Com PyPy, você escreve código Python antigo/tradicional, e o compilador trata quaisquer otimizações.

  • Numba: um “compilador especializado em just-in-time” que adiciona JIT a código Python anotado. Em termos básicos, você dá algumas dicas, e ele acelera algumas porções de seu código. Numba vem como parte da distribuição Anaconda, um conjunto de pacotes para análise e gerenciamento de dados.

  • IPython: bem diferente de qualquer outra coisa discutida. Um ambiente computacional para Python. Interativo e com suporte para toolkits GUI toolkits e aproveitamento no navegador, etc.

  • Psyco: um módulo de extensão do Python, e um dos primeiros esforços de JIT para Python. Por isso, foi marcado como “não mantido e morto”. De fato, o desenvolvedor líder da Psyco, Armin Rigo, agora trabalha no PyPy.

Bindings de Linguagem

  • RubyPython: uma ponte entre VMs Ruby e Python. Permite embutir código Python em seu código Ruby. Você define onde o Python começa e para, e o RubyPython regula os dados entre as VMs.

  • PyObjc: binding de linguagem entre Python e Objective-C, agindo como uma ponte entre elas. Isto praticamente significa que você pode utilizar bibliotecas Objective-C (incluindo tudo que você precisa para criar aplicações OS X) a partir de seu código Python, e módulos Python a partir de seu código Objective-C. Neste caso, é conveniente que CPython seja escrito em C, que é um subconjunto do Objective-C.

  • PyQt: enquanto PyObjc lhe dá binding para os componentes da GUI OS X, o PyQt faz o mesmo para o framework de aplicações Qt, deixando você criar interfaces gráficas ricas, acessar bancos de dados SQL, etc. Outra ferramenta mirando em trazer a simplicidade do Python a outros frameworks.

Frameworks JavaScript

  • pyjs (Pyjamas): um framework para criação de aplicações web e desktop em Python. Inclui um compilador Python-para-JavaScript, um conjunto de widgets, e mais algumas ferramentas.

  • Brython: uma VM Python escrita em JavaScript para permitir que código Py3k seja executado no navegador.


Conteúdo traduzido por Eduardo Kienetz, membro do TransBrunko, um mercado para traduções técnicas.

Hiring? Meet the Top 10 Freelance Python Developers for Hire in December 2016
Don't miss out.
Get the latest updates first.
No spam. Just great engineering and design posts.
Don't miss out.
Get the latest updates first.
Thank you for subscribing!
You can edit your subscription preferences here.

Comments

Fabiano Góes
muito bom post, parabéns cara!
Vanderson Ramos
Parabéns, muito bem explicado.
Bruno Marinho
se voce usar a JVM voce terá o JIT mais performatico que vem sendo evoluido a 15 anos.
Luís Eduardo
Post bem explicado. Apesar de ainda ter ficado com algumas dúvidas sobre o PyPy.
Vitório
Só me tira uma dúvida: os gráficos que têm escrito neles Toptal foram gerados em algum lugar específico? As imagens estão tão bonitas. Só curioso. ;-) Abraço!
Allan Lucio Correia
Muito bom post, Parabéns!!
wagner
Amigo, qual programa usou para gerar esses gráficos?
comments powered by Disqus