¬ŅPor que hay tantos Pythons?

Python es asombroso.

Sorprendentemente, esa es una declaraci√≥n bastante ambigua. ¬ŅA qu√© me refiero con ‚ÄėPython‚Äô?, ¬ŅMe refiero a la interfaz abstracta de Python?, ¬ŅMe refiero a CPython, la implementaci√≥n com√ļn de Python (y no confundir con Cython, que son similares en sus nombres)?, ¬ŅO me refiero a algo completamente distinto? Tal vez me est√© refiriendo indirectamente a Jython, o IronPython, o PyPy. O tal vez me he ido al extremo y estoy hablando de RPython o RubyPython (los cuales son cosas muy, muy distintas).

Mientras las tecnologías mencionadas anteriormente son llamadas de formas parecidas y referenciadas de la misma manera, algunas de ellas sirven para propósitos completamente distintos (o, al menos, operan de maneras completamente distintas).

A lo largo de mi tiempo trabajando con Python, me topé con toneladas de estas herramientas .*ython. Pero no hasta hace poco me tomé el tiempo de entender qué es lo que son, cómo funcionan y por qué son necesarias (a sus maneras).

En este artículo, voy a empezar desde cero y recorreré varias implementaciones de Python, concluyendo con una introducción detallada a PyPy, el cual creo es el futuro del lenguaje.

Todo empieza con entender que es lo que ‚ÄėPython‚Äô realmente es.

Si tienes un buen entendimiento sobre código binario, máquinas virtuales y parecidos, siéntete libre de saltarte esta parte.

‚ÄúPython es interpretado o compilado?‚ÄĚ

Este es un punto com√ļn de confusi√≥n para principiantes en Python.

La primera cosa que hay que saber es que ‚ÄėPython‚Äô es una interfaz. Existe una especificaci√≥n sobre lo que Python deber√≠a hacer y c√≥mo deber√≠a comportarse (c√≥mo con cualquier interfaz). Y hay m√ļltiples implementaciones (como en cualquier interfaz).

Lo segundo que hay que saber es que ‚Äėinterpretado‚Äô y ‚Äėcompilado‚Äô son propiedades de una implementaci√≥n, no de una interfaz.

Entonces, la pregunta no est√° realmente bien formada.

¬ŅPython es interpretado o compilado? La pregunta no est√° realmente bien formada.

Dicho esto, para la implementaci√≥n m√°s com√ļn (CPython: escrito en C, usualmente llamado simplemente ‚ÄėPython‚Äô, y seguramente lo que est√°s usando si no tienes idea de lo que estoy hablando), la respuesta es: interpretado, con algunas partes compiladas. CPython compila** el c√≥digo fuente de Python a *bytecode, y en ese momento interpreta ese bytecode, ejecut√°ndolo sobre la marcha.

* Nota: no es una ‚Äėcompilaci√≥n‚Äô en sentido tradicional de la palabra. Normalmente, decimos que ‚Äėcompilar‚Äô es tomar el c√≥digo de alto nivel y convertirlo en c√≥digo binario. Pero es un tipo de ‚Äėcompilaci√≥n‚Äô.

Veamos la respuesta un poco más de cerca, ya que nos permitirá entender algunos de los conceptos que surgirán más adelante en el artículo.

Bytecode vs. código binario

Es muy importante entender la diferencia entre bytecode y código binario (o nativo), tal vez mejor ilustrada con ejemplos:

  • C compila a c√≥digo binario, que luego es ejecutado directamente en tu procesador. Cada instrucci√≥n le indica a tu CPU que mueva cosas alrededor.
  • Java compila a bytecode, que luego es ejecutado en la m√°quina virtual de Java(Java Virtual Machine, √≥ JVM), una abstracci√≥n de una computadora que ejecuta programas. Cada instrucci√≥n es entonces manejada por la JVM, que interact√ļa con tu computadora.

En términos breves: código binario es más rápido, pero bytecode es más portable y seguro.

El código binario se ve distinto, dependiendo de tu máquina, pero bytecode se ve igual en todas las maquinas. Se podría decir que el código binario está optimizado para tu configuracion.

Volviendo a CPython, el proceso en el conjunto de herramientas sucede de la siguiente manera:

  1. CPython compila tu código Python a bytecode
  2. Ese bytecode es entonces ejecutado en la M√°quina Virtual CPython

Los principiantes asumen que Python es compilado a raíz de los archivos .pyc. Hay alguna verdad en esto: el archivo .pyc es bytecode compilado, que es después interpretado. Entonces si haz ejecutado código Python y ya tienes un archivo .pyc disponible, el mismo va a ejecutarse más rápido la segunda vez ya que no necesitará recompilar el bytecode.

Maquinas virtuales alternativas: Jython, IronPython, y m√°s

C√≥mo mencion√© anteriormente, Python tiene varias implementaciones. De vuelta, como mencion√© antes, la m√°s com√ļn es CPython. √Čsta es una implementaci√≥n de Python escrita en C y es considerada la implementaci√≥n ‚Äėpor defecto‚Äô.

¬ŅPero, qu√© pasa con las alternativas? Una de las m√°s prominentes es Jython, una implementaci√≥n en Java que utiliza la JVM. Mientras CPython produce bytecode para ser corrido en la VM de CPython, Jython produce bytecode de Java para correr en la JVM (esto es lo mismo que es producido cuando se compila un programa en Java).

‚Äú¬ŅPor qu√© usar√≠a alguna vez una implementaci√≥n alternativa?‚ÄĚ, podr√≠as preguntar. Bueno, para empezar, esas diferentes implementaciones juegan muy bien con diferentes conjuntos de tecnolog√≠as.

CPython hace muy fácil el escribir extensiones C para tu código Python porque al final es ejecutado por un intérprete de C. Por otro lado, Jython, facilita trabajar con otros programas en Java: puedes importar cualquier clase de Java sin mayor esfuerzo, evocando y utilizando tus clases Java dentro tus programas Jython. (Nota aparte: si no pensaste en esto detalladamente, es una locura. Estamos en un punto donde puedes mezclar y triturar diferentes lenguajes y compilarlos todos en una misma esencia. Como fue mencionado por Rostin, los programas que mezclan código Fortran y C están desde hace un tiempo. Así que, por supuesto que esto no es algo necesariamente nuevo. Pero sigue siendo genial.)

Cómo ejemplo, esto es 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 es otra implementación popular de Python, escrita enteramente en C# y apuntando a la tecnología .NET. En particular, corre con lo que se podría llamar la Máquina Virtual .NET,Common Language Runtime (CLR)de Microsoft, comparable con la JVM.

Podrías decir que Jython : Java :: IronPython : C#. Corren en sus respectivas VMs, puedes importar clases C# en tu código IronPython y clases Java desde tu código Jython, etc.

Es totalmente posible sobrevivir sin tocar alguna vez una implementaci√≥n de Python no-CPython. Pero hay ventajas que se obtienen desde el cambio, muchas de ellas son dependientes de la tecnolog√≠a que uses. ¬ŅUsas muchos lenguajes basados en la JVM? Jython puede ser para t√≠. ¬ŅTodo lo que haces es sobre la tecnolog√≠a .NET? Tal vez debas probar IronPython (y tal vez ya lo hayas hecho).

Por cierto: mientras que esto no ser√≠a una raz√≥n para usar una implementaci√≥n diferente, nota que estas implementaciones s√≠ difieren en comportamiento m√°s all√° de como tratan tu c√≥digo fuente en Python. Sin embargo, esas diferencias son com√ļnmente menores, y se disuelven o emergen con el tiempo mientras estas implementaciones se encuentran bajo un activo desarrollo. Por ejemplo, IronPython usa cadenas Unicode por defecto; Sin embargo, CPython, por defecto usa ASCII para versiones 2.x (fallando con un error de codificaci√≥n UnicodeEncodeError para caracteres no-ASCII), pero s√≠ soporta cadenas Unicode por defecto para las versiones 3.x.

Compilación Justo-a-Tiempo: PyPy y el futuro

Por lo tanto, tenemos una implementaci√≥n de Python escrita en C, una en Java una en C#. El pr√≥ximo paso l√≥gico: una implementaci√≥n de Python escrita en‚Ķ Python. (El lector educado encontrar√° esta notaci√≥n levemente enga√Īosa).

Aquí es donde las cosas se ponen confusas. Primero, discutamos sobre compilación Justo-a-Tiempo (Just-in-Time, ó JIT).

JIT: El por qué y el cómo

Recordemos que el c√≥digo binario es mucho m√°s r√°pido que bytecode. Bueno, ¬Ņy si pudi√©ramos compilar algunas partes de nuestro bytecode y luego correrlo como c√≥digo nativo? Tendr√≠amos que pagar alg√ļn precio al compilar a bytecode (por ej., tiempo), pero si el resultado fuese m√°s r√°pido, eso ser√≠a genial! Esa es la motivaci√≥n de la compilaci√≥n JIT, una t√©cnica h√≠brida que mezcla los beneficios de los interpretadores y los compiladores. En t√©rminos b√°sicos, JIT quiere utilizar compilaci√≥n para acelerar un sistema interpretado.

Por ejemplo, un enfoque com√ļn tomado por la compilaci√≥n JIT:

  1. Identificar bytecode que es ejecutado frecuentemente.
  2. Compilar a código binario.
  3. Almacenar el resultado en memoria caché.
  4. Siempre que el mismo bytecode sea encontrado para ejecutar, en vez de usarlo, ejecutar el código binario precompilado y cosechar los beneficios (por ej., aumentos de velocidad)

De esto se trata PyPy: llevar JIT a Python (mira el Ap√©ndice para ver esfuerzos anteriores). Hay, por supuesto, otros objetivos: PyPy apunta a ser multiplataforma, bajo en consumo de memoria e independiente del conjunto de tecnolog√≠as. Pero JIT realmente se vende por si solo. Como promedio de un pu√Īado de pruebas de tiempo, se dice que mejora el rendimiento a un factor de 6.27. Para un mayor an√°lisis, v√©ase este cuadro del PyPy Speed Center:

Like what you’re reading?
Get the latest updates first.
No spam. Just great engineering posts.
Like what you’re reading?
Get the latest updates first.
Thank you for subscribing!
You can edit your subscription preferences here.

PyPy es difícil de entender

PyPy tiene un gran potencial, y a estas alturas es muy compatible con CPython (así que puede correr Flask, Django, etc.).

Pero hay mucha confusión alrededor de PyPy (véase, por ejemplo, esta propuesta sin sentido para crear un PyPyPy…). En mi opinión, eso es principalmente porque PyPy es actualmente dos cosas:

  1. Un int√©rprete de Python escrito en RPython (no Python (he mentido antes). RPython es un subconjunto de Python con tipos est√°ticos. En Python, es ‚Äúpr√°cticamente imposible‚ÄĚ razonar rigurosamente acerca de tipos (¬ŅPor que es tan dif√≠cil? Bueno, considera el hecho que:
     x = random.choice([1, "foo"])
    

    ser√≠a c√≥digo Python v√°lido (cr√©ditos a Ademan). ¬ŅDe qu√© tipo es x? ¬ŅC√≥mo podemos razonar acerca de tipos de variables cuando los tipos ni siquiera son estrictamente forzados?). Con RPython, sacrificas algo de flexibilidad, pero a cambio es much√≠simo m√°s f√°cil razonar sobre manejo de memoria y dem√°s, lo cual permite optimizaciones.

  2. Un compilador que compila código RPython para varios objetivos y agrega JIT. La plataforma por defecto es C, por ej., un compilador RPython-a-C, pero también puedes apuntar a JVM y otros.

Únicamente para mayor claridad, me referiré a ellos como PyPy (1) y PyPy (2).

¬ŅPor qu√© necesitar√≠as esas dos cosas, y por qu√© bajo el mismo techo? Pi√©nsalo de esta manera: PyPy(1) es un int√©rprete escrito en RPython. Entonces toma el c√≥digo Python del usuario y lo compila a bytecode. Pero el interpretador en s√≠ (escrito en RPython) tiene que ser interpretado por otra implementaci√≥n de Python para poder correr, ¬ŅVerdad?

Bueno, podríamos simplemente usar CPython para correr el intérprete. Pero eso no sería lo suficientemente rápido.

En cambio, la idea es que usemos PyPy(2) (también conocido cómo RPython Toolchain)-Set de herramientas RPython) para compilar al interpretador de PyPy a código que otra plataforma (por ej., C, JVM o CLI) pueda correr en nuestra máquina, agregando también JIT. Es mágico: PyPy agrega dinámicamente JIT a un interpretador, generando su propio compilador! (De vuelta, esto es una locura: estamos compilando un interpretador y agregando otro compilador independiente por separado).

Al final, el resultado es un ejecutable independiente que interpreta el código fuente Python y explota las optimizaciones de JIT. Que es lo que justamente queríamos! Es un gran bocado, pero tal vez este diagrama ayude:

Reiterando, la verdadera belleza de PyPy es que podemos escribir nosotros mismos un pu√Īado de interpretadores Python distintos en RPython sin preocuparnos por JIT (salvo algunas sugerencias). PyPy entonces implementar√≠a JIT por nosotros usando el set de herramientas de RPython/PyPy(2).

De hecho, si nos ponemos a√ļn m√°s abstractos, podr√≠as, te√≥ricamente, escribir un interpretador para cualquier lenguaje, alimentar a PyPy con √©l, y obtener un JIT para ese lenguaje. Esto es porque PyPy se enfoca en optimizar el interpretador actual, en vez de los detalles del lenguaje que est√° interpretando.

Podrías, teóricamente, escribir un interpretador para *cualquier* lenguaje, alimentar a PyPy con él, y obtener un JIT para ese lenguaje.

Divagando un poco, me gustaría mencionar que JIT en sí mismo es absolutamente fascinante. Usa una técnica llamada tracing (ó seguimiento), la cual se ejecuta de la siguiente manera:

  1. Correr el interpretador e interpretar todo (sin agregar nada de JIT)
  2. Perfilar levemente el código interpretado.
  3. Identificar operaciones que hayas realizado antes.
  4. Compilar esos pedazos a código binario.

Para más información, este documento es altamente accesible y muy interesante.

Para ir concluyendo: usamos el compilador RPython-a-C de PyPy (u otra plataforma) para compilar el interpretador implementado RPython de PyPy.

Concluyendo

¬ŅPor qu√© es tan genial? ¬ŅPor qu√© vale la pena perseguir esta idea tan loca? Creo que Alex Gaynor lo describi√≥ muy bien en su blog: ‚Äú[PyPy es el futuro] porque ofrece mejor velocidad, m√°s flexibilidad y es una mejor plataforma para el crecimiento de Python.‚ÄĚ

En resumen:

Apéndice: Otros nombres que tal vez hayas oído

  • Python 3000 (Py3k): nombre alternativo para Python 3.0, un mayor, compatible-con-versiones-anteriores lanzamiento de Python que alcanz√≥ la escena en 2008. El equipo de Py3k predijo que llevar√≠a alrededor de cinco anos para que esta versi√≥n sea completamente adoptada. Y mientras que la mayor√≠a (cuidado: se dice que es anecd√≥tico) de los desarrolladores de Python siguen usando Python 2.x, la conciencia de Py3k entre la gente est√° increment√°ndose.
  • Cython: un super set de Python que incluye bindings (√≥ enlaces)para llamar funciones de C.
    • Objetivo: permitirte escribir extensiones en C para tu c√≥digo Python
    • Adem√°s te permite agregar tipos de variables est√°ticos a tu c√≥digo Python, permiti√©ndole que sea compilado y alcanzar rendimiento parecido al de C.
    • Es similar a PyPy, pero no es lo mismo. En este caso, est√°s forzado a escribir el c√≥digo del usuario antes de pasarlo al compilador. Con PyPy, escribes simplemente c√≥digo Python, y el compilador maneja cualquier optimizaci√≥n.
  • Numba: : un ‚Äúcompilador especializado justo-a-tiempo‚ÄĚ que agrega JIT a c√≥digo Python anotado. En t√©rminos m√°s b√°sicos, le das algunas indicaciones y acelera partes de tu c√≥digo. Numa viene como parte de la distribuci√≥n Anaconda,un set de paquetes para manejo y an√°lisis de datos.
  • IPython: muy diferente a todo lo que hemos discutido hasta ahora. Es un ambiente de procesamiento para Python. Interactivo y con soporte para herramientas gr√°ficas y experiencia de navegador, etc.

Enlaces de lenguaje

  • RubyPython: un puente entre las m√°quinas virtuales de Ruby y Python. Permite embeber c√≥digo de Python dentro de tu c√≥digo de Ruby. Defines donde Python comienza y termina, y RubyPython calcula los datos entre las VMs.
  • PyObjc: enlaces de lenguaje entre Python y Objetive-C, actuando como un puente entre ellos. Pr√°cticamente, eso significa que puedes utilizar librer√≠as de Objective-C (incluyendo todo lo que necesitas para crear aplicaciones de OS X) desde tu c√≥digo Python, y m√≥dulos de Python desde tu c√≥digo Objective-C. En este caso, es conveniente que CPython est√© escrito en C, el cual es un subconjunto de Objective-C.
  • PyQt: mientras PyObjc te ofrece una interfaz para los componentes gr√°ficos de OS X, PyQt hace lo mismo para el framework de QT, permitiendote crear completas interfaces gr√°ficas, acceso a bases de datos SQL, etc. Otra herramienta dirigida a traer la simplicidad de Python a otros frameworks.

Frameworks JavaScript

  • pyjs (Pyjamas): un framework para crear aplicaciones web y de escritorio en Python. Incluye un compilador Python-a-Javascript, un conjunto de widgets, y algunas herramientas m√°s.
  • Brython: una m√°quina virtual de Python escrita en JavaScript para permitir que el c√≥digo de Py3k sea ejecutado en navegadores.

Contenido traducido por Pablo Fabregat, miembro de TransBunko, un mercado de traducciones técnicas.

Contenido extraído de:  https://www.toptal.com/python/por-que-hay-tantos-pythons/es
Autor: Charles Marsh – Head of Community @ Toptal (translated by Pablo Fabregat)