<jupyter_start><jupyter_text>This tutorial shows various methods of reusing your Python code. The follow up tutorial on packaging code will explore ways to make code reusable by others.<jupyter_code>import numpy as np
import matplotlib.pyplot as plt<jupyter_output><empty_output><jupyter_text>Step 0: functionsFunctions are the simplest way to cut down duplicated code. The simple example we will use is sets up a specifically scaled multi axes figure<jupyter_code>def go_figure():
    figure, axes = plt.subplots(2, 2, figsize=(10,6))
    axes = axes.ravel()
go_figure();<jupyter_output><empty_output><jupyter_text>To make this function useful, we should return some objects to work with. In this case, we want fig and ax<jupyter_code>def go_figure():
    figure, axes = plt.subplots(2, 2, figsize=(10,6), sharex='col', sharey='row')
    axes = axes.ravel()
    return figure, axes
# Some data to plot
t = np.arange(0,50,0.1)
y0 = np.sin(t)
y1 = np.cos(t/2)
fig, ax = go_figure();
ax[0].plot(t,y0)
ax[1].plot(t,y1)
ax[2].plot(t ,y0 + y1)
ax[3].plot(t ,y0 * y1);<jupyter_output><empty_output><jupyter_text>Step 1: argumentsWe can extend our functions with **arguments** (mandatory) and **key word arguments** (optional). You will often see the short-hand **args** and **kwargs** used to refer to these.The pattern is:```pythondef function(arg0, arg1, kwarg0=default0, kwarg1=default1):    do stuff    return value0, value1```<jupyter_code>def go_figure(rows, cols, title=''):
    figure, axes = plt.subplots(rows, cols, figsize=(10,6), sharex='col', sharey='row')
    if type(axes) == np.array:
        axes = axes.ravel()
    figure.suptitle(title, fontsize=18)
    return figure, axes<jupyter_output><empty_output><jupyter_text>Our functions now requires two arguments to be passed, rows and cols. Use arguments if you expect to change the value often<jupyter_code>fig, ax  = go_figure(2,2)<jupyter_output><empty_output><jupyter_text>Our figure also has a kwarg, title. Use kwargs for things you do not expect to change often. The default can be a typical value or an empty string or list. For example, I rarely use plot title<jupyter_code>fig, ax  = go_figure(2, 3);<jupyter_output><empty_output><jupyter_text>Step 2: share between scripts in directoryI have created another function `sine_waves` in a script called **waves.py** in the same directory as this one. Note that this must be a plain Python file, not a jupyter notebook.We can import this just as we would from a downloaded Python package<jupyter_code>from waves import sine_combo
t, y0 = sine_combo()
__, y1 = sine_combo(f=(1,1.2))
fig, ax = go_figure(2,1)
ax[0].plot(t,y0)
ax[1].plot(t,y1);<jupyter_output><empty_output><jupyter_text>If you have scripts nested in directories, you can go into them with a `.` just as you would for sub-modules like `matplotlib.pyplot`<jupyter_code>from adjacent_dir.more_waves import cosine_combo
t, y0 = cosine_combo()
t, y1 = cosine_combo(f=(1,1.1))
fig, ax = go_figure(2,1)
ax[0].plot(t,y0)
ax[1].plot(t,y1);<jupyter_output><empty_output><jupyter_text>Step 3: scripts in distant directoriesIf your script is located somewhere distant on your computer, Python won't know where it is.By default, python only knows of locations in `sys.path`<jupyter_code>import sys
sys.path<jupyter_output><empty_output><jupyter_text>Notice that this includes the location of packages in your environments (an anaconda environment called **sci** in this case) and the folder of the script itself. This is how we can import from scripts next to ours or in subdirectories of the current directory, Python already knows about them.`sys.path` is just a list, so we can append directories to it.I have a set of scripts with handy functions in a directory called `python_scripts`. Let's add it to the path.<jupyter_code>sys.path.append('/home/callum/Documents/coding/python_scripts')
sys.path
from glider_utils import labels

labels<jupyter_output><empty_output><jupyter_text>**Note**: this cross script sharing is not limited to functions, although these are the most common things we share. You can import any Python object from one project to another. You can share a lot of useful data in dictionaries, e.g. TeX formatted strings for consistent axis labels. The main value comes with reuse, if you decide to change the way you represent the $^{\circ}$ sign in your axes labels, you'll only need to change it in one place**A word of caution.** It may be tempting to add a load of directories to your path, to ensure your favourite scripts and functions are always within reach. However, unless you are careful with your naming scheme, you may overwirte the namespace of Python. That is, if you have two locations where the function `plot_data` is included in the script `useful_functions` and you have added both of these locations to your path, performing `from useful_functions import plot_data`could give unepxected results.Only add the paths you need to `sys.path` and try to ensure unique naming strategies for scripts and functions.  Step 4: Create a packageA package is the highest unit of organisation in Python. Some popular packages include *numpy*, *pandas* and *xarray*. Packages are not the sole domain of venerable Python mystics however, you too can create and use modules!Here it is in three easy steps:1. Put functions in a .py file and put the file in a directory2. Put that directory on the internet3. ProfitMaking these is so easy, we've already done it! a Python script in a directory is a package for all intents and purposes.To show just how easy it is to make and share packages in Python, we'll be doing it live next week. ---------------------- Footnotes  Use of `__main__`In Python scripts, you will often see a small if statment with `__main__` like so:```pythonif __name__ == '__main__':    function_name()```This allows for dual use code. The script's functions and variables can be imported from other scripts as we have practiced today. The script can also be run from the command line, in which case the contents of this `if` statement are executed. The use of `__main__` allows the script to distinguish when it is being used in an import statment or called directly and execute accordingly.  What about `__init__.py`?Reading about modules and imports in Python, you will often come across use of an empty Python file called `__init__.py`. This used to be a requirement for Python to treat a directory as a module name, to enable imports from scripts and directories within that directory. This is no longer necessary as of Python 3.3  Resources- https://docs.python.org/3/tutorial/modules.html- https://stackoverflow.com/questions/37139786/is-init-py-not-required-for-packages-in-python-3-3<jupyter_code>HTML(html)<jupyter_output><empty_output>
{'fraction_non_alphanumeric': 0.08566355969818021, 'fraction_numerical': 0.01301967746708093, 'mean_word_length': 5.112115732368897, 'pattern_counts': {'":': 0, '<': 45, '<?xml version=': 0, '>': 45, 'https://': 2, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 1, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'chain_length': '11', 'dir': 'jupyter-structured-clean-dedup', 'id': '316510', 'license': 'MIT', 'n_tokens_mistral': 2036, 'n_tokens_neox': 1959, 'n_words': 884, 'path': 'content/notebooks/2021-02-11-python-reuse.ipynb', 'repo_name': 'ueapy/ueapy.github.io', 'size': '6759'}