{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "# Custom Modules\n", "\n", "In this offering of PIC16A, we have primarily done our coding in the Jupyter Notebook interface, which emphasizes interactivity and exploratory coding. Another useful skill is writing and using your own modules (i.e. `.py` files), which can hold useful classes and functions that you would like to reuse. In this lecture, we'll go into some more detail on how to create and use modules.\n", "\n", "## Importing, Revisited\n", "\n", "At core, the `import` keyword is just a fancy way to run the code in one or more `.py` files -- the main difference is that the `import` keyword will additionally assign objects and functions created within those files to the module's namespace. That is, a function `f` defined in module `m` will have name `m.f`. We've seen ways to use `from` and `as` to further manipulate how names are organized. \n", "\n", "Because `import` literally runs code, we can also use it to execute other commands in local `.py` files. Here's an example: " ] }, { "cell_type": "code", "execution_count": 1, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Hi there!\n", "What's your name?Jean-Luc Picard\n", "Nice to meet you, Jean-Luc Picard!\n" ] } ], "source": [ "# hello.py : \n", "# print(\"Hi there!\")\n", "# name = input(\"What's your name?\")\n", "# print(\"Nice to meet you, \" + name + \"!\")\n", "\n", "import hello\n", "# ---" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Because the `import` keyword checks whether a module has already been loaded, you can only `import` a given file once per session (In Jupyter, you can \"restart the kernel\" to start a new session). So, running `import hello` again doesn't do anything. " ] }, { "cell_type": "code", "execution_count": 2, "metadata": {}, "outputs": [], "source": [ "import hello" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "While it is possible to run the same external code multiple times, this is almost always bad practice and we will not discuss it in this course. Instead, structure your code so that each file needs to be imported only once. " ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Multi-file Projects\n", "\n", "When you have many hundreds, thousands, or tens of thousands of lines of code, you don't want to put them all in the same `.py` file. Instead, it's common to split these up into multiple files. A **package** is a directory structure containing multiple modules, alongside a special `__init__.py` file that tells the Python interpreter that the files in the given directory should be treated as modules. These directories can be arbitrarily nested. \n", "\n", "Here's an example, with the following directory structure: \n", "\n", "```\n", "example_module/\n", "+-- __init__.py\n", "+-- top_level.py\n", "+-- example_submodule/\n", " +-- __init__.py\n", " +-- funs_1.py\n", " +-- funs_2.py\n", "```\n", "\n", "Both of the files `__init__.py` are completely empty -- the only thing that matters is their name. Once these files are in place, we can use `import` in exactly the way we did previously on modules written by others. " ] }, { "cell_type": "code", "execution_count": 7, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "function imported from example_module.top_level\n" ] } ], "source": [ "from example_module import top_level\n", "top_level.describe()\n", "# ---" ] }, { "cell_type": "code", "execution_count": 8, "metadata": {}, "outputs": [], "source": [ "# importing submodules\n", "from example_module.example_submodule import funs_1, funs_2" ] }, { "cell_type": "code", "execution_count": 9, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "function imported from example_module.example_submodule.funs_1\n" ] } ], "source": [ "funs_1.describe_1()\n", "# ---" ] }, { "cell_type": "code", "execution_count": 10, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "function imported from example_module.example_submodule.funs_2\n" ] } ], "source": [ "funs_2.describe_2()\n", "# ---" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "# Modules as Scripts" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "A very common pattern is to write a single `.py` file that contains both function or object definitions and imperative commands (e.g. function calls). For example: \n", "\n", "```\n", "# file: boldly.py\n", "\n", "# function definitions\n", "\n", "def boldly_print():\n", " print(\"to boldly go\")\n", "\n", "# imperative commands \n", "\n", "boldly_print()\n", "```\n", "\n", "We might decide that `boldly_print()` is a super-good function, and that we'd like to use it in other projects *without* rewriting the code. However, we don't always want to *call* `boldly_print()` immediately, as would happen if we ran `import boldly`. \n", "\n", "For this reason, Python defines a special `__name__` variable. In the global scope, `__name__` has value `\"__main__\"`. " ] }, { "cell_type": "code", "execution_count": 11, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "'__main__'" ] }, "execution_count": 11, "metadata": {}, "output_type": "execute_result" } ], "source": [ "__name__" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "On the other hand, when a module is `import`ed, the value of `__name__` is the name of the module. This means we can check whether the module is being treated as a script (i.e. run in global scope) or imported (so that we can use its definitions without running its imperative commands). Here's an example: " ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "```\n", "# file: boldly2.py\n", "\n", "def boldly_print():\n", " print(\"to boldly go\")\n", "\n", "print(__name__)\n", "\n", "if __name__ == \"__main__\":\n", " boldly_print()\n", "```" ] }, { "cell_type": "code", "execution_count": 12, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "boldly2\n" ] } ], "source": [ "import boldly2 # prints the value of __name__\n", "# ---" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "In this course, we don't frequently run modules as scripts, but it's a good thing to know how to do. If you like the command line, then you can just do this after navigating to the appropriate directory: \n", "\n", "```bash\n", "> python3 boldly2.py\n", "\n", "__main__\n", "to boldly go\n", "```\n", "\n", "A more comfortable approach for many is to use the Spyder IDE. " ] } ], "metadata": { "kernelspec": { "display_name": "Python 3", "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.8.3" } }, "nbformat": 4, "nbformat_minor": 4 }