Functions, Files, and Scripts
Module Overview
In this module, we're going to learn how to turn the Python syntax we've already learned into something that can actually do useful work. So far, we've been working with small snippets of code in notebooks, but real-world Python programs usually live in files and scripts that can be run again and again. In this lesson, we'll explore how to structure code into functions, work with external files, and build scripts that perform useful tasks.
In this module, you will learn:
- How to create and use functions to organize reusable code
- How to write Python scripts and run them from the terminal
- How to read data from files and work with that data in your programs
- How to use modules and imports to add new functionality to your code
- How to build a small project that combines these ideas into one workflow
Session Setup
During this session, we're going to use more of the features in our Google Colab environment. If you used Colab for the first session's notebook, you can keep that same notebook open. If not, you can start a new Colab session by following this link: New Notebook
(Alternatively, if you chose to set up your local Python coding environment, you can follow along there. If you're not working with a Linux system, some of the command line instructions may be different.)
We won't actually need the notebook for most of this session (you can still use it for scratch work), so we're going to configure our view by opening a Terminal tab and expanding it so that it takes up most of our screen. The Terminal button can be found in the bottom-left corner of the screen:
When you're done, your Colab window might look something like this:
What is a terminal? Quick command line demo
A terminal is a text-based user interface that lets you interact with your computer by typing commands instead of clicking through menus. It is especially useful for running scripts, navigating folders, and managing files in a fast and repeatable way.
Here are some basic bash commands that you can run from the command line:
Where am I? Use pwd to print your working directory—the folder you're currently in
We are currently inside the /content folder, which is the default folder for the Colab environment.
What's in here? Use ls to list all of the files in the current directory
Running ls from the /content directory tells us that the only thing inside our current working directory is another directory called sample_data/. To quickly see what's inside this nested directory, we can pass it to the ls command like this:
ls sample_data/
This lists all of the files in that lower directory without having to navigate to it:
This sample_data/ folder contains some popular practice datasets for machine learning, which Colab includes in the standard environment.
Similarly, we can list the files in a higher-level directory than our current working directory by using ls ..
Download the practice files by cloning the workshop repo
In your terminal window, paste and run the following command:
git clone https://github.com/Launchpad-Analytics/Workshop-IntroToPython.git
Run the following command to navigate to the folder containing our lab files for today:
cd Workshop-IntroToPython/s310/day2/
While you won't need this installation step until later, you can install the requirements now if you choose:
pip install -r requirements.txt
Creating our first script:
In this context, a script is a file that stores Python instructions so they can be run in order whenever needed. Instead of typing commands one at a time in a notebook or terminal, a script lets you save and reuse an entire workflow in a single file.
Using the same terminal window, we're going to create an empty Python file with the following command:
touch good_morning.py
After a brief moment, your new Python file should appear in the list of files on the left. Double-click the script file to open it in a new tab. The terminal tab will be hidden behind this new file, but you can drag it down below the script file to split the window if you prefer:
To start, we're going to create as basic a script as we can. We're going to create a single string variable called name, and then print a custom greeting message using whatever name is passed to that variable:
# good_morning.py
name = "Kelly"
print("Good Morning, " + name + "!")
Next, we're going to run our script with the following command in our terminal:
python good_morning.py
After running it, our output should look like this:
There are often many different ways to achieve the same result in programming, and each method may be better suited to what you want to accomplish. The concatenation of the text strings to build our message works well, but we can also use what's called an f-string to create better string templates and embed values directly. We're going to do that now with our print statement:
name = "Kelly"
print(f"Good Morning, {name}!")
Example Build: Personal Assistant Wake-up Message
In this scenario, we're programming the software for a personal home-tech product: a digital panel that displays weather information, news, and other customizable widgets at a glance. The feature we'll be coding is the "Wake-up Routine," which plays after the user's morning alarm goes off and gives them the day's weather and news headlines.
Adding the first function: customizing the greeting message
Using the same good_morning.py file, our first function will customize the greeting message based on the user's name. The function will be called greeting(), and it will take the user's name as its single argument:
name = "Kelly"
def greeting(user_name):
print(f"Good Morning, {user_name}!")
greeting(name)
Remember: after you create a function, you must call it in your script for the code to run.
Save your script and run it again from the terminal to verify that the greeting message still displays. You will repeat this process after every new element you add to your script:
python good_morning.py
Using the built-in datetime module to display the current date and time
The next step for our wake-up routine is to give the user the date and the current time, like this:
Good Morning, Kelly!
Today is Wednesday, July 29th
The time is 8:34 AM
To get the current date and time, we will use the datetime module, which is a built-in module of code that comes included with Python and contains several functions for handling more complex tasks. To use those functions in our script, we first need to use an import statement to include the datetime module in our code. We will also need another module, zoneinfo, to get information about our specific timezone:
from datetime import datetime
from zoneinfo import ZoneInfo
What is a datetime?
A datetime is a Python object that represents both a date and a time, making it possible to work with calendar and clock information in a structured way. This is helpful when you want to display the current date, compare times, or format output for users.
Using strftime to format the date and time
Using the datetime.now() function, we're going to grab the current datetime (using New York City as our location reference) and save it to a variable called now_ny:
now_ny = datetime.now(ZoneInfo("America/New_York"))
Because now_ny is a variable of type datetime, it now has all of the methods included in the datetime module. One of those methods is the .strftime() method, which formats the datetime into a string based on a custom-defined format. The format we want our date to be in is [Weekday], [Month] [Day], [Year], so the format template we're going to pass to .strftime() is '%A, %B %d, %Y':
today = f"Today is {now_ny.strftime('%A, %B %d, %Y')}"
You don't need to memorize what each format code means, because you can often look up the information when you're formatting datetimes for a specific task. This post is an example resource you can use to look up the right format codes you need. We'll use the same method to create a variable called current_time for the time, and then add both statements to the greeting message.
script checkpoint:
good_morning.py
from datetime import datetime
from zoneinfo import ZoneInfo
import time
def greeting(name):
# Build and return the greeting message.
greeting = f"Good Morning, {name}!"
now_ny = datetime.now(ZoneInfo("America/New_York"))
today = f"Today is {now_ny.strftime('%A, %B %d, %Y')}"
current_time = f"The time is {now_ny.strftime('%I:%M %p')}"
print(greeting)
time.sleep(1)
print(today)
time.sleep(1)
print(current_time)
greeting("Kelly")
Run the good_morning.py script in your terminal to verify that it works.
We've also added the time.sleep() function to the script to make the output appear to have a more natural delay.
Code Improvement: Using lists and loops to optimize our code
Notice the repeated print() and time.sleep() function calls. While it's manageable for the short list of three messages we currently have, it will quickly become slow and inefficient once the number of messages starts to grow. A more efficient way to code this functionality is to create a list of all of the messages we want to send, and then create a for loop to print each message to the console:
messages = [greeting, today, current_time]
for m in messages:
time.sleep(2)
print(m)
greeting("Kelly")
Code Improvement: Returning values instead of printing them
So far, we've been printing our messages to the console, which we've been using as our display screen while testing our code. The problem is that these test statements don't let us do anything with this data, such as pass it off to the hardware component that would display these messages on the product screen. So, the first major functionality improvement we're going to make to this script is to return a list containing our greeting, date, and time messages from the greeting() function instead of printing them to the console.
Now that our greeting() function gives us access to the messages as data, we will use it to generate a list of messages in a variable called message_list and implement similar logic:
script checkpoint:
good_morning.py
from datetime import datetime
from zoneinfo import ZoneInfo
import time
def greeting(name):
greeting = f"Good Morning, {name}!"
now_ny = datetime.now(ZoneInfo("America/New_York"))
today = f"Today is {now_ny.strftime('%A, %B %d, %Y')}"
current_time = f"The time is {now_ny.strftime('%I:%M %p')}"
messages = [greeting, today, current_time]
return messages
message_list = greeting("Kelly")
for m in message_list:
time.sleep(2)
print(m)
Code Improvement: One Task Per Function
We've expanded our initial greeting function to also include information about the time and date, but what if we just wanted the simple greeting by itself? With this function, we would have to parse out the greeting from the rest of the date and time information, which would quickly show us why it would be better to have a separate function for each logical piece of information.
We're going to keep the greeting message in the greeting() function, but move the time and date messages to a new function called time_and_date() that returns the today and current_time variables:
def greeting(name):
greeting_message = f"Good Morning, {name}!"
return greeting_message
def time_and_date(time_zone):
now_ny = datetime.now(ZoneInfo(time_zone))
today = f"Today is {now_ny.strftime('%A, %B %d, %Y')}"
current_time = f"The time is {now_ny.strftime('%I:%M %p')}"
return [today, current_time]
Adding Weather and News Message Functions
Copy the following two functions and add them to your script below the time_and_date() function:
def get_weather(location):
# Use the weather helper functions to create a weather update message.
weather_info = None
weather_message = f"The current temperature in {location} is {None} with a high of {None} and a low of {None}"
return weather_message
def get_headlines():
# Read the saved headlines from a text file and clean each line.
with open('headlines.txt', 'r') as file:
headlines = file.readlines()
return [headline.strip() for headline in headlines]
We're going to tackle the weather information last, so for now we'll make a skeleton function that returns the message template we want for our wake-up routine. This function will take a location as an argument and return the current temperature, plus the forecasted high and low temperatures for that location.
Context Managers
The second function, get_headlines(), reads a series of news headlines (listed in headlines.txt) and returns them as a list. The with statement you see in this function is called a context manager, which is used to efficiently handle memory and resources in Python when interacting with external files. The 'r' argument being passed designates that the text file is being opened in read mode, which means that no changes can be made to the file itself. Within this code block, the list variable headlines is being created by reading each line of text and passing it into a list created by the .readlines() method.
List Comprehensions
Before we explain what's happening in this return statement, copy the with block and paste it into a blank cell in your notebook. Then call the headlines variable and observe the output. It should look something like this:
['Local Park Welcomes Record-Breaking Number of Geese after Implementing Mandatory Soft-Rock Playlist\n',
'City Council Approves $4.2M Budget Hike to Upgrade Potholes with Eco-Friendly, Biodegradable Memory Foam\n',
"Commuters Report Smoother Transit Experience Following Subway System’s Transition to 'Honor System' High-Fives\n",
"Nationwide Coffee Chain Introducing 'Deconstructed Espresso' Served via Dropper Directly onto Tongue\n",
'Regional Airport Flight Delays Plummet 40% after Replacing Boarding Queue with Mildly Competitive Musical Chairs']
When reading the text file, the code is picking up the invisible newline characters (\n) at the end of each line. These are part of a category of whitespace characters. We don't want these characters to be read incorrectly by the wake-up routine, so we need to loop through each list item and remove them. A function with this looping and cleaning logic fully written out might look like this:
def get_headlines():
with open('headlines.txt', 'r') as file:
headlines = file.readlines()
headlines_clean = []
for line in headlines:
line_clean = line.strip()
headlines_clean.append(line_clean)
return headlines_clean
In this function, the code makes it easy to see exactly what's happening to clean each line of text, but the last five lines of this function can also be collapsed into a single line of code by using a powerful tool called a list comprehension:
return [headline.strip() for headline in headlines]
Discussion: Code Readability vs. Efficiency
Readable code is easier for people to understand and maintain, while efficient code is often more concise and avoids unnecessary repetition. In practice, the best solution is usually a balance between the two: write code that is clear first, then improve it when needed to make it simpler or faster.
Code Improvement: Message List
You should now have four separate functions that return the specific pieces of information needed to build the wake-up routine message. Keeping the same message_list variable and the looping code at the bottom of our script, we're going to set message_list to an empty list, which we will use to add messages from all of our functions, along with some custom messages that we'll add to complete the wake-up routine:
name = "Kelly"
message_list = []
message_list.append(greeting(name))
message_list += time_and_date("America/New_York")
message_list.append(get_weather("Pittsburgh"))
message_list.append("Here are today's headlines:")
message_list += get_headlines()
message_list.append("Have a great day!")
for m in message_list:
time.sleep(1)
print(m)
code checkpoint:
good_morning.py
from datetime import datetime
from zoneinfo import ZoneInfo
import time
def greeting(name):
greeting_message = f"Good Morning, {name}!"
return greeting_message
def time_and_date(timezone):
now_ny = datetime.now(ZoneInfo(timezone))
today = f"Today is {now_ny.strftime('%A, %B %d, %Y')}"
current_time = f"The time is {now_ny.strftime('%I:%M %p')}"
return [today, current_time]
def get_weather(location):
weather_info = None
weather_message = f"The current temperature in {location} is {None} with a high of {None} and a low of {None}"
return weather_message
def get_headlines():
with open('headlines.txt', 'r') as file:
headlines = file.readlines()
return [headline.strip() for headline in headlines]
name = "Kelly"
message_list = []
message_list.append(greeting(name))
message_list += time_and_date("America/New_York")
message_list.append(get_weather("Pittsburgh"))
message_list.append("Here are today's headlines:")
message_list += get_headlines()
message_list.append("Have a great day!")
for m in message_list:
time.sleep(1)
print(m)
Finishing the Weather Function
Included in the workshop files is a script that outputs the current weather and the forecasted high and low temperatures for a given location. This script can be passed the name of a city from the command line using the --location flag. For example, if we wanted to get the weather in Chicago, we would run this line:
python get_weather_info.py --city Chicago
Observe the output of running this script for a city of your choice:
/content# python get_weather_info.py --city "Chicago, IL"
Weather in: Chicago, IL ([41.8755616, -87.6244212])
Current Temp: 86
Max Temp: 92
Min Temp: 76
Discussion: Inspecting the get_weather_info.py script
Open the get_weather_info.py script and explore its contents as you answer these questions:
- In plain language, what is this code doing?
- What elements of this script are similar to the one we are building?
- Which elements are different?
- How can we use this code to finish our weather message function?
get_weather_info.py:
import openmeteo_requests
import argparse
from geopy.geocoders import Nominatim
def get_city_latlong(city_name):
# Convert a city name into latitude and longitude coordinates.
geolocator = Nominatim(user_agent="my_city_geocoder")
location = geolocator.geocode(city_name)
if location:
return [location.latitude, location.longitude]
else:
return None
def weather_lookup(coords):
# Query the weather API for current and forecast temperatures.
openmeteo = openmeteo_requests.Client()
url = "https://api.open-meteo.com/v1/forecast"
params = {
"latitude": coords[0],
"longitude": coords[1],
"daily": ["temperature_2m_max", "temperature_2m_min"],
"current": "temperature_2m",
"timezone": "America/New_York",
"temperature_unit": "fahrenheit",
}
responses = openmeteo.weather_api(url, params=params)
current_temp = round(responses[0].Current().Variables(0).Value())
max_temp = round(float(responses[0].Daily().Variables(0).ValuesAsNumpy().max()))
min_temp = round(float(responses[0].Daily().Variables(1).ValuesAsNumpy().max()))
return current_temp, max_temp, min_temp
def main():
# Parse a city name from the command line and print weather results.
parser = argparse.ArgumentParser()
parser.add_argument('-c', '--city')
args = parser.parse_args()
city_coords = get_city_latlong(args.city)
current_temp, max_temp, min_temp = weather_lookup(city_coords)
print("Weather in: " + str(args.city) + " (" + str(city_coords) + ")")
print("Current Temp: " + str(current_temp))
print("Max Temp: " + str(max_temp))
print("Min Temp: " + str(min_temp))
if __name__ == "__main__":
main()
Importing weather script functions
We can treat this get_weather_info.py script as a module of functions we can use to construct our weather update for the wake-up routine. We need to use both the geolocator function and the function that gets the weather API information, so we will import both functions at the top of our script:
from get_weather_info import get_city_latlong, weather_lookup
Return to the get_weather() function from earlier. It is already being passed the location as an argument, so we just need to pass it to the get_city_latlong() function and save the results to the city_coords variable. Additionally, instead of the weather_info variable, we actually need three separate variables to capture the data coming from the weather_lookup() function. We can write a similar line of code to the one in the original script:
current_temp, max_temp, min_temp = weather_lookup(city_coords)
We now have everything we need to complete the weather message function, and we can plug the current_temp, max_temp, and min_temp variables into the string template. Our new get_weather() function should look like this:
def get_weather(location):
city_coords = get_city_latlong(location)
# weather_info = None
current_temp, max_temp, min_temp = weather_lookup(city_coords)
weather_message = f"The current temperature in {location} is {current_temp} with a high of {max_temp} and a low of {min_temp}"
return weather_message
Run your good_morning.py script to observe the real weather data being returned. Remember that we hard-coded the city of Pittsburgh in the list of messages at the bottom, but you have been given some hints to figure out how to make this value more dynamic and user-driven.
Code Improvement: Dynamic Input of User Data
with open("user_settings.json", "r") as file:
user_settings = json.load(file)
message_list = []
message_list.append(greeting(user_settings["user_name"]))
message_list += time_and_date(user_settings["user_time_zone"])
message_list.append(get_weather(user_settings["user_location"]))
message_list.append("Here are today's headlines:")
message_list += get_headlines()
message_list.append("Have a great day!")
for m in message_list:
time.sleep(1)
print(m)
Code Improvement: Last Mile
When looking at the get_weather_info.py script, we talked about the importance of the if __name__ == "__main__": expression and the main() function in a script, so the last finishing touch we'll add to this script will be to implement our core logic for constructing the message list within these blocks.
code checkpoint:
good_morning.py:
from datetime import datetime
import json
from zoneinfo import ZoneInfo
import time
from get_weather_info import get_city_latlong, weather_lookup
def greeting(name):
# Build and return the personalized greeting message.
greeting_message = f"Good Morning, {name}!"
return greeting_message
def time_and_date(timezone):
# Get the current date and time for the selected timezone.
now_ny = datetime.now(ZoneInfo(timezone))
today = f"Today is {now_ny.strftime('%A, %B %d, %Y')}"
current_time = f"The time is {now_ny.strftime('%I:%M %p')}"
return [today, current_time]
def get_weather(location):
# Use the weather helper functions to create a weather update message.
city_coords = get_city_latlong(location)
current_temp, max_temp, min_temp = weather_lookup(city_coords)
weather_message = f"The current temperature in {location} is {current_temp} with a high of {max_temp} and a low of {min_temp}"
return weather_message
def get_headlines():
# Read the saved headlines from a text file and clean each line.
with open('headlines.txt', 'r') as file:
headlines = file.readlines()
return [headline.strip() for headline in headlines]
def main():
# Load user settings and build the full wake-up message list.
with open("user_settings.json", "r") as file:
user_settings = json.load(file)
message_list = []
message_list.append(greeting(user_settings["user_name"]))
message_list += time_and_date(user_settings["user_time_zone"])
message_list.append(get_weather(user_settings["user_location"]))
message_list.append("Here are today's headlines:")
message_list += get_headlines()
message_list.append("Have a great day!")
for m in message_list:
time.sleep(1)
print(m)
if __name__ == "__main__":
main()
Wrapping Up
In this lesson, we took the Python concepts we learned earlier and used them to build a small, real-world script. We saw how functions help us break a task into smaller, reusable pieces, how scripts let us run a sequence of commands from a file, and how modules can be imported to reuse code that already exists.
We also practiced working with files, reading data from external sources, and using lists and loops to make our code more efficient. By returning values from functions instead of only printing them, we made our programs more flexible and easier to build into larger applications. Finally, we explored how to structure a script so it can run as a standalone program while still being organized and easy to understand.
These ideas—functions, files, imports, loops, and good script structure—are the foundation for writing more complex Python programs.
Continued Learning
Here are some of the main concepts we covered, along with links to learn more about each one:
- Functions: reusable blocks of code that help organize programs. Learn more: https://docs.python.org/3/tutorial/controlflow.html#defining-functions
- Files and input/output: reading from and writing to files in Python. Learn more: https://docs.python.org/3/tutorial/inputoutput.html
- Modules and imports: using built-in and external code libraries in your scripts. Learn more: https://docs.python.org/3/tutorial/modules.html
- Lists and loops: working with collections of data and repeating actions efficiently. Learn more: https://docs.python.org/3/tutorial/introduction.html#lists
- String formatting: building readable messages and output with f-strings. Learn more: https://realpython.com/python-f-strings/
- Working with dates and times: using the datetime module to handle current time and formatting. Learn more: https://docs.python.org/3/library/datetime.html
- Script structure and execution: organizing code so it can run as a standalone program. Learn more: https://realpython.com/if-name-main-python/