6.3 Python outside the notebook
Contents
6.3 Python outside the notebook#
Estimated time for this notebook: 15 minutes
We will often want to save our Python functions and classes, for use in multiple Notebooks or to interact with them via a terminal.
Writing Python in Text Files#
If you create your own Python files ending in .py
, then you can import them with import
just like external libraries.
It’s best to use an editor like VS Code or PyCharm to do this. Here we use the %%writefile
Jupyter “magic” to create files from the notebook.
Let’s create a file greeter.py
with a function greet
that prints a welcome message in multiple colours (using the colorama
package):
%%writefile greeter.py
import colorama # used for creating coloured text
def greet(personal, family, title="", polite=False):
greeting = "How do you do, " if polite else "Hey, "
greeting = colorama.Back.BLACK + colorama.Fore.YELLOW + greeting
if title:
greeting += colorama.Back.BLUE + colorama.Fore.WHITE + title + " "
greeting += (
colorama.Back.WHITE
+ colorama.Style.BRIGHT
+ colorama.Fore.RED
+ personal
+ " "
+ family
)
return greeting
Writing greeter.py
Loading Our Function#
We just wrote the file, there is no greet
function in this notebook yet:
greet("James", "Hetherington")
---------------------------------------------------------------------------
NameError Traceback (most recent call last)
Cell In[2], line 1
----> 1 greet("James", "Hetherington")
NameError: name 'greet' is not defined
But we can import the functionality from greeter.py
file that we created:
import greeter # note that you don't include the .py extension
print(greeter.greet("James", "Hetherington"))
Hey, James Hetherington
Or import the function from the file directly:
from greeter import greet
print(greet("James", "Hetherington"))
Hey, James Hetherington
Note the file we created is in the same directory as this notebook:
# glob is a library for finding files that match given patterns
from glob import glob
# all files with a .py or .ipynb extension in the current directory
glob("*.py") + glob("*.ipynb")
['greeter.py',
'06_01_installing_packages.ipynb',
'06_09_exercise.ipynb',
'06_02_managing_dependencies.ipynb',
'06_08_software_issues.ipynb',
'06_05_documentation.ipynb',
'06_06_software_development.ipynb',
'06_03_non_notebook_python.ipynb',
'06_07_software_licensing.ipynb',
'06_00_libraries.ipynb',
'06_04_packaging.ipynb']
Currently we’re relying on all the module source code being in our current working directory. We’ll want to import
our modules from notebooks elsewhere on our computer: it would be a bad idea to keep all our Python work in one folder.
The best way to do this is to learn how to make our code into a proper module that we can install. We’ll see more on that in the next notebook.
Command-line Interfaces#
argparse is the standard Python library for building programs with a command-line interface (another popular library is click).
Here’s an example that creates a command-line interface to our greet
function (in a file named command.py
):
%%writefile command.py
from argparse import ArgumentParser
from greeter import greet
def process():
parser = ArgumentParser(description="Generate appropriate greetings")
# required (positional) arguments
parser.add_argument("personal")
parser.add_argument("family")
# optional (keyword) arguments
parser.add_argument("--title", "-t")
parser.add_argument("--polite", "-p", action="store_true")
# polite will be false unless "--polite" or "-p" given at command-line
args = parser.parse_args()
print(greet(args.personal, args.family, args.title, args.polite))
if __name__ == "__main__":
process()
Writing command.py
We can now run our saved interface with python command.py
+ the arguments we want to specify.
argparse
generates some documentation to help us understand how to use it:
%%bash
python command.py --help
usage: command.py [-h] [--title TITLE] [--polite] personal family
Generate appropriate greetings
positional arguments:
personal
family
optional arguments:
-h, --help show this help message and exit
--title TITLE, -t TITLE
--polite, -p
A few examples:
%%bash
python command.py James Hetherington
Hey, James Hetherington
%%bash
python command.py --polite James Hetherington
How do you do, James Hetherington
%%bash
python command.py James Hetherington --title Dr
Hey, Dr James Hetherington
Having to type python command.py ...
is not very intuitive, and we’re still relying on our files being in the same directory. In the next notebook we’ll see a better way to include command-line interfaces as part of a package.
if __name__ == "__main__"
#
In the command.py
script above you may have noticed the strange if __name__ == "__main__"
line. This is generally used when you have a file that can be used both as a script and as a module in a package.
Let’s create a simplified version of greeter.py
that prints the name of the special __name__
variable when it is called:
%%writefile greeter.py
print("executing greeter.py, __name__ is", __name__)
def greet(personal, family):
return "Hey, " + personal + " " + family
if __name__ == "__main__":
print(greet("Laura", "Greeter"))
Overwriting greeter.py
If we invoke greeter.py
directly, Python sets the value of __name__
to "__main__"
and the code in the if block runs:
%%bash
python greeter.py
executing greeter.py, __name__ is __main__
Hey, Laura Greeter
Now let’s create a simplified command.py
that also prints __name__
, and imports the greet
function from greeter.py
as before:
%%writefile command.py
print("executing command.py, __name__ is", __name__)
from argparse import ArgumentParser
from greeter import greet
def process():
parser = ArgumentParser(description="Generate appropriate greetings")
parser.add_argument("personal")
parser.add_argument("family")
args = parser.parse_args()
print(greet(args.personal, args.family))
if __name__ == "__main__":
process()
Overwriting command.py
And run the command script:
%%bash
python command.py Sarah Command
executing command.py, __name__ is __main__
executing greeter.py, __name__ is greeter
Hey, Sarah Command
Note that when we import greeter.greet
the contents of the whole greeter.py
file are executed, so the code to print the value of __name__
still runs. However, __name__
is now given the value greeter
. This means when the if statement is executed __name__ == "__main__"
returns False
, and we don’t see the “Hey, Laura Greeter” output.
Without that if statement we would get
Hey, Laura Greeter
Hey, Sarah Command
which is unlikely to be what we wanted when running python command.py Sarah Command
.