Skip to article frontmatterSkip to article content
Site not loading correctly?

This may be due to an incorrect BASE_URL configuration. See the MyST Documentation for reference.

8 Code Blocks: Importing Functions and Libraries

8.1Lesson goals

8.2Overview

In a previous lesson, we covered how we can use functions to bundle up reusable code blocks. These functions will work in the interactive Python shell space or the Python file where we defined it, but what if we want to use it in another interactive notebook or Python file? Are we forced to copy and paste these functions over to the new location if we plan on reusing them in another program?

An important “Pythonic” style convention is to avoid duplication of code whenever possible, and with the import built-in keyword we can import Python code from one program into another, whether that is a separate Python file or a JupyterLab notebook. In this lesson, we will demonstrate this ability by first creating a basic Python code file that contains a function and then import this function into a JupyterLab notebook. Then we will import and explore other built-in libraries, including the external library NumPy. Finally, we will cover how to read and write to external files with Python.

8.3The import keyword

The import built-in keyword is used to import code from a module (i.e., a file that contains Python code) into your code. To learn how this all works, we will first create a Python file that contains a function definition (a.k.a., a module!), and then we will import the module into a interactive notebook using import.

8.3.1Step 1: Create the module

Open your IDE and copy the following code:

def resistance(i, v):         
    # Use Ohm's law to calculate electrical resistance
    r = v / i
    # return resistance
    return r

Save the file as resistor.py to a convenient working folder.

8.3.2Step 2: Import the module using the import keyword

Now launch a new interactive Python file (usually a IPython notebook file → a IPYNB file) in the same folder where you saved resistor.py. Execute the following command:

import resistor

r = resistor.resistance(i=10, v=5)
print(f"The resistance is {r} ohms")
The resistance is 0.5 ohms

The import resistor statement tells the interpreter to scan for any files in your Python env folder and your current directory that are named resistor.py. If the interpreter finds the file it then imports the entire file as a module object named resistor (i.e., it names the module object based on the filename).

The second command calls the resistance function using resistor.resistance() since the function is inside the resistor module. Since this is not a built-in Python function so we to tell the interpreter where to find the function! We save the output from the function call to the variable r and then print it out to the shell.

Since all data in Python is an object, our imported resistor.py file is also an object part of the module class. You can see this by using type() on resistor:

print(type(resistor))
<class 'module'>

To cut down on keystrokes, we can alternatively import just resistance() from the resistor module via the command:

from resistor import resistance

r = resistance(i=10, v=5)
print(f"The resistance is {r} ohms")
The resistance is 0.5 ohms

The from keyword can only be used with the import keyword to import specific code from a Python module. The from resistor import resistance import pattern means that the module resistor will be inspected to find some Python code that is named resistance, which will then be imported. You can import any Python code from a module, like a variable, function, class, or in fact another module. The from - import command structure cuts down on typing and typically lowers memory usage but sometimes can make it harder to understand the origins of a function.

Let us create a new Python file in the same folder as the resistor.py file. Open up your IDE of choice, type the following code, and save it as ohms_law.py:

from resistor import resistance

def voltage(i, r):         
    # return voltage
    return i * r
    
def current(v, r):         
    # return current
    return v / r

Now we can import multiple things from the ohms_law.py file as a module:

from ohms_law import resistance, voltage

r = resistance(v=10, i=0.25)
v = voltage(i=0.25, r=r)
i = current(v=10, r=r)

print(f"The voltage is {v} V")
print(f"The current is {i} A")
print(f"The resistance is {r} ohms")
The voltage is 10.0 V
The current is 0.25 A
The resistance is 40.0 ohms

In this example, resistance() is imported via a two-step route: it is first called via the ohms_law module import (ohms_law.py), which then imports resistance() from resistor.py. We also imported another function and variable from the ohms_law module by typing their names using the statement import resistance, voltage. Notice that we use a comma to separate the various objects we wished to import. We did not import current from ohms_law, so that function is unavailable to our code. It is usually a good practice to import the specific functionality you need from a module with from - import pattern instead of importing the top level module, but that is by no means a hard rule.

8.3.3Example: Importing your work

Let’s expand out import skills even further by importing two different modules. First, create a Python file called user.py and copy the function user(). Save this in the same folder as your ohms_law.py file. Next, open up an interactive Python session in the same folder as your user.py and your ohms_law.py files and create a code block that does the following:


Solution:

A copy of user.py can be found here. The code block below loads in these two modules and uses the associated functions to solve the problem:

# Libraries
from ohms_law import resistance, voltage, current
from user import user

# Print user name
user_first_name, user_last_name, user_age = user(first="Goldy", last="Gopher", 
                                                 age=40)
print("User's first name:", user_first_name)
print("User's last name:", user_last_name)
print("User's age in months:", user_age)

# Ohm's law analysis
r = resistance(v=10, i=0.25)
v = voltage(i=0.25, r=r)
i = current(v=10, r=r)

print(f"The voltage is {v} V")
print(f"The current is {i} A")
print(f"The resistance is {r} ohms")
User's first name: Goldy
User's last name: Gopher
User's age in months: 480
The voltage is 10.0 V
The current is 0.25 A
The resistance is 40.0 ohms

The , character after each function in the from ohms_law import line allows us to list multiple objects to be imported from a common module. We could have written the from - import scheme two times over with each function, but the above example is more concise. As seen above, all functions work and now are deployable in our code!


It is important to document how your function operates in case another user (or even yourself) would like to reuse your function on a later date. A docstring is a string that follows after the function definition statement (i.e., the line with def) and provides a place to describe what a function does, the arguments that are passed into the function, details about the arguments, and the function returns.

Docstrings are built into all functions in Python. To create a docstring we use the multi-line string notation (i.e., a str that starts and ends with three single or double quotes) on the line after the def line. The code below demonstrates how a docstring can be used with our convert_temp() function from earlier:

As seen above, docstrings utilize the multi-line string format in which the string content is bounded between triple quotes (here we used """). As a side note, besides being used for docstrings, the multi-line string format is a useful format whenever you need to create a string that needs to span multiple lines of code:

multi_line_string = """
We can type
multiple lines
in a triple quoted string.
"""
print(multi_line_string)

We can type
multiple lines
in a triple quoted string.

help(convert_temp)
Help on function convert_temp in module __main__:

convert_temp(temp, input_unit='F', output_unit='K')
    Convert temperature from one unit to another. Returns temperature and
    output_unit.

    Arguments:
    temp: Numeric temperature to be converted
    input_unit: String designating the input unit. Can be one of 'F', 'C',
        'R', 'K'. Default is 'F'
    output_unit: String designating the output unit. Can be one of 'F', 'C',
        'R', 'K'. Default is 'K'

    Returns:
    new_temp: New temperature
    output_unit: Unit of the new temperature

There are numerous ways to format a docstring. Sometimes you may see docstrings in this format:

def convert_temp(temp, input_unit='F', output_unit='K'):
    """
    Convert temperature from one unit to another. Returns temperature and 
    output_unit.
    
    :param temp: Temperature to converted.
    :type: float

    :param input_unit: Units of input temperature. Can be one of 'F', 
    'C', 'R', 'K'. Default is 'F'.
    :type: str

    :param output_unit: String designating the output unit. Can be one of 'F', 
    'C', 'R', 'K'. Default is 'K'.
    :type: str

    :return new_temp: Converted temperature.
    :type: float

    :return output_unit: Unit of converted temperature.
    :type: str
    """
    
    # Convert input temp to Kelvin
    if input_unit == "F":    
        temp_k = (temp - 32) * (5 / 9) + 273.15
    elif input_unit == "C":
        temp_k = temp + 273.15
    elif input_unit == "R":
        temp_k = temp * (5 / 9)
    
    # Convert Kelvin to desired output
    if output_unit == "F":
        new_temp = ((temp_k - 273.15) * (9 / 5)) + 32 
    elif output_unit == "C":
        new_temp = temp_k - 273.15
    elif output_unit == "R":
        new_temp = temp_k * (9 / 5)
    elif output_unit == "K":
        new_temp = temp_k
        
    return new_temp, output_unit

This is an example of the ReStructuredText format for styling docstrings. By developing a docstring format, documentation generators can automatically create documentation based on the docstring of the functions and classes. It is important to note the ReStructuredText keywords :param, :type, and :return: are not part of the function definition, they are just helpful hints for documentation generation.

Another ReStructuredText based-docstring style uses indents and underscores to define input arguments and returns. Below is the convert_temp_docs() function from above but with this alternative formatting schema:

def convert_temp(temp, input_unit='F', output_unit='K'):
    """
    Convert temperature from one unit to another. Returns temperature and 
    output_unit.
    
    Parameters
    ----------
    temp : float
        Temperature to converted.

    input_unit : str
        Units of input temperature. Can be one of 'F', 'C', 'R', 'K'. 
        Default is 'F'.

    output_unit : str
        String designating the output unit. Can be one of 'F', 'C', 'R', 'K'. 
        Default is 'K'.

    Returns
    -------
    new_temp : float 
        Converted temperature.

    output_unit : str
        Unit of converted temperature.
    """
    
    # Convert input temp to Kelvin
    if input_unit == "F":    
        temp_k = (temp - 32) * (5 / 9) + 273.15
    elif input_unit == "C":
        temp_k = temp + 273.15
    elif input_unit == "R":
        temp_k = temp * (5 / 9)
    
    # Convert Kelvin to desired output
    if output_unit == "F":
        new_temp = ((temp_k - 273.15) * (9 / 5)) + 32 
    elif output_unit == "C":
        new_temp = temp_k - 273.15
    elif output_unit == "R":
        new_temp = temp_k * (9 / 5)
    elif output_unit == "K":
        new_temp = temp_k
        
    return new_temp, output_unit

There are several other style guides that exist for Python docstrings, and you can search for them online to find one that works for you. By following the rules of a docstring style guide will allow clear and readable documentation of your code.

Using what you have learned so far with this guide, create a function that converts a force between one of the three most commonly used force units (i.e., newtons (N), pound-force (lbf), and dyne (dyn)). The conversion between the three unit scales is,

1 N = 0.225 lbf = 100,000 dyn

Input arguments should include the starting force’ value, the starting force’s units, and the desired units. Set default values for the initial units and the final units to be newtons and pound-force, respectively. Include a way for the function to display an error if the starting or desired units are not one of the three units. Finally, include a docstring that documents the function.


Solution:

There are a few ways to code this function. The example code below builds off of the earlier temperature conversion function by first converting the starting force to newtons and then converting to the desired force. A chain of if, elif, and else commands are used for the logic checks. Error handling is done by passing strings through the function that state that an error has been caused. These can be registered by viewing the returned variables. While this is works for our needs, there are more effective ways to address error states using logging reports called tracebacks. An upcoming lesson will show you how to utilize tracebacks for documenting errors. Finally, note the use of the ReStructuredText documentation format for the docstring. While not mandatory when creating function, adding a docstring, even if it is written in plain text, is useful for explaining how function operates.

def convert_force(initial_force, initial_units="N", converted_units="lbf"):
    """
    Convert force between newtons, pound force, and dynes. Returns the 
    converted force and units.

    Parameters
    ----------
    initial_force : float
        Force to be converted.

    initial_units : str
        Initial units of force. Allowable values are "N", "lbf", and "dyn". 
        Default value is "N"

    converted_units : str
        Units of force to be converted to. Allowable values are "N", "lbf", 
        and "dyn". Default value is "N"

    Returns
    -------
    converted_force : float 
        Converted force value.

    converted_units : str
        Units of converted force. Default value is "lbf".
    """
    
    # Convert force to N to standardize
    if initial_units == "N":
        force_N = initial_force
    elif initial_units == "lbf":
        force_N = initial_force / 0.225
    elif initial_units == "dyn":
        force_N = initial_force / 100000
    else:
        force_N = "\"incorrect initial force units\""

    # Convert to new units
    if force_N == "\"incorrect initial force units\"":
        converted_force = force_N
    elif converted_units == "N":
        converted_force = force_N
    elif converted_units == "lbf":
        converted_force = force_N * 0.225
    elif converted_units == "dyn":
        converted_force = force_N * 100000
    else:
        converted_force = "\"incorrect converted force units\""
        converted_units = ""
    
    return converted_force, converted_units

To test the basic “functionality” of this function, the code below converts 10 N of force to pounds-force. We should get 2.25 lbf.

converted_force, converted_units = convert_force(10)

print(f"The converted force is {converted_force} {converted_units}.")
The converted force is 2.25 lbf.

Since we are using the default values for initial_units and converted_units, we do not need to include them in the argument list. For readability, however, it is sometimes better to include them:

converted_force, converted_units = convert_force(10, 
                                                 initial_units="N", 
                                                 converted_units="lbf")

print(f"The converted force is {converted_force} {converted_units}.")
The converted force is 2.25 lbf.

Let’s now try converting 2,536.2 dyn to newtons. We should get 0.025362 N, which is shown in the code below:

converted_force, converted_units = convert_force(2536.2, 
                                                initial_units="dyn", 
                                                converted_units="N")

print(f"The converted force is {converted_force} {converted_units}.")
The converted force is 0.025362 N.

The code block below demonstrates how the function handles an incorrect desired unit:

converted_force, converted_units = convert_force(2536.2, 
                                                 initial_units="dyn", 
                                                 converted_units="bad")

print(f"The converted force is {converted_force} {converted_units}.")
The converted force is "incorrect converted force units" .

Finally, can issue the help() command to prove that the docstring works correctly:

help(convert_force)
Help on function convert_force in module __main__:

convert_force(initial_force, initial_units='N', converted_units='lbf')
    Convert force between newtons, pound force, and dynes. Returns the
    converted force and units.

    Parameters
    ----------
    initial_force : float
        Force to be converted.

    initial_units : str
        Initial units of force. Allowable values are "N", "lbf", and "dyn".
        Default value is "N"

    converted_units : str
        Units of force to be converted to. Allowable values are "N", "lbf",
        and "dyn". Default value is "N"

    Returns
    -------
    converted_force : float
        Converted force value.

    converted_units : str
        Units of converted force. Default value is "lbf".

Depending on your programming environment, your help() return may display the docstring in either plain text or as ReStructuredText format. Here, the shell returns the docstring in a plain text format.


8.5Python libraries

So far we have demonstrated how to create modules to import functions we have already created into other code. We can also import additional modules into Python made by other people.

A Python library is a collection of modules that are gathered into one package. There are two types of Python libraries: “built-in” libraries which are bundled along with Python and “external” libraries which are available online to download. There are over 400,000 external libraries which can be installed with a package manager. They are stored in the lib folder of the Python environment. In this section, let us explore some popular libraries.

The random library is useful for generating random numbers. It is a built-in library found in Python. Let us try importing random and utilizing some functions from it:

from random import randint, random, gauss

print("Random integer 0 - 75:", randint(0, 75))
print("Random float 0 - 1:", round(random(), 3))
print("Random float from gauss distribution with mean = 10 and std = 5:", 
      round(gauss(10, 5), 3))
Random integer 0 - 75: 26
Random float 0 - 1: 0.227
Random float from gauss distribution with mean = 10 and std = 5: 3.229

The code block above tries out a few functions from random. The first function, randint(), returns a random integer from the range of the passed min and max values. Next, the function random() is used to return a random float between 0 - 1. The last function used is gauss(), which returns a random float from a Gaussian (normal) distribution.

The math library is a simple and useful built-in library that contains multiple functions and mathematical constants. The code below shows an example of importing π\pi and ln(x)\ln(x) via the the variable pi and the function log(), respectively:

from math import pi, log

van_der_pauw_constant = pi / log(2)
print("vdP constant:", round(van_der_pauw_constant, 4))
vdP constant: 4.5324

math also has trigonometric functions so it is useful in many scientific and engineering applications!

Using both the random and math libraries, create a simple code block that displays the cosine of five random numbers between 02π0 - 2\pi. Output values should be floating point numbers with three significant figures after the decimal point. You will need to look at math library’s documentation to see how to implement a cosine function in Python.


Solution:

There are a few ways one can do this depending on your overall Python knowledge. Since we have not covered object-oriented programming or the NumPy library yet, we will use a for loop to take the cosine of five random numbers between 02π0 - 2\pi. An example code block is shown below:

from random import random
from math import cos, pi

for i in range(5):
    value = random() * (2 * pi)
    value = round(value, 3)

    output = cos(value)
    output = round(output, 3)

    print(f"The cosine of {value:.3f} is {output:.3f}.")
The cosine of 3.929 is -0.706.
The cosine of 5.742 is 0.857.
The cosine of 5.992 is 0.958.
The cosine of 2.018 is -0.432.
The cosine of 0.283 is 0.960.

The time built-in library is useful for tracking how long a program has currently run for or sleeping a Python program to pause execution. The code below imports time() and sleep() from the time library and then runs a for loop where the program delays for one second and then prints out a timestamp:

from time import time, sleep

# Get current time -> use for start time
start_time = time()

for i in range(4):
    # Sleep 1 second
    sleep(1)
    # Get a timestamp
    timestamp = time() - start_time
    print("timestamp:", round(timestamp, 4), "s")
timestamp: 1.0003 s
timestamp: 2.0014 s
timestamp: 3.0028 s
timestamp: 4.0038 s

8.5.4The datetime library

Similar to the time library, the built-in datetime library has useful functionality to parse and work with dates. The example below creates a datetime object named today and then formats the object into a string with the .strftime() datetime method.

from datetime import datetime

today = datetime.today()
print("Datetime object:", today)
today_string = today.strftime("%B %d %Y")
print("Formatted date string:", today_string)
Datetime object: 2026-09-08 21:39:03.748040
Formatted date string: September 08 2026

NumPy is not a built-in Python library, but can be quickly installed to your virtual Python environment. It is an open source library that focuses on scientific computing. NumPy is a commonly used library for many scientific applications, and we will explore this library further in a later chapter. For now, let us rewrite our example code from the math library example, but now using NumPy:

import numpy as np

van_der_pauw_constant = np.pi / np.log(2)
print("VDP constant:", round(van_der_pauw_constant, 4))
VDP constant: 4.5324

Notice in this example we introduce the as keyword in conjunction with import. The import - as pattern allows you to import something using an alias name. Here, we import the numpy library using the alias np, which is commonly done with this library to cut down on keystrokes. Therefore, we do not have to type out numpy.pi to utilize the value of π\pi, but instead we can simply type np.pi.

8.6Finding documentation

There are a few ways we can investigate what functions and constants a library offers. One way we can do this is to first import the library into Python and then pass its name through the help() function. The code block below demonstrates using the math library (note: we have turned off the output on this code block since the output is rather long!):

import math
help(math)

Furthermore, Python modules are simply Python code files, so we can look at the source file to understand how a library works. For example, the random source file can be in the Python GitHub repository. This file also exists on your computer, and when you call from random import random, you are importing the random.py file as a module.

Alternatively, you can search online to understand a library (e.g., “python docs library name”). Online documentation often will include “Getting Started” guides and tutorials to get you familiar with the library. Since many libraries have documentation that is unwieldy to display with the help() command, reading a library’s documentation is often best suited through a web browser.

8.7Final thoughts

Extending on what we learned about how to create functions, we now know how to import Python code from one file to another as a Python module. By developing a code base of Python files / modules, we can import common functionality throughout our programs and JupyterLab notebooks. This can cut down on development time and make a more manageable centralized code base. Whatever code you do make, it is important to document its functionality. Python has as a built in structure with docstrings that allows you to fully document your functions (and as you will see soon, more objects) for later understanding.

We can also import many built-in and external libraries in Python. We looked at the random, math, time, and datetime built-in libraries, and briefly introduced the external library NumPy.

There are a lot of Python libraries out there, and it can be difficult to figure out which library is the best to use or how we can go about writing our own library. But we can always run the command import this to remind ourselves of how to write beautiful Python code.

import this
The Zen of Python, by Tim Peters

Beautiful is better than ugly.
Explicit is better than implicit.
Simple is better than complex.
Complex is better than complicated.
Flat is better than nested.
Sparse is better than dense.
Readability counts.
Special cases aren't special enough to break the rules.
Although practicality beats purity.
Errors should never pass silently.
Unless explicitly silenced.
In the face of ambiguity, refuse the temptation to guess.
There should be one-- and preferably only one --obvious way to do it.
Although that way may not be obvious at first unless you're Dutch.
Now is better than never.
Although never is often better than *right* now.
If the implementation is hard to explain, it's a bad idea.
If the implementation is easy to explain, it may be a good idea.
Namespaces are one honking great idea -- let's do more of those!