My Python Learning Journey: Week 5 - Steps I Took, Problems I Faced, and How I Solved Them.

My Python Learning Journey: Week 5 - Steps I Took, Problems I Faced, and How I Solved Them.

Hey everyone, in this week, I learned about the following Python concepts:


  • Type Hints.

  • Exception Handling.

  • if __name__ == “__main__”

  • Global Keyword.

  • Enumerate( ) Function.

  • List comprehension.

  • Newly Added Python Features: Walrus Operator (:=) and Match-Case Statements.

These are the steps I took to grasp the concepts:


  • I practiced type hints by rewriting my previous functions improving code readability & reducing errors.

  • Handled errors using try-except, try-except-else, and try-except-finally blocks in small projects.

  • Used the if __name__ == "__main__" condition to check if a Python file is being run directly or imported as a module.

  • Worked with the global keyword to modify global variables inside functions.

  • Solved problems with enumerate() functions to simplify loops.

  • Practiced list comprehension to create lists using a single line of code.

  • Explored the walrus operator and match-case statements with examples from Python 3.8+

These are the problems that I encountered:


  1. Use Case of Type Hints in Function Definitions: Auto-Suggestion of Methods.

  2. Type Hints Error: Mismatch Between Declared and Actual Types.

  3. Exception Handling Error: Catching the Wrong Exception Type.

  4. Enumerate( ) Function Error: Forgetting to Unpack Tuple Values.

  5. Walrus Operator Error: Misunderstanding Scope.

  6. Importance of try-except-finally block inside User-Defined Functions while Handling Errors.

This is how I solved those problems:


1. Use Case of Type Hints in Function Definitions: Auto-Suggestion of Methods:

Problem: Beginners usually get confused by the use case of type hints, especially in function definitions.

Solution: By using type hints in function definitions, we can make our code editor smarter. Type hints not only act as documentation but also enable features like auto-suggestions, which reduces errors.

def concatenate(a: int, b: str):
    # Example usage inside the function
    result = b.upper()     # IDE suggests methods related to 'str' for 'b'
    return str(a) + result

output = concatenate(123, "hello") # Calling the function
print(output)                      # Output: "123HELLO"
  • When I typed a. inside the function, my editor suggested methods related to the int type, such as bit_length() and to_bytes().

  • Similarly, when I typed b., it suggested methods related to the str type, such as upper(), lower(), and replace().

  • This happens because type hints (a: int, b: str) help the editor understand the types of the variables.

2. Type Hints Error: Mismatch Between Declared and Actual Types:

Problem: I encountered an issue when I declared type hints for a function but passed an argument of the wrong type, resulting in a runtime error.

def add_numbers(a: int, b: int) -> int:
    return a + b

result = add_numbers(5, "10")  # Passing an integer and a string
# Output: TypeError: unsupported operand type(s) for +: 'int' and 'str'

Solution: To solve this, I made sure that the arguments passed to the function matches with type hints. Additionally, I also added runtime checks to validate the input types.

def add_numbers(a: int, b: int) -> int:
    # Validate input types
    if not isinstance(a, int) or not isinstance(b, int):
        raise TypeError("Both arguments must be integers")
    return a + b

result = add_numbers(5, 10)  # Passing correct types
print(result)                # Output: 15

# Passing incorrect types (e.g., add_numbers(5, "10")) will raise a clear error message:
# Output: TypeError: Both arguments must be integers

3. Exception Handling Error: Catching the Wrong Exception Type:

Problem: I encountered an issue where I tried to catch the wrong exception type, which caused the program to crash instead of handling the error properly.

  • A division by zero raises a ZeroDivisionError, but I mistakenly tried to catch a ValueError.

  • Since the ValueError exception doesn't match the actual error (ZeroDivisionError), the program gets crashed.

try:
    result = 10 / 0  # This will raise a ZeroDivisionError
except ValueError:  # Incorrect exception type
     print("You cannot divide by zero!")

Output:

Traceback (most recent call last):
  File "<string>", line 2, in <module>
ZeroDivisionError: division by zero

Solution: To fix this error it is important to write the correct exception type as in the above case it is (ZeroDivisionError) in the except block.

try:
    result = 10 / 0  # This will raise a ZeroDivisionError
except ZeroDivisionError:  # Correct exception type
    print("You cannot divide by zero!")
# Output: You cannot divide by zero!

4. Enumerate( ) Function Error: Forgetting to Unpack Tuple Values:

Problem: While using the enumerate() function, It is a common mistake to forget to write index for the first element, which leads to the generation of tuple values as the output.

fruits = ["apple", "banana", "cherry"]

# Forgetting to unpack the tuple returned by enumerate()
for fruit in enumerate(fruits):
    print(fruit)

# Output: 
(0, 'apple')
(1, 'banana')
(2, 'cherry')

Solution: To fix this, I unpacked the tuple into two variables: one for the index and the other for the value.

fruits = ["apple", "banana", "cherry"]

# Unpacking the tuple returned by enumerate()
for index, fruit in enumerate(fruits):
    print(f"{index}: {fruit}")

# Output:
0: apple
1: banana
2: cherry

5. Walrus Operator Error: Misunderstanding Scope:

Problem: While using the Walrus operator (:=), I encountered an issue where the variable declared inside an if statement unexpectedly overwrote a variable with the same name in the outer (global) scope.

x = 10            # Global variable
if (x := 5) > 3:  # Assigns 5 to `x` using the Walrus operator
    print(x)      # Output: 5

print(x)          # Output: 5 (Global `x` is overwritten)

Solution: To avoid this issue, we can use different variable names when working with the Walrus operator, especially if a variable already exists in the global or outer scope as shown below:

x = 10            # Global variable
if (y := 5) > 3:  # Use a different variable name
    print(y)      # Output: 5

print(x)          # Output: 10 (Global `x` is not overwritten)

6. Importance of try-except-finally block inside User-Defined Functions while Handling Errors.

Problem: I found that the exact use of finally clause is inside a user-defined function for example: without the finally clause in a try-except block, essential steps like displaying a confirmation message or running critical tasks such as cleanup or logging may be skipped if the function exits early with a return keyword especially inside user-defined functions.

# Code Example (Without finally):
def main():
    try:
        a = int(input("Enter a number: "))
        print(a)
        return  # Function returns here
    except Exception as e:
        print(e)
        return  # Function also returns here

    print("Goodbye!") # This statement will never be executed due to the `return` above

main()

Solution: To solve the above error we must use the finally clause which ensures that the code runs regardless of whether the function exits due to a return statement or an exception.

# Code Example (With finally):
def main():
    try:
        a = int(input("Enter a number: "))
        print(a)
        return  # Function returns here, but `finally` still executes
    except Exception as e:
        print(e)
        return  # Even if this executes, `finally` will still run
    finally:
        print("Goodbye!")  # This will always execute, regardless of `return`

main()

These are the resources that helped me learn: