¿Se puede compilar python?

¡Hola! Mucha gente se pregunta: ¿Se puede compilar python? La respuesta es no, y diréis, ¿Para que haces un articulo si no se puede, lo explico matizando por qué digo que no se puede:

¿Se puede compilar python?

Realmente python es un lenguaje interpretado por lo que no se puede compilar, pero hay varias utilizades que nos pueden generar un ejecutable a partir de nuestro código python, ya sea standalone o dependiente de librerías:

1- La primera forma de compilar python: cython

La primera forma de crear un ejecutable a partir de nuestro script python es con la utilidad Cython, la cual convierte el código a lenguaje C para posteriormente compilarlo con un compilador C como puede ser GCC:

Script “test.py”

def testPython():
   toprint = "Python mola"
   print(toprint)

if __name__=="__main__":
   testPython()

Para hacer lo siguiente es necesario instalar cython, se puede instalar con easy_install o con pip:

root@host:~# pip install cython
root@host:~# 

Después transformamos el codigo python en C y compilamos con GCC:

root@host:~# cython --embed -o test.c test.py
root@host:~# gcc -I/usr/include/python2.7 -lpython2.7 -o test test.c
root@host:~# 

Como se puede ver se crea el ejecutable y se comporta exactamente igual que el script:

root@host:~# ./test 
Python mola
root@host:~# python test.py 
Python mola

Como se puede ver, se ha generado un fichero ejecutable elf de linux (exe en windows), depende de las librerías de python (y como en cualquier binario hay que tener cuidado de lo que se deja en los strings ya que con el comando strings se puede obtener texto en claro):

root@host:~# file test
test: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 2.6.32, BuildID[sha1]=e54070acae5bb3d0c751b495919680933e43955f, not stripped
root@host:~# ldd test
	linux-vdso.so.1 (0x00007ffea39cc000)
	libpython2.7.so.1.0 => /usr/lib/x86_64-linux-gnu/libpython2.7.so.1.0 (0x00007fdf70a44000)
	libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007fdf70699000)
	libz.so.1 => /lib/x86_64-linux-gnu/libz.so.1 (0x00007fdf7047e000)
	libpthread.so.0 => /lib/x86_64-linux-gnu/libpthread.so.0 (0x00007fdf70261000)
	libdl.so.2 => /lib/x86_64-linux-gnu/libdl.so.2 (0x00007fdf7005d000)
	libutil.so.1 => /lib/x86_64-linux-gnu/libutil.so.1 (0x00007fdf6fe5a000)
	libm.so.6 => /lib/x86_64-linux-gnu/libm.so.6 (0x00007fdf6fb59000)
	/lib64/ld-linux-x86-64.so.2 (0x00007fdf70fc9000)
root@host:~# strings test|grep "Python"
testPython
Python mola
testPython
test.testPython
 while calling a Python object
__pyx_k_testPython
__pyx_k_Python_mola
__pyx_kp_s_Python_mola
__pyx_n_s_testPython
__pyx_mdef_4test_1testPython
__pyx_pw_4test_1testPython
__pyx_pf_4test_testPython

2- Segundo camino para compilar python: pyinstaller

También podemos crear un ejecutable con pyinstaller, aunque de esta forma realmente empaquetamos el script python y sus librerías dentro de un binario, de esta forma no se requiere ni python ni sus librerías instaladas en el sistema que se ejecute el binario generado:

Lo primero es instalar pyinstaller, se puede hacer con pip o con easy_install

root@host:~# pip install pyinstaller
root@host:~# 

Después generamos el binario con:

root@host:~# pyinstaller -F test.py 
23 INFO: PyInstaller: 3.2.1
23 INFO: Python: 2.7.9
24 INFO: Platform: Linux-3.16.0-4-amd64-x86_64-with-debian-8.10
24 INFO: wrote /tmp/test.spec
27 INFO: UPX is not available.
28 INFO: Extending PYTHONPATH with paths
['/tmp', '/tmp']
28 INFO: checking Analysis
28 INFO: Building Analysis because out00-Analysis.toc is non existent
28 INFO: Initializing module dependency graph...
29 INFO: Initializing module graph hooks...
61 INFO: running Analysis out00-Analysis.toc
74 INFO: Caching module hooks...
76 INFO: Analyzing /tmp/test.py
77 INFO: Loading module hooks...
77 INFO: Loading module hook "hook-encodings.py"...
1121 INFO: Looking for ctypes DLLs
1122 INFO: Analyzing run-time hooks ...
1125 INFO: Looking for dynamic libraries
1231 INFO: Looking for eggs
1231 INFO: Python library not in binary depedencies. Doing additional searching...
1260 INFO: Using Python library /usr/lib/x86_64-linux-gnu/libpython2.7.so.1.0
1262 INFO: Warnings written to /tmp/build/test/warntest.txt
1290 INFO: checking PYZ
1290 INFO: Building PYZ because out00-PYZ.toc is non existent
1290 INFO: Building PYZ (ZlibArchive) /tmp/build/test/out00-PYZ.pyz
1440 INFO: Building PYZ (ZlibArchive) /tmp/build/test/out00-PYZ.pyz completed successfully.
1465 INFO: checking PKG
1465 INFO: Building PKG because out00-PKG.toc is non existent
1465 INFO: Building PKG (CArchive) out00-PKG.pkg
3070 INFO: Building PKG (CArchive) out00-PKG.pkg completed successfully.
3074 INFO: Bootloader /usr/local/lib/python2.7/dist-packages/PyInstaller/bootloader/Linux-64bit/run
3074 INFO: checking EXE
3074 INFO: Building EXE because out00-EXE.toc is non existent
3075 INFO: Building EXE from out00-EXE.toc
3075 INFO: Appending archive to ELF section in EXE /tmp/dist/test
3090 INFO: Building EXE from out00-EXE.toc completed successfully.

El fichero binario se ha generado en el directorio “dist/” y como se puede ver a continuación se comporta igual que el script como el otro binario que hemos generado antes:

root@host:~# dist/test 
Python mola

Como se puede ver, también se ha generado un fichero binario ELF (en windows un EXE) ,tiene menos dependencias de librerias y no aparece nada de lo escrito en los strings cuando ejecutamos el comando “strings”:

root@host:~# file dist/test 
dist/test: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 2.6.32, BuildID[sha1]=373ec5dee826653796e927ac3d65c9a8ec7db9da, stripped
root@host:~# ldd dist/test 
	linux-vdso.so.1 (0x00007ffc22787000)
	libdl.so.2 => /lib/x86_64-linux-gnu/libdl.so.2 (0x00007fc7ada4b000)
	libz.so.1 => /lib/x86_64-linux-gnu/libz.so.1 (0x00007fc7ad830000)
	libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007fc7ad485000)
	/lib64/ld-linux-x86-64.so.2 (0x00007fc7adc4f000)
root@host:~# strings dist/test |grep Python
Py_SetPythonHome
Cannot dlsym for Py_SetPythonHome
Error loading Python lib '%s': %s
Error detected starting Python VM.

Otra cosa a tener en cuenta es que el tamaño del fichero ejecutable será mayor con esta utilidad ya que se ha generado un fichero standalone y para eso se han incluido las librerias necesarias en el código:

root@host:~# ls -alh test
-rwxr-xr-x 1 root root 48K feb 15 01:24 test
root@host:~# ls -alh dist/test
-rwxr-xr-x 1 root root 3,6M feb 15 01:35 dist/test

Deja una respuesta