Functions
Introduction
This chapter refactors the date processing code from the previous chapter so that it makes proper use of functions. Breaking logic into small, well-named functions improves readability and makes code reusable — instead of one long script, you get a toolkit of functions that each do one clearly defined job.
You'll also look at default arguments, which let a function parameter fall back to a sensible value when the caller doesn't supply one, and (optionally) variadic functions, which accept an arbitrary number of positional arguments using *args.
Concepts
Defining functions
def is_leap_year(year):
return (year % 4 == 0 and year % 100 != 0) or (year % 400 == 0)
def get_days_in_month(month, year):
days = [31, 29 if is_leap_year(year) else 28, 31, 30, 31, 30,
31, 31, 30, 31, 30, 31]
return days[month - 1]
Default arguments
def greet(name, greeting="Hello"):
print(f"{greeting}, {name}!")
greet("Alice") # Hello, Alice!
greet("Bob", "Hi there") # Hi there, Bob!
Variadic functions (*args and **kwargs)
def display_special_dates(month, year, *days, verbose=False):
for day in days:
if verbose:
print(f"{day} of month {month}, {year}")
else:
print(f"{day:02d}/{month:02d}/{year}")
display_special_dates(12, 2024, 25, 26, 31, verbose=True)
display_special_dates(12, 2024, 25, 26, 31)
Examples
def is_leap_year(year):
return (year % 4 == 0 and year % 100 != 0) or (year % 400 == 0)
def get_days_in_month(month, year):
days = [31, 29 if is_leap_year(year) else 28, 31, 30, 31, 30,
31, 31, 30, 31, 30, 31]
return days[month - 1]
def is_valid_date(day, month, year):
if month < 1 or month > 12:
return False
if year < 0 or year > 2099:
return False
return 1 <= day <= get_days_in_month(month, year)
def get_month_name(month):
names = ["January", "February", "March", "April", "May", "June",
"July", "August", "September", "October", "November", "December"]
return names[month - 1]
def get_day_suffix(day):
if 11 <= day <= 13:
return "th"
return {1: "st", 2: "nd", 3: "rd"}.get(day % 10, "th")
def display_all_dates_in_month(month, year, verbose=False):
for day in range(1, get_days_in_month(month, year) + 1):
if verbose:
print(f"{day}{get_day_suffix(day)} {get_month_name(month)} {year}")
else:
print(f"{day:02d}/{month:02d}/{year}")
display_all_dates_in_month(2, 2016, verbose=True)
Challenges
1. Refactor the date code into functions
- Refactor last chapter's date processing code into the following functions, and call them from your client code:
is_leap_year(year)— returnsTrue/False.get_days_in_month(month, year)— returns the number of days in the month.is_valid_date(day, month, year)— returnsTrue/False.get_month_name(month)— returns the name of the month.get_day_suffix(day)— returns"st","nd","rd", or"th"as appropriate.display_all_dates_in_month(month, year)— displays all the dates in that month/year.
- Run your program and verify each function works correctly in isolation.
💡 Show Answer
def is_leap_year(year):
return (year % 4 == 0 and year % 100 != 0) or (year % 400 == 0)
def get_days_in_month(month, year):
days = [31, 29 if is_leap_year(year) else 28, 31, 30, 31, 30,
31, 31, 30, 31, 30, 31]
return days[month - 1]
def is_valid_date(day, month, year):
if month < 1 or month > 12:
return False
if year < 0 or year > 2099:
return False
return 1 <= day <= get_days_in_month(month, year)
def get_month_name(month):
names = ["January", "February", "March", "April", "May", "June",
"July", "August", "September", "October", "November", "December"]
return names[month - 1]
def get_day_suffix(day):
if 11 <= day <= 13:
return "th"
return {1: "st", 2: "nd", 3: "rd"}.get(day % 10, "th")
def display_all_dates_in_month(month, year):
for day in range(1, get_days_in_month(month, year) + 1):
print(f"{day}{get_day_suffix(day)} {get_month_name(month)} {year}")
display_all_dates_in_month(2, 2016)
2. Default arguments
- Enhance
display_all_dates_in_month()so it takes an optionalverboseargument, defaulting toFalse. - When
verboseisTrue, print dates like1st February 2016; otherwise print01/02/2016. - Call the function both with and without the
verboseargument to confirm the default works.
💡 Show Answer
def display_all_dates_in_month(month, year, verbose=False):
for day in range(1, get_days_in_month(month, year) + 1):
if verbose:
print(f"{day}{get_day_suffix(day)} {get_month_name(month)} {year}")
else:
print(f"{day:02d}/{month:02d}/{year}")
display_all_dates_in_month(2, 2016) # non-verbose
display_all_dates_in_month(2, 2016, verbose=True) # verbose
3. Variadic function for special dates
- Write a function
display_special_dates_in_month(month, year, *days, verbose=False)that displays a variable number of specific days in a month (e.g. birthdays or anniversaries), in verbose or non-verbose format. - Call it with several day numbers, with and without
verbose=True.
💡 Show Answer
def display_special_dates_in_month(month, year, *days, verbose=False):
for day in days:
if verbose:
print(f"{day}{get_day_suffix(day)} {get_month_name(month)} {year}")
else:
print(f"{day:02d}/{month:02d}/{year}")
display_special_dates_in_month(12, 2024, 3, 25, 26, 31, verbose=True)
display_special_dates_in_month(12, 2024, 3, 25, 26, 31)