Week 00

Workload expectation

In general, each credit hour in a 100-200 level course will require an average of 3 hours per week spent on work for that course, outside of the synchronous meeting time.

Homework

Homework is due 7 days after assigned. No extensions.

Learning to program takes time. Sometimes you read a problem, write some code, and try to figure out why the code doesn’t work. You struggle with it, and go to the next problem, you sleep on it, you wrestle with it again, you go to tutoring or office hours, and you finally figure it out. To some, the process of debugging code comes naturally, for other you will need to build up a new set of skills.

Beginning to Program in Python

1def our_first_program():
2  print("hello world!")
3
4our_first_program()

Dissecting a simple program

  • def: a reserved string.

  • hour_first_program a function name.

  • () an empty list of parameters.

  • print another reserved string.

  • "hello world": a string.

  • our_first_program(): invocation of a function.

Known words above: a, an, empty, of, another.

Syntax rules and good practices for function names

  1. Start with a Letter or Underscore: A function name must start with a letter (uppercase or lowercase) or an underscore (_). It cannot start with a number or special character.

  2. Alphanumeric and Underscore: After the first character, a function name can contain letters, numbers, and underscores. No spaces or special characters are allowed.

  3. Case-Sensitive: Function names in Python are case-sensitive, so myfunction, myFunction, and MYFUNCTION are all different functions.

  4. No Reserved Words: You cannot use Python’s reserved words as function names. These include words like if, else, while, for, return, etc.

  5. Snake Case: By convention, function names in Python are usually written in snake_case, where all letters are lowercase, and words are separated by underscores. For example, my_function_name.

  6. Avoid Leading Underscores Unless Necessary: A leading underscore in a function name (e.g., _my_function) typically indicates that the function is intended for internal use within a module or class and should not be accessed from outside. It’s a convention, not a strict rule, so it won’t prevent access, but it signals to other developers that the function is considered “private” or “protected.”

  7. Descriptive Names: It’s good practice to choose function names that clearly describe what the function does. This makes the code more readable and maintainable.

Examples of valid function names

def my_function():

def _internal_use_only():

def calculate_sum(x, y):

Examples of invalid function names

def 123_function(): # Starts with a number

def my-function(): # Contains a special character

def for(): # Uses a reserved word

Knowledge test

  • Basic Understanding

    • What does the def keyword do in Python?

    • What is the purpose of the print statement in the code?

    • How would you describe the function’s name and its naming convention?

  • Function Concepts

    • What does the our_first_program function do when it is called?

    • How many parameters does the our_first_program function take? Can you explain what a parameter is?

  • Code Execution

    • What will be the output of the code when it is run?

    • In what order are the lines of code executed?

  • Indentation and Structure * Why is indentation important in defining the body of a function? * What is the significance of the colon : at the end of the function definition?

  • Application and Extension

    • Can you write a similar function that prints a different message?

    • How would you modify the our_first_program function to take a name as a parameter and print “Hello, [name]!”?

  • Understanding Errors (Advanced)

    • What would happen if you removed the indentation from line 2? Why?

    • What would happen if you tried to call the our_first_program function before defining it?

(Answers)

f-strings

An f-string is created by placing an f before the opening quotation mark of a string and including expressions inside curly braces {} within the string. For example:

name = "John"
age = 25
greeting = f"Hello, {name}! You are {age} years old."

Here, the variables name and age are directly embedded within the string, resulting in the greeting: Hello, John! You are 25 years old.

f-strings provide a concise way to include variables or expressions inside strings.By embedding values directly in the string, the code remains clean and easy to read. The expressions inside the curly braces are evaluated at runtime, allowing for dynamic string creation.

f-strings are an essential tool in Python programming, and you’ll often find them used to create clear and efficient string representations.

Variables and Boolean logic

This next example will introduce variables, boolean values, boolean logic, and function parameters.

 1def check_age(age):
 2  is_adult = age >= 18
 3  is_teenager = 13 <= age < 18
 4  print(f"Age provided: {age}")
 5  if is_adult:
 6    print("You are an adult.")
 7  elif is_teenager:
 8    print("You are a teenager.")
 9  else:
10    print("You are a child.")
11  print(f"Is adult? {is_adult}")
12  print(f"Is teenager? {is_teenager}")
13
14  check_age(20)

The function takes an input parameter, “age”, and then produces text output that changes depending on the value used. Let’s go through this example line by line.

Dissecting the code

  • Line 1: def check_age(age): - Defines a function named check_age that takes one parameter, age.

  • Line 2: is_adult = age >= 18 - Declares a boolean variable is_adult, which is True if age is 18 or older.

  • Line 3: is_teenager = 13 <= age < 18 - Declares a boolean variable is_teenager, which is True if age is between 13 and 17 (inclusive).

  • Line 4: print(f"Age provided: {age}") - Prints the provided age using an f-string.

  • Line 5: if is_adult: - Begins an if statement that checks if is_adult is True.

  • Line 6: print("You are an adult.") - Prints a message if is_adult is True.

  • Line 7: elif is_teenager: - Begins an elif statement that checks if is_teenager is True.

  • Line 8: print("You are a teenager.") - Prints a message if is_teenager is True.

  • Line 9-10: else: / print("You are a child.") - If neither is_adult nor is_teenager is True, this message is printed.

  • Line 11-12: print(f"Is adult? {is_adult}") / print(f"Is teenager? {is_teenager}") - Prints the values of the boolean variables.

  • Line 14: check_age(20) - Calls the check_age function with an argument of 20.

Knowledge test

  • What is the output of the code when check_age(20) is called?

  • How would the output change if the age provided was 15?

  • What are the boolean variables is_adult and is_teenager, and how are they used in the code?

  • Can you write a similar function that checks and prints whether a number is positive, negative, or zero?

(Answers)

Working with numbers

 1def calculate_statistics(number1: int, number2: float) -> str:
 2    """Calculates and returns a summary of statistics.
 3
 4    Args:
 5        number1 (int): An integer input.
 6        number2 (float): A decimal input.
 7
 8    Returns:
 9        str: A string summary of the statistics.
10    """
11    sum_result = number1 + number2  # Addition
12    product = number1 * number2     # Multiplication
13    is_odd = number1 % 2 == 1       # Modulo operation to check odd
14
15    summary = (f"Sum of {number1} and {number2}: {sum_result}\n"
16               f"Product of {number1} and {number2}: {product}\n"
17               f"{number1} is {'odd' if is_odd else 'even'}")
18
19    return summary
20
21print(calculate_statistics(3, 4.5))

Dissecting the code

  • Line 1: def calculate_statistics(number1: int, number2: float) -> str: - Defines a function with type annotations for the parameters and return type. This improves code readability and helps tools provide better analysis.

  • Line 2-10: These lines form the docstring, which provides a detailed explanation of the function, its parameters, and its return value. This is essential for understanding the function’s purpose and usage.

  • Line 11: sum_result = number1 + number2  # Addition - Calculates the sum of number1 and number2 and stores it in sum_result. The inline comment explains the operation.

  • Line 12: product = number1 * number2     # Multiplication - Calculates the product of the numbers.

  • Line 13: is_odd = number1 % 2 == 1       # Modulo operation to check odd - Uses the modulo operator % to check if number1 is odd. If number1 divided by 2 has a remainder of 1, it’s odd.

  • Line 15-17: Constructs a summary string that includes the results of the previous calculations and whether number1 is odd or even.

  • Line 19: return summary - Returns the summary string.

  • Line 21: print(calculate_statistics(3, 4.5)) - Calls the function with an integer and a float, and prints the result.

Knowledge test

  • What is the purpose of the type annotations in the function definition, and how are they used?

  • What is a docstring, and why is it included in the code?

  • How does the code determine if number1 is odd or even?

  • What will be the output of the code when calculate_statistics(3, 4.5) is called?

(Answers)

Booleans and truth tables

Booleans are a fundamental data type in programming that represents one of two values: True or False. They are named after George Boole, who first defined an algebraic system of logic in the mid-1800s.

In Python, booleans are often used to represent the results of logical comparisons, such as equality or inequality tests.

x = 10
y = 5
is_greater = x > y  # Evaluates to True
is_equal = x == y   # Evaluates to False

Truth Tables

A truth table is a mathematical table used to represent the values of logical expressions based on their possible inputs. It’s a systematic way to list every possible combination of truth values for a given logical operation, such as AND, OR, and NOT.

Here are the truth tables for the three basic logical operations:

(A AND B) Truth Table: both must be true

A

B

A AND B

True

True

True

True

False

False

False

True

False

False

False

False

(A OR B) Truth Table: at least one must be true

A

B

A OR B

True

True

True

True

False

True

False

True

True

False

False

False

(NOT A) Truth Table: reverses the truth value

A

NOT A

True

False

False

True

Booleans and truth tables are fundamental to understanding logic in programming. They form the basis of conditional statements and control flow, allowing programs to make decisions and perform different actions depending on certain conditions. Understanding these concepts will enable you to write more complex and dynamic code. ### What does it mean for something to be evaluated in a boolean context: Truthiness and Falsiness

In programming, the concepts of truthiness and falsiness refer to how non-boolean values are treated when evaluated in a boolean context. Essentially, truthiness and falsiness determine how values are interpreted as True or False when used in conditions, such as in an if statement.

Truthiness and Falsiness in General

  • Truthiness: A value is considered “truthy” if it evaluates to True in a boolean context, even if it is not explicitly the boolean value True.

  • Falsiness: Conversely, a value is considered “falsy” if it evaluates to False in a boolean context, even if it is not explicitly the boolean value False.

Different programming languages have different rules for what values are considered truthy or falsy.

Truthiness and Falsiness in Python

In Python, the following values are considered falsy:

  • None

  • False

  • Zero of any numeric type, such as 0, 0.0, 0j

  • Any empty sequence, such as ''`, ``[], ()

  • Any empty mapping, such as {}

  • Custom objects that implement a __bool__() or __len__() method that returns False or 0

All other values are considered truthy.

if 'hello':           # Truthy, because the string is not empty
    print('True')

if []:                # Falsy, because the list is empty
    print('True')
else:
    print('False')

if 42:                # Truthy, because the number is not zero
    print('True')

if 0.0:               # Falsy, because the number is zero
    print('True')
else:
    print('False')

Understanding truthiness and falsiness is vital for writing conditional statements and working with logical operations. It allows you to leverage non-boolean values in conditions and can lead to more concise and expressive code. However, it’s essential to be aware of these rules, as unexpected truthiness or falsiness can lead to subtle bugs in a program.

Variable Naming Rules in Python

Variables are used to store data in a program, and their names should be chosen to convey meaning. In Python, the rules for naming variables are:

  1. Start with a Letter or Underscore: Variable names must start with a letter (either uppercase or lowercase) or an underscore.

  2. Contain Only Alphanumeric Characters and Underscores: The rest of the name can consist of letters, numbers, and underscores.

  3. Cannot Be a Reserved Word: Python’s reserved words, such as if, for, while, cannot be used as variable names.

  4. Case-Sensitive: Variable names are case-sensitive, so myVariable and myvariable are different.

  5. Conventions: By convention, variable names are written in snake_case (e.g., my_variable), and constants are written in UPPERCASE.

Common Data Types in Python

Python has several built-in data types that can be grouped into the following categories:

  1. Numeric Types: Integers (int), Floating-Point Numbers (float), Complex Numbers (complex).

  2. Sequence Types: Lists (list), Tuples (tuple), Ranges (range).

  3. Text Type: Strings (str).

  4. Mapping Type: Dictionaries (dict).

  5. Set Types: Sets (set), Frozen Sets (frozenset).

  6. Boolean Type: Boolean (bool), with values True or False.

  7. Binary Types: Bytes (bytes), Byte Arrays (bytearray).

Type Annotations in Python

Type annotations are used to indicate the expected type of a variable, parameter, or return value. They enhance code readability and can help with error checking.

Here’s how you can use type annotations for different types:

  • Integers: x: int = 10

  • Floats: y: float = 5.5

  • Strings: name: str = "Alice"

  • Lists: numbers: list[int] = [1, 2, 3]

  • Dictionaries: mapping: dict[str, int] = {'a': 1}

  • Booleans: is_active: bool = True

  • Function Parameters and Return Types:

def add(x: int, y: int) -> int:
  return x + y

Understanding the rules for naming variables, the common types in Python, and how to use type annotations will enable you to write clear, expressive, and maintainable code. It’s an essential foundation for programming in Python.

Introduction to Data Structures

Data structures are a way of organizing and storing data in a computer. They provide a means to manage large amounts of data efficiently for uses such as large databases and internet indexing services. In Python, two fundamental data structures are lists and dictionaries.

Lists

Lists are ordered collections of items and are one of the most versatile data structures in Python. They can contain items of different types, but usually, all the items in a list are of the same type.

Dictionaries

Dictionaries are unordered collections where data is stored in key-value pairs. Keys must be unique and immutable, while values can be of any type.

def add_to_list(lst: list, item: int) -> None:
  """Add an item to the list."""
  lst.append(item)

 def read_from_list(lst: list, index: int) -> int:
     """Read an item from the list by index."""
     return lst[index]

 def update_list(lst: list, index: int, item: int) -> None:
     """Update an item in the list by index."""
     lst[index] = item

 def delete_from_list(lst: list, index: int) -> None:
     """Delete an item from the list by index."""
     lst.pop(index)

 def create_dict() -> dict:
     """Create a new dictionary."""
     return {}

 def add_to_dict(dct: dict, key: str, value: int) -> None:
     """Add a key-value pair to the dictionary."""
     dct[key] = value

 def read_from_dict(dct: dict, key: str) -> int:
     """Read a value from the dictionary by key."""
     return dct[key]

 def update_dict(dct: dict, key: str, value: int) -> None:
     """Update a value in the dictionary by key."""
     dct[key] = value

 def delete_from_dict(dct: dict, key: str) -> None:
     """Delete a key-value pair from the dictionary by key."""
     del dct[key]

Knowledge test

  • What are lists and dictionaries, and how are they used in Python?

  • Explain the CRUD operations performed on lists and dictionaries in the code.

  • How can you handle situations where you attempt to read from or delete a non-existing index or key?

  • What are some use cases for using lists and dictionaries in programming?

(Answers)

Lists and Dictionary: Example Usage

Here’s a code example for a function that takes two lists as input and creates a dictionary where the keys are from the first list and the values are from the second list. The function ensures that the lists are of the same length.

 1def create_dict_from_lists(keys: list[str], values: list[int]) -> dict[str, int]:
 2     """Create a dictionary from two lists of equal length.
 3
 4     Args:
 5         keys (list[str]): A list of keys.
 6         values (list[int]): A list of values.
 7
 8     Returns:
 9         dict[str, int]: A dictionary with keys from the first list and values from the second list.
10     """
11     if len(keys) != len(values):  # Check if lists are of the same length
12         raise ValueError("Keys and values must be of the same length.")
13
14     result_dict = {}
15     for i in range(len(keys)):  # Iterate through indices of both lists
16         key = keys[i]
17         value = values[i]
18         result_dict[key] = value
19
20     return result_dict

Dissecting the code

  • Line 1: Function definition with type annotations for parameters and return type.

  • Lines 2-10: Docstring explaining the purpose, parameters, and return value of the function.

  • Line 11: An inline comment mentioning the purpose of the upcoming condition.

  • Line 12: Checks if the lengths of the keys and values lists are the same; if not, raises a ValueError.

  • Line 14: Initializes an empty dictionary to store the result.

  • Line 15: Uses a for loop to iterate through the indices of the keys list (and implicitly, the values list since they have the same length).

  • Line 16: Retrieves the key from the keys list using the current index.

  • Line 17: Retrieves the value from the values list using the current index.

  • Line 18: Assigns the value to the corresponding key in the result dictionary.

  • Line 20: Returns the created dictionary.

Knowledge test

  1. What is the purpose of the create_dict_from_lists function, and how does it ensure that the input lists are of the same length?

  2. How does the for loop work in this code, and how are the keys and values retrieved using the index?

  3. What error will be raised if the input lists have different lengths, and how can it be handled?

  4. How would you write a test case to verify that the function works correctly with valid input lists?

(Answers)

Unit tests and loops

Advanced topic – mentioned here just as an fyi

In this final section we’ll introduce loops (although you got a sneak peak last section to the _for_ loop) and unit tests. We’ll also showcase the test() function you’ll use in future homework and lab assignments.

import sys

 def test(did_pass):
     """Print the result of a test."""
     linenum = sys._getframe(1).f_lineno
     msg = "Test at line {0} {1}.".format(linenum, "ok" if did_pass else "FAILED")
     print(msg)

 def factorial(n: int) -> int:
     """Calculate the factorial of a non-negative integer.

     Args:
         n (int): A non-negative integer.

     Returns:
         int: The factorial of n.
     """
     result = 1
     for i in range(1, n + 1):  # For loop to iterate through the range
         result *= i
     return result

 def count_odds(numbers: list[int]) -> int:
     """Count the number of odd numbers in a list.

     Args:
         numbers (list[int]): A list of integers.

     Returns:
         int: The count of odd numbers in the list.
     """
     count = 0
     i = 0
     while i < len(numbers):  # While loop to iterate through the list
         if numbers[i] % 2 == 1:
             count += 1
         i += 1
     return count

 def test_suite():
     """Run the suite of tests for code in this module (this file)."""
     test(factorial(5) == 120)
     test(factorial(0) == 1)
     test(count_odds([1, 2, 3, 4, 5]) == 3)
     test(count_odds([2, 4, 6]) == 0)

 test_suite()  # Here is the call to run the tests

Dissecting the code

  • Line 1: Imports the sys module, needed to obtain the line number of the calling code in the test function.

  • Lines 3-7: Defines the test function, which takes a boolean and prints a success or failure message, including the line number of the test.

  • Lines 9-21: Defines the factorial function, which calculates the factorial of a non-negative integer using a for loop. The docstring and type annotations provide details about the function.

  • Lines 23-38: Defines the count_odds function, which counts the number of odd numbers in a list using a while loop.

  • Lines 40-46: Defines the test_suite function, which runs a series of tests to verify the functionality of the factorial and count_odds functions.

  • Line 48: Calls the test_suite function to run the tests.

Knowledge test

  1. What is the purpose of the test function, and how does it obtain the line number of the calling code?

  2. Explain the use of the for loop in the factorial function.

  3. Describe how the count_odds function uses a while loop to achieve its goal.

  4. What are the benefits of including docstrings and type annotations in the functions?

  5. How can you write a test to verify that the factorial function returns an error for negative input?

(Answers)