Cómo funcionan las bibliotecas de entorno virtual

¿Alguna vez has pensado en cómo funcionan las bibliotecas de entorno virtual en Python? En este artículo, sugiero familiarizarse con el concepto principal que utilizan todas las bibliotecas para entornos, como virtualenv, virtualenvwrapper, conda, pipenv.

Inicialmente, en Python no había una capacidad incorporada para crear entornos, y esta característica se implementó como un truco. Al final resultó que, todas las bibliotecas se basan en una característica muy simple del intérprete de Python.

Cuando Python inicia el intérprete, comienza a buscar el directorio con los módulos (paquetes de sitio). La búsqueda comienza con el directorio principal con respecto a la ubicación física del ejecutable del intérprete (python.exe). Si no se encuentra la carpeta del módulo, Python sube un nivel más y lo hace hasta que se alcanza el directorio raíz. Para comprender que este es un directorio con módulos, Python busca el módulo os, que debe estar en el archivo os.py y es necesario para que Python funcione.

Imaginemos que nuestro intérprete se encuentra en /usr/dev/lang/bin/python . Entonces las rutas de búsqueda se verán así:

 /usr/dev/lang/lib/python3.7/os.py /usr/dev/lib/python3.7/os.py /usr/lib/python3.7/os.py /lib/python3.7/os.py 

Como puede ver, Python agrega un prefijo especial ( lib/python$VERSION/os.py ) a nuestra ruta. Tan pronto como el intérprete encuentra la primera coincidencia (la presencia del archivo os.py), cambia sys.prefix y sys.exec_prefix a esta ruta (sin el prefijo). Si por alguna razón no se encuentran coincidencias, se utiliza la ruta estándar, que se compila en el intérprete.

Ahora veamos cómo lo hace una de las bibliotecas más antiguas y famosas, virtualenv.

 user@arb:/usr/home/test# virtualenv ENV Running virtualenv with interpreter /usr/bin/python3 New python executable in /usr/home/test/ENV/bin/python3 Also creating executable in /usr/home/test/ENV/bin/python Installing setuptools, pkg_resources, pip, wheel...done. 

Después de la ejecución, crea directorios adicionales:

 user@arb:/usr/home/test/ENV# tree -L 3 . ├── bin │ ├── activate │ ├── activate.csh │ ├── activate.fish │ ├── activate_this.py │ ├── easy_install │ ├── easy_install-3.7 │ ├── pip │ ├── pip3 │ ├── pip3.7 │ ├── python │ ├── python-config │ ├── python3 -> python │ ├── python3.7 -> python │ └── wheel ├── include │ └── python3.7m -> /usr/include/python3.7m ├── lib │ └── python3.7 │ ├── __future__.py -> /usr/lib/python3.7/__future__.py │ ├── __pycache__ │ ├── _bootlocale.py -> /usr/lib/python3.7/_bootlocale.py │ ├── _collections_abc.py -> /usr/lib/python3.7/_collections_abc.py │ ├── _dummy_thread.py -> /usr/lib/python3.7/_dummy_thread.py │ ├── _weakrefset.py -> /usr/lib/python3.7/_weakrefset.py │ ├── abc.py -> /usr/lib/python3.7/abc.py │ ├── base64.py -> /usr/lib/python3.7/base64.py │ ├── bisect.py -> /usr/lib/python3.7/bisect.py │ ├── codecs.py -> /usr/lib/python3.7/codecs.py │ ├── collections -> /usr/lib/python3.7/collections │ ├── config-3.7m-darwin -> /usr/lib/python3.7/config-3.7m-darwin │ ├── copy.py -> /usr/lib/python3.7/copy.py │ ├── copyreg.py -> /usr/lib/python3.7/copyreg.py │ ├── distutils │ ├── encodings -> /usr/lib/python3.7/encodings │ ├── enum.py -> /usr/lib/python3.7/enum.py │ ├── fnmatch.py -> /usr/lib/python3.7/fnmatch.py │ ├── functools.py -> /usr/lib/python3.7/functools.py │ ├── genericpath.py -> /usr/lib/python3.7/genericpath.py │ ├── hashlib.py -> /usr/lib/python3.7/hashlib.py │ ├── heapq.py -> /usr/lib/python3.7/heapq.py │ ├── hmac.py -> /usr/lib/python3.7/hmac.py │ ├── imp.py -> /usr/lib/python3.7/imp.py │ ├── importlib -> /usr/lib/python3.7/importlib │ ├── io.py -> /usr/lib/python3.7/io.py │ ├── keyword.py -> /usr/lib/python3.7/keyword.py │ ├── lib-dynload -> /usr/lib/python3.7/lib-dynload │ ├── linecache.py -> /usr/lib/python3.7/linecache.py │ ├── locale.py -> /usr/lib/python3.7/locale.py │ ├── no-global-site-packages.txt │ ├── ntpath.py -> /usr/lib/python3.7/ntpath.py │ ├── operator.py -> /usr/lib/python3.7/operator.py │ ├── orig-prefix.txt │ ├── os.py -> /usr/lib/python3.7/os.py │ ├── posixpath.py -> /usr/lib/python3.7/posixpath.py │ ├── random.py -> /usr/lib/python3.7/random.py │ ├── re.py -> /usr/lib/python3.7/re.py │ ├── readline.so -> /usr/lib/python3.7/lib-dynload/readline.cpython-37m-darwin.so │ ├── reprlib.py -> /usr/lib/python3.7/reprlib.py │ ├── rlcompleter.py -> /usr/lib/python3.7/rlcompleter.py │ ├── shutil.py -> /usr/lib/python3.7/shutil.py │ ├── site-packages │ ├── site.py │ ├── sre_compile.py -> /usr/lib/python3.7/sre_compile.py │ ├── sre_constants.py -> /usr/lib/python3.7/sre_constants.py │ ├── sre_parse.py -> /usr/lib/python3.7/sre_parse.py │ ├── stat.py -> /usr/lib/python3.7/stat.py │ ├── struct.py -> /usr/lib/python3.7/struct.py │ ├── tarfile.py -> /usr/lib/python3.7/tarfile.py │ ├── tempfile.py -> /usr/lib/python3.7/tempfile.py │ ├── token.py -> /usr/lib/python3.7/token.py │ ├── tokenize.py -> /usr/lib/python3.7/tokenize.py │ ├── types.py -> /usr/lib/python3.7/types.py │ ├── warnings.py -> /usr/lib/python3.7/warnings.py │ └── weakref.py -> /usr/lib/python3.7/weakref.py └── pip-selfcheck.json 

Como puede ver, el entorno virtual se creó copiando el binario de Python a una carpeta local (ENV / bin / python). También podemos notar que la carpeta principal contiene enlaces simbólicos a los archivos de la biblioteca estándar de Python. No podemos crear un enlace simbólico al archivo ejecutable, como el intérprete lo cambiará de nombre a la ruta real.

Ahora activemos nuestro entorno:

 user@arb:/usr/home/test# source ENV/bin/activate 

Este comando cambia la variable de entorno $ PATH para que el comando python apunte a nuestra versión local de python. Esto se logra al sustituir la ruta local de la carpeta bin al comienzo de la línea $ PATH para que la ruta local tenga prioridad sobre todas las rutas a la derecha.

 export "/usr/home/test/ENV/bin:$PATH" echo $PATH 

Si ejecuta el script desde este entorno, se ejecutará utilizando el binario en /usr/home/test/ENV/bin/python . El intérprete utilizará esta ruta como punto de partida para encontrar módulos. En nuestro caso, los módulos de la biblioteca estándar se encontrarán en la ruta /usr/home/test/ENV/lib/python3.7/ .

Este es el truco principal, gracias al cual funcionan todas las bibliotecas para trabajar con entornos virtuales.

Mejoras en Python 3


A partir de la versión Python 3.3, apareció un nuevo estándar, denominado PEP 405 , que introduce un nuevo mecanismo para entornos livianos.

Este PEP agrega un paso adicional al proceso de búsqueda. Si crea el pyenv.cfg configuración pyenv.cfg , en lugar de copiar el binario de Python y todos sus módulos, simplemente puede indicar su ubicación en esta configuración.

Esta característica es utilizada activamente por el módulo venv estándar, que apareció en Python 3.

 user@arb:/usr/home/test2# python3 -m venv ENV user@arb:/usr/home/test2# tree -L 3 . └── ENV ├── bin │ ├── activate │ ├── activate.csh │ ├── activate.fish │ ├── easy_install │ ├── easy_install-3.7 │ ├── pip │ ├── pip3 │ ├── pip3.5 │ ├── python -> python3 │ └── python3 -> /usr/bin/python3 ├── include ├── lib │ └── python3.7 ├── lib64 -> lib ├── pyvenv.cfg └── share └── python-wheels 

 user@arb:/usr/home/test2# cat ENV/pyvenv.cfg home = /usr/bin include-system-site-packages = false version = 3.7.0 user@arb:/usr/home/test2# readlink ENV/bin/python3 /usr/bin/python3 

Gracias a esta configuración, en lugar de copiar el binario, venv simplemente crea un enlace a él. Si el parámetro include-system-site-packages cambia a true , todos los módulos de la biblioteca estándar serán accesibles automáticamente desde el entorno virtual.

A pesar de estos cambios, la mayoría de las bibliotecas de terceros para trabajar con entornos virtuales utilizan el enfoque anterior.

PD: Soy el autor de este artículo, puedes hacer cualquier pregunta.

Source: https://habr.com/ru/post/es418579/


All Articles