{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "(sec-pypckg)=\n", "# Packages, Modules and Libraries\n", "\n", "Import external libraries and organize your code into functional chunks. For interactive reading and executing code blocks [![Binder](https://mybinder.org/badge_logo.svg)](https://mybinder.org/v2/gh/hydro-informatics/jupyter-python-course/main) and find *b05-pypckg.ipynb*, or install {ref}`Python ` and {ref}`JupyterLab ` locally.\n", "\n", "```{admonition} Requirements\n", "Make sure to understand {ref}`Python functions `.\n", "```\n", "\n", "```{admonition} Watch this section as a video\n", ":class: tip, dropdown\n", "\n", "

Watch this section as a video on the @Hydro-Morphodynamics channel on YouTube.

\n", "```\n", "\n", "## Import Packages or Modules\n", "\n", "Importing a package or module in Python makes external functions and other elements (such as objects) of modules accessible in a script. The functions and other elements are stored within another Python file (`.py`) in some */site_packages/* folder (directory) of the interpreter environments. Thus, to use a non-standard package, it needs to be downloaded and installed first. Standard Python packages (e.g., `os`, `math`) are always accessible and other can be added with *conda* ({ref}`read more about conda-installing `) or *pip* ({ref}`read more pip-installing `).\n", "\n", "```{admonition} The Difference between a Module and a Package\n", "Modules are single or multiple files that can be imported independently (e.g. import a script named `a_module.py` with `import a_module`). Packages are a collection of modules in a directory with a defined hierarchy, which also enables the import of individual modules (e.g. `from a_package.module import a_function`). Packages are therefore also modules, but with a hierarchy definition (i.e., a `__path__` attribute and an `__init__.py` file). Sounds fuzzy? Read this section down to the bottom and come back here to re-read this note.\n", "```\n", "\n", "The `os` package provides basic system-terminal-like commands, for example, to manage folder directories. So let's import this essential package:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "import os\n", "print(os.getcwd()) # print current working directory\n", "print(os.path.abspath('')) # print directory of script running" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Overview of Import Options\n", "\n", "Here is an overview of options to import packages or modules (hierarchical parts of packages):\n", "\n", "| Command | Description | Usage of attributes |\n", "|---------|-------------|---------------------|\n", "| `import package-name` | Import an original module | `package.item()` |\n", "| `import package-name as nick-name` | Import module and rename (alias) it in the script | `nick-name.item()` |\n", "| `from package-name import item` | Import only a function, class or other items | `item()` |\n", "| `from package-name import *` | Import all items | `item()` |\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Example" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "import matplotlib.pyplot as plt # import pyplot from the matplotlib module and alias it with plt\n", "import math as m\n", "\n", "x = []\n", "y = []\n", "\n", "for e in range(1, 10):\n", " x.append(e)\n", " y.append(e**2)\n", "\n", "plt.plot(x, y)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### What is the best way to import a package or module?\n", "There is no global answer to this question. However, be aware that `from package-name import *` overwrites any existing variable or other items in the script. Thus, only use `*` when you are aware of all contents of a module or package." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "pi = 9.112 # define a float called pi \n", "print(\"Pi is not %1.3f.\" % pi)\n", "\n", "from math import pi # this overwrites the before defined variable pi\n", "print(\"Pi is %1.3f.\" % pi)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "```{tip}\n", "Define default import packages for *JupyterLab*'s *IPython* kernel (read more in the {ref}`Python installation section `).\n", "```" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### What items (attributes, classes, functions) are in a module?\n", "\n", "Sometimes we want to explore modules or check variable attributes. This is achieved with the `dir()` command:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "import sys\n", "print(sys.path)\n", "print(dir(sys.path))\n", "\n", "a_string = \"zabaglione\"\n", "print(\", \".join(dir(a_string)))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "(make-mod)=\n", "## Create a new Module \n", "\n", "In object-oriented programming and code factorization, writing custom, new modules is an essential task. To write a new module, first, create a new script. Then, open the new script and add some parameters and functions." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# icecreamdialogue.py\n", "flavors = [\"vanilla\", \"chocolate\", \"bread\"]\n", "price_scoops = {1: \"two euros\", 2: \"three euros\", 3: \"your health\"}\n", "welcome_msg = \"Hi, I only have \" + flavors[0] + \". How many scoops do you want?\"" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "[`icecreamdialogue.py`](https://github.com/hydro-informatics/icecream/raw/master/single-scripts/icecreamdialogue.py) can now either be executed as a script (nothing will happen visibly) or imported as a module to access its variables (e.g., `icecreamdialogue.flavors`):" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "import icecreamdialogue as icd\n", "print(icd.welcome_msg)\n", "scoops_wanted = 2\n", "print(\"That makes {0} please\".format(icd.price_scoops[scoops_wanted]))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "(standalone)=\n", "### Make Script Stand-alone \n", "\n", "As an alternative, we can append the call to items in [`icecreamdialogue.py`](https://github.com/hydro-informatics/icecream/raw/master/single-scripts/icecreamdialogue.py) in the script and run it as a stand-alone script by adding an `if __name__ == \"__main__\":` block:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# icecreamdialogue_standalone.py\n", "flavors = [\"vanilla\", \"chocolate\", \"bread\"]\n", "price_scoops = {1: \"two euros\", 2: \"three euros\", 3: \"your health\"}\n", "welcome_msg = \"Hi, I only have \" + flavors[0] + \". How many scoops do you want?\"\n", "\n", "\n", "if __name__ == \"__main__\":\n", " print(welcome_msg)\n", " scoops_wanted = 2\n", " print(\"That makes {0} please\".format(price_scoops[scoops_wanted]))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Now we can run [`icecreamdialogue_standalone.py`](https://github.com/hydro-informatics/icecream/raw/master/single-scripts/icecreamdialogue_standalone.py) in a terminal (e.g., Linux Terminal, PyCharm's Terminal tab at the bottom of the window, or in Atom using `platformio-ide-terminal`)." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "```\n", "C:\\temp\\ python icecreamdialogue_standalone.py\n", "```" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "```{note}\n", "Depending on the definition of system variables used in the *Terminal* environment, Python must be called with a different variable name than `python` (e.g., `python3` on some Linux platforms).\n", "```" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "(standaloneplus)=\n", "### Standalone Scripts with Input Parameters\n", "\n", "To make the script more flexible, we can define, for instance, `scoops_wanted` as an input variable of a function." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "```python\n", "# icecreamdialogue_standalone_withinput.py\n", "flavors = [\"vanilla\", \"chocolate\", \"bread\"]\n", "price_scoops = {1: \"two euros\", 2: \"three euros\", 3: \"your health\"}\n", "welcome_msg = \"Hi, I only have \" + flavors[0] + \". How many scoops do you want?\"\n", "\n", "def dialogue(scoops_wanted): #formerly in the __main__ statement\n", " print(welcome_msg)\n", " print(\"That makes {0} please\".format(price_scoops[scoops_wanted]))\n", "\n", "if __name__ == \"__main__\":\n", " # import the terminal function emulator sys\n", " import sys\n", " if len(sys.argv) > 1: # make sure input is provided\n", " # if true: call the dialogue function with the input argument\n", " dialogue(int(sys.argv[1]))\n", "```" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Now, we can run `icecreamdialogue_standalone_withinput.py` in a terminal.\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "```\n", "C:\\temp\\ python3 icecreamdialogue_standalone.py 2\n", "```" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "(make-pckg)=\n", "### Initialization of a Package (Hierarchically Organized Module)\n", "\n", "Good practice involves that one script does not exceed 50-100 lines of code (except inline docs and multiline variables). In consequence, a package will most likely consist of multiple scripts that are stored in one folder and one core script serves for the initiation of the scripts. This core script is called `__init__.py` and Python will always invoke this script name in a package folder. Example structure of a module called `icecreamery`:\n", "\n", "* `icecreamery` (folder name)\n", " - `__init__.py` - package initiation *Python* script \n", " - `icecreamdialogue.py` - dialogue producing *Python* script\n", " - `icecream_maker.py` - virtual ice cream producing *Python* script\n", "\n", "To automatically invoke the two relevant scripts (sub-modules) of the `icecreamery` module, the `__init__.py` needs to include the following:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# __init__.py\n", "print(f'Invoking __init__.py for {__name__}') # not absolutely needed ..\n", "import icecreamery.icecreamdialogue, icecreamery.icecream_maker" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# example usage of the icecreamery package\n", "import icecreamery\n", "print(icecreamery.icecreamdialogue.welcome_msg)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Do you remember the `dir()` function? It is intended to list all modules in a package, but it does not do so unless we defined an `__all__` list in the `__init__.py`. " ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "```python\n", "# __init__.py with __all__ list\n", "__all__ = ['icecreamdialogue', 'icecream_maker']\n", "```" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "ThThe full example of the `icecreamery_all` package is also available in an [icecream](https://github.com/hydro-informatics/icecream) repository." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# example usage of the icecreamery package\n", "from icecreamery_all import *\n", "print(icecreamdialogue.welcome_msg)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Package Creation Summary\n", "\n", "A hierarchically organized package contains an `__init__.py` file with an `__all__` list to invoke relevant module scripts. The structure of a module can be more complex than the above example list (e.g., with sub-folders). When you write a package, consider using {ref}`meaningful script and variable names `, along with appropriate documentation.\n", "\n", "```{admonition} Exercise with logging\n", "Implement a custom {ref}`logger ` in your module with `logger = logging.getLogger(__name__)` (replace `__name__` with for example `my-module-log`).\n", "```" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Reload (Re-import) a Package or Module\n", "\n", "Since Python3, reloading a module requires importing the `importlib` module first. Reloading makes only sense if you are actively writing a new module. To reload any module type:" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "```python\n", "import importlib\n", "importlib.reload(my-module)\n", "```" ] } ], "metadata": { "celltoolbar": "Raw Cell Format", "kernelspec": { "display_name": "Python 3 (ipykernel)", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.10.6" } }, "nbformat": 4, "nbformat_minor": 4 }