Daily Notes: 2025-12-13

daily
Published

December 13, 2025

ML Notes

Brief Interlude on Python

  • For debugging/testing, using python -i as in python3 -i helloworld.py is helpful because it runs the program and then enters the interactive shell afterwards.
    • This is helpful when I define a function and then experiment with this defined function
    • Also helpful when I use exception handling
  • Use snake_case for multiple word variable names instead of camelCase
    • Prefer lowercase
  • Use leading _ for “private” or internal names. This is helpful because it reduces accidental use outside a module/class. Not real access control.
    • E.g., def _internal_helper
    • from module import * will skip underscored names
  • You can add placeholders for statements to be added later with a pass such as below:
if name in namelist:
    # not evaluated yet
    pass
else:
    # statements
  • You have to close files after opening and using them, like as follows:
f = open('foo.txt','r')
# Use f
f.close()
  • You can achieve the same purpose using with and it automatically closes:
with open('foo.txt', 'r') as f:
    # automatically closes afterwards

    # for line-by-line reads
    for line in f:
        # Process the line
    
    # for reading the entire file into a strong
    data = f.read()

    # for writing into a file
    f.write('some text\n')

Exception Handling

  • Exception Handling: This is how Python deals with things going wrong. Errors are “exceptions”
  • Wrap risky code in try blocks
  • With proper exception handling, if an error/exception occurs, control jumps to an except block.
  • Without an except block, any runtime error can crash our script.
  • With an except block:
    • Long-running programs stay alive after isolated failures
    • Friendly error messages instead of tracebacks
    • Differentiate between expected issues (aka user error) and bugs
  • Importantly, the name must match the type of error you’re trying to catch (e.g., ValueError, ZeroDivisionError)
  • Exceptions have associated values that can be passed to variables (e.g., except ValueError as e)
  • Also, finally can be used for cleanup and for code that must run whether or not an exception occurs
    • Also useful for managing resources (esp. files)
try:
    result = divide(num, denom)
except ZeroDivisionError:
    print("Can't divide by zero—please try again.")
except ValueError as e: # can catch different kinds of exceptions, and prints the associated value
    print("Value error—please try again.", e)
else:
    print("Result:", result)
finally:
    cleanup_resources()
  • Here’s something I wrote, which also includes an f-string that allows me to drop values into the string with {…}
def portfolio_cost(file_name):
    total_cost = 0.0

    with open(file_name, 'r') as f:
        for line in f:
            fields = line.split()
            try:
                number_shares = float(fields[1])
                purchase_price = float(fields[2])
                total_cost += number_shares * purchase_price
            except ValueError as e:
                print(f"Couldn't parse: {line.strip()}")
                print("Reason: ", e)
    return total_cost

Objects

  • All of the basic data types (strings, integers, lists, etc.) are Objects, therefore they involve “methods” to carry out operations
  • You make your own Objects as follows (with their own methods):
class Player:
    def __init__(self, name, age):
        self.name = name
        self.age = age
        self.health = 100
    
    def move(self, points):
        self.health -= points
  • __init__ method initializes a new instance and is important for storing data attributes
  • By convention, the method is called on self and self is defined as the first argument
    • The actual name is unimportant. Just know that the object is always passed as the first argument.
    • Just conventional Python style to call it self
  • import module as m imports a module (a namespace) that are the (global) variables and functions defined in module.py
    • Everything defined with global scope can be called as m.method()
    • from module import function allows you to import certain functions from a module and put them into local scope. No need to use the module prefix module.function()
    • from module import * allows you to import all functions. Don’t use this often in practice because it impacts readability.

Data Structures

  • Three options for data structures:
    • tuples
    • dictionaries
    • classes (user-defined)
  • Tuples are a collection of values (e.g., s = (GOOG, 10, 300.20)).
    • Can be used like an array (quantity = s[1])
    • Can be unpacked into separate variables (name, quantity, price = s).
    • Immutable (s[1] = 1 throws a TypeError)
  • Dictionaries are an unordered collection of key-value pairs (e.g., s = {‘name’: GOOG, …})
    • Use the key values to access (google_price = s[price])
    • Can be modified (s[price] = 320.20)
  • User-Defined Classes as defined above provide the nice clean syntax we know and love (e.g., s.name).
  • Some “advanced” variants that are worth knowing:
    • Slots: Saves memory
    • Dataclasses: Reduces coding
    • Named Tuples: Immutability/Tuple behavior
  • Why are Slots so great?
    • __slots__ are useful as a performance optimization because Python typically creates a __dict__ for the attributes of every instance of an object.
    • These per-instance dictionaries allow you to add arbitary attributes at runtime, but have high memory overhead.
    • In exchange for enumerating the attributes your instances will ever need in advance, a fixed slot layout saves the memory overhead of per-instance dictionaries.
    • This is especially useful when you have many objects.
class Player:
    __slots__('name', 'age', 'health')
    def __init__(self, name, age):
        self.name = name
        self.age = age
        self.health = 100
  • Why are Dataclasses so great?
    • It’s just easier… it helps make boilerplate-free classes.
    • Python auto-generates helpers like __init__
    • You do need to annotate the types (even if it’s just Any)
from dataclasses import dataclass

@dataclass
class Player:
    name: str
    age: int
    health: int
  • Why are Named Tuples so great?
    • Dataclasses with guaranteed immutability.
    • The most important feature is Immutability, which allows you to safely use them as dict keys
    • Other features for tuples (indexing, etc.) are useful here too.
import typing

class Player(typing.NamedTuple):
    name: str
    age: int
    health: int

Personal Notes

  • I’m feeling a lot of anxiety to start “doing” rather than just consuming information, step by step, from a textbook. I’ve been coding more aggressively as a result.
  • A reminder to myself on how learning via a textbook should feel:
    • like pieces of a puzzle retroactively coming together
    • like previous experiences making far more sense in context
    • like seeing in the light what I once grasped around for in the dark
  • I’m trying to avoid tutorial hell by building until I get stuck, and grasping around for the necessary context as I go.

Questions I still have

Tomorrow’s plan