Programación

Python: Virtualenvs y Virtualenvwrapper

Nekmo

Mayo 31, 2016

pip virtualenvwrapper python virtualenv workon

Hablar de los Virtualenvs es hablar de una de las herramientas más útiles y necesarias en un usuario de Python.

Muchos, cuando leen el término "virtual", suelen asociarlo a pérdidas de rendimiento o mayor consumo de recursos, como el que podríamos encontrar en una máquina Java. Es por ello, que tal vez muchos se asusten de Virtualenv cuando leen el término "virtual", cuando en realidad no hay razón para ello.

Cuando se usa Virtualenv, no existe una virtualización de ningún tipo. En resumidas cuentas, lo único que hacemos es decirle a Python "carga los módulos de /home/nekmo/.virtualenvs/nekmo.com/lib/python3.5/site-packages/ en vez de /usr/lib/python3.5/site-packages/". Nada más. Esto lo que nos permite es tener en nuestro Virtualenv paquetes distintos a los que tenemos en el sistema. ¿Y para qué nos sirve esto?

La utilidad de Virtualenv

En los virtualenvs podremos instalar paquetes independientes a los de nuestro sistema. Esto tiene diversas utilidades:

  • Realizar pruebas con nuevos paquetes sin necesidad de comprometer los paquetes del sistema.
  • Crear un entorno con paquetes aislados de los del sistema para un proyecto o programa.
  • Aislar las dependencias de nuestros proyectos de las del sistema, para poder importarlas y exportarlas en otros entornos.

La primera se explica sola. ¿Y la segunda? Ésta es sobre todo útil para administradores y usuarios de programas Python: si uso un programa en Python crítico o que tengo miedo que pueda romperse al actualizar el sistema, puedo crear un Virtualenv con los paquetes que requiere dicho programa (encontrándose estos aislados de los del sistema). También sirve a la inversa: si el sistema tiene dependencias con paquetes de versiones de Python antiguas y el programa requiere de paquetes más actuales, puede crearse un virtualenv con las nuevas versiones.

La tercera es similar a la segunda, pero orientada a programadores. Un problema común de cualquier proyecto, son las dependencias y sus versiones. En ocasiones, puede olvidarse apuntar una dependencia en un proyecto, o puede que éste sólo funcione a partir de cierta versión de una dependencia, y uno no se dé cuenta. Con los virtualenvs este problema se solventa: si se instalan las dependencias en el Virtualenv, y se genera el listado de dependencias (con sus versiones), es posible utilizar ese mismo listado en otro sistema para replicar el mismo virtualenv, y conseguir que todo funcione exactamente igual.

Pero ahora pasemos de la teoría, y pongámonos a la práctica.

Virtualenvwrapper

Trabajar con virtualenvs con las herramientas de serie es tedioso. Es por ello, que recomiendo utilizar algo como Virtualenvwrapper, que reduce todos los comandos que normalmente tendrían que utilizarse para crear y trabajar con virtualenvs, a uno solo.

Lo primero es instalar Virtualenvwrapper, bien con el gestor de paquetes del sistema, bien con Pip.

Debian/Ubuntu: sudo apt-get install virtualenvwrapper
Arch Linux: sudo pacman -S python-virtualenvwrapper
Fedora: sudo dnf install python-virtualenvwrapper
En cualquier otro: sudo pip install virtualenvwrapper

 

Ahora debemos poner en nuestro .bashrc lo siguiente:

~/.bashrc
---------
export WORKON_HOME=$HOME/.virtualenvs
export PROJECT_HOME=$HOME/Projects
source `which virtualenvwrapper.sh`

Aquí tenemos 2 paths importantes: uno es WORKON_HOME, que es donde se alojarán los virtualenvs con los módulos de Python que usemos. Nosotros, normalmente no tocaremos aquí, por lo que tiene sentido que sea un directorio oculto. El otro, PROJECT_HOME, es donde se encontrarán los proyectos que hagamos (en caso de que vayamos a crear proyectos). Entraré en detalles sobre esto más adelante.

El directorio ~/.virtualenvs lo creará el propio virtualenvwrapper, pero el directorio ~/Projects (el del PROJECT_HOME) no.

~$ mkdir ~/Projects

 

Ahora debemos recargar nuestro shell (también podemos salir y volver a entrar).

~$ source ~/.bashrc

 

Tras esto ya tendremos todo listo. Ahora crearemos un primer virtualenv:

[nekmo@homura ~]$ mkvirtualenv test
Using base prefix '/usr'
New python executable in test/bin/python3
Also creating executable in test/bin/python
Installing setuptools, pip, wheel...done.
virtualenvwrapper.user_scripts creating /home/nekmo/.virtualenvs/test/bin/predeactivate
virtualenvwrapper.user_scripts creating /home/nekmo/.virtualenvs/test/bin/postdeactivate
virtualenvwrapper.user_scripts creating /home/nekmo/.virtualenvs/test/bin/preactivate
virtualenvwrapper.user_scripts creating /home/nekmo/.virtualenvs/test/bin/postactivate
virtualenvwrapper.user_scripts creating /home/nekmo/.virtualenvs/test/bin/get_env_details
(test)[nekmo@homura ~]$

Nada más entrar en un virtualenv, puede verse que antes del prompt, se encuentra el nombre del virtualenv entre paréntesis. Esto significa que nuestra instalación de Python está aislada de la del sistema, y no contaremos con los paquetes que tuviésemos. No sólo esto: cualquier programa Python que ejecutemos mientras estamos en modo virtualenv, se ejecutará en el virtualenv.

 

Uno de los paquetes que se instala de serie en los virtualenvs, es pip. Lo usaremos para instalar requests:

(test)[nekmo@homura ~]$ pip install requests
...
Successfully installed requests-2.10.0

 

Si se entra en un intérprete de Python, podrá verse que el módulo de requests que importamos, está en el virtualenv.

(test)[nekmo@homura ~]$ python
...
>>> import requests
>>> requests.__file__
'/home/nekmo/.virtualenvs/test/lib/python3.5/site-packages/requests/__init__.py'

 

Ahora salimos del virtualenv:

(test)[nekmo@homura ~]$ deactivate

Si quisiésemos, podríamos volver a entrar:

[nekmo@homura ~]$ workon test

 

Los proyectos son una característica específica de Virtualenvwrapper que nos facilita un poco el trabajo en nuestros proyectos Python. Funcionan igual que los virtualenvs que hemos creado con mkvirtualenv con la diferencia de que al usar su comando, mkproject <proyecto>, se creará un directorio ~/Projects/<proyecto> y accederemos a él automáticamente cuando usemos workon. Como vemos no es una gran diferencia, pero nos evitará tener que ir hasta el directorio cada vez.

[nekmo@homura ~]$ mkproject miproyecto
...
Creating /home/nekmo/Projects/miproyecto
Setting project for miproyecto to /home/nekmo/Projects/miproyecto
(miproyecto)[nekmo@coco-laptop ~/Projects/miproyecto]$ 

 

Chuletario Virtualenvwrapper

Fuera del virtualenv

Comando Descripción
mkvirtualenv <venv> Crear un virtualenv
mkproject <proj> Crea un directorio de proyecto con su correspondiente virtualenv
mktmpenv Crea un virtualenv sin nombre y temporal, que al hacer deactivate se autodestruye.
rmvirtualenv <venv> Borrar un virtualenv. En el caso de proyectos, no borra el dir. de proyecto.
allvirtualenv <command> Ejecutar un comando en todos los venv. Útil para actualizar pip.

 

Dentro del virtualenv

Comando Descripción
workon <venv> Entrar en un virtualenv
deactivate Salir del virtualenv actual
cdvirtualenv Ir al directorio ~/.virtualenvs/<venv>
cdsitepackages Ir al directorio ~/.virtualenvs/<venv>/lib/PythonX.Y/site-packages
cdproject En el caso de proyectos, volver al directorio del proyecto.
wipeenv Borrar todos los paquetes del venv.
add2virtualenv <dir 1>[ <dir 2>] Permite añadir directorios al site-packages del virtualenv sin instalarlos
toggleglobalsitepackages Permite o deshabilita que se pueda acceder a paquetes del sistema en el virtualenv.

 

Crear y restaurar el requirements.txt

Esto no es algo propio de Virtualenv ni de Virtualenvwrapper, pero creo conveniente mencionarlo al utilizarse en conjunto cuando se trabaja en proyectos.

Es posible que hayas visto alguna vez un archivo llamado requirements.txt en algún proyecto Python, y te hayas preguntado qué es. Pues bien, se trata de un listado con los paquetes necesarios para que el proyecto funcione. Por ejemplo:

requirements.txt
----------------
Django==1.9.5
django-mptt==0.8.3
dpaste==2.10
ecdsa==0.13
...

 

Pues bien, si estamos usando en nuestro proyecto virtualenv, y todos los paquetes del virtualenv son para que funcione nuestro proyecto, ¿por qué no usar el listado de paquetes instalados en el virtualenv? Esto es lo que hace pip freeze, pudiendo generar un archivo requirements.txt

(miproyecto)[nekmo@homura ~/Projects/miproyecto]$ pip freeze > requirements.txt

 

Luego es posible reinstalar todos los paquetes del requirements.txt en otro virtualenv con:

(proj2)[nekmo@homura ~/Projects/proj2]$ pip install -r requirements.txt

En teoría, si reinstalamos los mismos paquetes con las mismas versiones en el nuevo sistema, todo debería funcionar exactamente igual que en el entorno original, lo cual lo convierte en una excelente solución para deployments (poner nuestro trabajo en el entorno real de trabajo).

 

¿Cómo funcionan los virtualenvs?

Cuando iniciamos un virtualenv, lo primero que se hace, es ejecutar el script ./bin/activate del virtualenv, que en el caso de virtualenvwrapper con la configuración usada, sería:

~/.virtualenvs/<venv>/bin/activate

 

Si abrimos este script, veremos que es un sencillo script de bash que entre otras cosas, hace lo siguiente:

PATH="$VIRTUAL_ENV/bin:$PATH"
export PATH

Con esto lo que se está haciendo es poner en primer lugar el directorio ./bin de virtualenv para la carga de binarios, por lo que priorizará el ejecutable de Python que allí se encuentra sobre el el del sistema. Podemos ver el ejecutable de Python del virtualenv en dicho directorio:

(test)[nekmo@homura ~/.virtualenvs/test/bin]$ ls -1
activate
...
easy_install
pip
python
...

 

La siguiente parte del truco se encuentra en cómo Python determina cual es su directorio con las bibliotecas: cuando se ejecuta el intérprete, éste va a buscar, desde la ruta en que se encuentra el binario, el archivo ./lib/pythonX.Y/os.py (sí, busca os.py literalmente), y desde el directorio del binario, va bajando niveles hasta encontrarlo. Así pues, en el caso de un virtualenv, esto suele ser:

~/.virtualenvs/<venv>/bin/lib/python2.7/os.py << No existe, sigo bajando...
~/.virtualenvs/<venv>/lib/python2.7/os.py << ¡Existe! ¡Usaré este directorio!

Para más información, recomiendo ver este vídeo y el PEP correspondiente.

 

Ejecutar programas en un virtualenv sin usar workon

Aunque con la anterior explicación se ha descubierto el pastel, por si acaso doy la respuesta: si workon sólo ejecuta ./bin/activate, y éste lo que hace es priorizar el binario de Python del virtualenv sobre el del sistema, para ejecutar un programa Python con virtualenv, sólo hay que usar su binario para ejecutar el programa. Así pues, usando:

[nekmo@homura ~]$ ~/.virtualenvs/test/bin/python miprograma.py

Es como si estuviésemos ejecutando miprograma.py dentro del virtualenv.

 

¿Qué puede salir mal?

Los virtualenvs son una de las cosas más geniales de Python para trabajar, pero sí que comentaría 2 cosas a tener en cuenta para evitar disgustos, como me ha pasado a mí:

La primera, que el ejecutable de Python del virtualenv require del intérprete de Python del sistema. Esto significa, que si actualizamos el del sistema, afectaremos también al del virtualenv. Esto normalmente no tendría que ser un problema, pues Python es muy estable en versiones patch (las 2.7.X), pero podría darse. Un problema habitual, sobre todo si se trabaja con Python3, es que se rompan los enlaces al actual, por ejemplo, de Python3.5 a 3.6. En tales casos, puede instalarse la versión específica que Python que usábamos a parte de la nueva. Si tuviésemos un entorno crítico, siempre podemos compilar Python a mano y enlazar a su ejecutable cuando se crea el virtualenv.

Otro posible problema, es cuando trabajamos con bibliotecas que no son puramente Python: es el caso por ejemplo de lxml o de MySQL-Python. En tales casos, al instalar la biblioteca con pip usando un requirements.txt, podríamos tener efectos indeseados si no se poseen los componentes necesarios para su compilación en el sistema, o podría no funcionar por las versiones de los mismos. Hay que tenerlo presente si se usan dependencias de este tipo.

Otras dudas comunes

¿Me ofrecen los virtualenvs una protección extra, como la de un sandbox?

No. Usar virtualenvs no es más seguro que usar las bibliotecas del sistema. Ésta no es su finalidad. Si buscas una solución para sandbox, recomiendo echarle un ojo a este artículo, aunque personalmente recomiendo conjuntarlo con una solución como Docker.

¿No se solapan los virtualenvs con Docker?

En cierta medida, sí. No hay nada con Virtualenvs que no puedas hacer con Docker. Pero utilizar Virtualenvs suele ser mucho más rápido y cómodo, además de no traer consigo un pico de recursos en la inicialización. Tox, otra excelente herramienta, utiliza virtualenvs para ejecutar baterías de tests con diversas versiones de Python y dependencias. Como suele ser necesario probar múltiples combinaciones (Python 3.5 con Django 1.9-1.7, Python 3.4 con Django 1.9-1.6...), puede llegar a usar hasta 30 combinaciones o más. Esto con virtualenvs es algo trivial y muy rápido, cosa que con Docker no.

¿Más dudas?

¡Gracias por leer mi artículo! Me ha llevado casi 1 semana en escribirlo con el poco tiempo que puedo dedicarle al blog. Espero que haya servido para animar a más gente a utilizar estas magníficas herramientas, y a programar en Python. Si tienes cualquier duda, no dudes en escribirla en los comentarios.