Skip to main content

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) — returns True/False.
    • get_days_in_month(month, year) — returns the number of days in the month.
    • is_valid_date(day, month, year) — returns True/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 optional verbose argument, defaulting to False.
  • When verbose is True, print dates like 1st February 2016; otherwise print 01/02/2016.
  • Call the function both with and without the verbose argument 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)