{ "cells": [ { "cell_type": "markdown", "id": "3e81438d-dc33-4fc9-a304-47851da7a752", "metadata": {}, "source": [ "# PYTHONPATH Tricks" ] }, { "cell_type": "markdown", "id": "4a51f064-0bb2-4c9b-bddd-49a94e3c7230", "metadata": {}, "source": [ "### What is PYTHONPATH\n", "PYTHONPATH is an environment variable that defines where python looks for importable modules\n", "\n", "In bash, running `echo $PYTHONPATH` will show you the current path\n", "\n", "Python then adds the directories in that path to `sys.path` at runtime" ] }, { "attachments": {}, "cell_type": "markdown", "id": "f20393d5-bbb2-472b-9549-4c8b8ec1b7b5", "metadata": {}, "source": [ "### Handling of dependencies with multiple versions\n", "\n", "If there are multiple versions of a package in `sys.path`, python searches `sys.path` in order and uses the first one found\n", "\n", "#### Experiment\n", "To prove this, setup the following scenario\n", "\n", "File: ~/package1/package.py\n", "def version():\n", " return \"Version 1\"\n", "\n", "File: ~/package2/package.py\n", "def version():\n", " return \"Version 2\"\n", "\n", "bash: `PYTHONPATH=~/package1/package.py:~/package2/package.py python`\n", "Inside the python interpreter: `import package; package.version()`\n", "Output: Version 1\n", "\n", "This makes sense, running `import sys; sys.path` shows us that package1 is listed first hence it was loaded" ] }, { "cell_type": "markdown", "id": "ebf61e77-2071-4787-b71c-09439a2d7794", "metadata": {}, "source": [ "## Overriding default resolution behaviors" ] }, { "cell_type": "code", "execution_count": null, "id": "e1b84fc0-236f-4b5d-8b8d-7cf11b403951", "metadata": { "trusted": true }, "outputs": [], "source": [ "### Changing default behavior, using path above, package.version() will output Version 1\n", "import os\n", "import sys\n", "MODULE = 'package'\n", "\n", "def printPackageVersion():\n", " pass", " #import package\n", " #print(f\"Currently loaded package version {package.version()}\")\n", "\n", "if 'package' in sys.modules.keys():\n", " # If already imported from somewhere else, remove\n", " printPackageVersion()\n", " del sys.modules[MODULE]\n", "\n", "# Changing to Version 2 of our package\n", "# sys.path was [\"\", \"package1\" \"package2\"] but will be [\"\", \"package2\", \"package1\"] now\n", "sys.path.insert(1, os.path.abspath(\"~/package2\"))\n", "printPackageVersion() # outputs Version2 \n", "\n", "# What if we wanted to use both versions of the package and use them both in the same file\n", "import importlib.util\n", "spec = importlib.util.spec_from_file_location('package', '~/package1/package.py')\n", "mod = importlib.util.module_from_spec(spec)\n", "#spec.loader.exec_module(mod)\n", "\n", "#mod.version() # Outputs Version 1\n", "#package.version() # Outputs Version 2\n", "\n", "# What about overriding the already loaded version of the module with our new version\n", "# Now our module versions are out of sync, to hack in Version 1 again globally\n", "#del sys.modules[MODULE]\n", "#sys.modules[MODULE] = mod\n", "\n", "#mod.version() # Outputs Version 1\n", "#package.version() # Outputs Version 1" ] } ], "metadata": { "kernelspec": { "display_name": "Python (Pyodide)", "language": "python", "name": "python" }, "language_info": { "codemirror_mode": { "name": "python", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.8" } }, "nbformat": 4, "nbformat_minor": 5 }