Python Fundamentals: Operators, Control Flow, Functions, Strings, and Data Structures

Python Operators & Evaluation Order
  • Arithmetic Operators:

    • Basic Operations: Addition (++), Subtraction (−-), and Multiplication (∗*).

    • True Division (//): Always returns a floating-point number, even if numbers divide evenly (e.g., 7/2=3.57 / 2 = 3.5 and 4/2=2.04 / 2 = 2.0).

    • Floor Division (////): Divides and rounds down toward negative infinity (to the left on the number line), rather than rounding toward zero.

    • Positive numbers simply drop the decimal: 7//2=37 // 2 = 3.

    • Negative results round further down away from zero: −7//2=−4-7 // 2 = -4 and 11//−4=−311 // -4 = -3.

    • Modulo (%\%): Finds the remainder after floor division using the mathematical identity remainder=a−(a//b)×b\text{remainder} = a - (a // b) \times b.

    • In Python, the remainder always shares the sign of the divisor (bb).

    • Examples: 7%2=17 \% 2 = 1, whereas 11%−4=−111 \% -4 = -1 (calculated as 11−(−3×−4)=11−12=−111 - (-3 \times -4) = 11 - 12 = -1).

    • Exponentiation (∗∗**): Calculates powers (e.g., 2∗∗3=82 ** 3 = 8).

  • Operator Precedence (Order of Execution from Highest to Lowest):

    1. Parentheses: () — Always evaluated first to group operations.

    2. Exponentiation: ** — Evaluates right-to-left and binds tighter than unary negation (e.g., −3∗∗2=−(32)=−9-3 ** 2 = -(3^2) = -9, not 99; 2∗∗3∗∗2=2∗∗9=5122 ** 3 ** 2 = 2 ** 9 = 512).

    3. Unary Signs: +x, -x — Positive or negative signs applied to a single number.

    4. Multiplication, Division, Floor Division, Modulo: *, /, //, \% — Evaluated from left to right.

    5. Addition and Subtraction: +, - — Evaluated from left to right.

    6. Comparison Operators: <, <=, >, >=, ==, != — Evaluated after arithmetic.

    7. Boolean Logical Operators: Evaluated in order: not, then and, then or.

  • Chained Comparisons:

    • Python allows comparisons to be linked smoothly. An expression like 3<5<73 < 5 < 7 automatically evaluates as 3<53 < 5 and 5<75 < 7 simultaneously without requiring explicit and statements.


Boolean Logic & Short-Circuiting
  • Logical Operators:

    • not: Inverts the truth value (True becomes False). Has the highest logical precedence.

    • and: Evaluates to True only when both sides are True. It has medium precedence.

    • or: Evaluates to True if at least one side is True. It has the lowest logical precedence.

    • Precedence Rule: Because and binds tighter than or, the expression a or b and c is always evaluated as a or (b and c).

  • De Morgan's Laws:

    • Useful for simplifying negations of complex conditions:

    • not (p and q) is equivalent to (not p) or (not q).

    • not (p or q) is equivalent to (not p) and (not q).

    • Simple Rule: Distribute the not to every condition, reverse the comparisons, and switch and to or (or vice versa).

  • Short-Circuit Evaluation (Lazy Evaluation):

    • and Short-Circuit: If the left-hand condition is False, Python immediately stops and returns False without checking the right-hand condition.

    • or Short-Circuit: If the left-hand condition is True, Python immediately stops and returns True without checking the right-hand condition.

    • The Safety Guard Pattern: Used to avoid crashes like division by zero or index errors:

    • x != 0 and 10 // x > 1: If x=0x = 0, the left side is False, so the division step is never executed, preventing a ZeroDivisionError.


Control Flow: Conditionals & Branching
  • Branching Rules:

    • Statements require a colon : at the end of the line and an indented code block below.

    • Execution proceeds strictly top-to-bottom. The program enters the first branch that evaluates to True, runs its code, and exits the entire conditional structure.

    • Specific to General: Always place the strictest (most specific) condition first. Broad conditions placed too early will swallow inputs meant for later branches (e.g., checking score >= 50 before score >= 80 prevents the grade A branch from ever running).

    • In an if/elif/else construct, at most one branch runs (exactly one if an else is present).

    • Separate if statements start fresh checks, meaning multiple independent blocks can execute.

  • String Comparisons (Lexicographical / Dictionary Order):

    • Strings compare character-by-character using Unicode/ASCII numeric values.

    • Capital Letters Come First: All uppercase letters have lower numeric values than lowercase letters (e.g., "apple" < "Banana" evaluates to False because "B" comes before "a").

    • Digit Characters Are Alphabetical: Strings of numbers compare character-by-character (e.g., "10" < "9" is True because the character "1" comes before "9").

    • Prefix Rule: If one string matches the start of another, the shorter string is smaller (e.g., "cat" < "catalog" is True).


Strings: Indexing, Slicing, and Immutability
  • Indexing Rules:

    • Positive indices start at 00 from the left: 0,1,…,len(s)−10, 1, \dots, \text{len}(s) - 1.

    • Negative indices count backwards from the right: −1-1 is the last character, −2-2 is second-to-last.

    • Accessing an index that does not exist (such as s[len(s)]) raises an IndexError.

  • Slicing Syntax (s[start:stop:step]):

    • Exclusion of stop: The slice includes the character at start and stops right before stop. A slice s[a:b] extracts exactly b−ab - a characters.

    • Default Values: Omitting start starts at the beginning; omitting stop runs to the end.

    • Step Direction:

    • A positive step moves forward.

    • A negative step moves backward (e.g., s[::-1] reverses the entire string).

    • Error Safety: Unlike indexing, slicing out-of-bounds never crashes. Indices are automatically clipped to the boundaries of the string (e.g., s[10:] on a short word returns an empty string "").

    • Mismatched Steps: If start, stop, and step contradict each other (such as s[2:5:-1]), Python safely produces an empty string "".

    • Chained Slicing: Read left-to-right; each slice operates on the newly generated substring from the previous slice.

  • Immutability (Read-Only Strings):

    • Strings cannot be altered in place. Assigning to an index (e.g., s[0] = "b") triggers a TypeError.

    • To modify a string, you must build a new string using concatenation (+), repetition (*), or slices (e.g., "b" + s[1:]).


Functions, Return Mechanics, and Scope
  • Defining vs. Calling:

    • A def block merely registers instructions; nothing runs until the function is invoked by name with parentheses (e.g., func()).

    • Arguments are pass into parameters strictly by order/position, not by variable names used outside the function.

  • print() vs. return:

    • print() displays a visual value to the screen and immediately throws it away; the program cannot reuse that data.

    • return sends a computed value back to the caller so it can be stored in a variable or used in an expression.

    • return exits the function immediately. Any code placed below an executed return statement in the same block is unreachable.

    • If a function finishes without reaching an explicit return, it evaluates to None.

  • Variable Scope (LEGB Hierarchy):

    • Python searches for variable names in four levels: Local, Enclosing, Global, Built-in.

    • Variables created or assigned inside a function belong exclusively to that specific function call (local scope) and vanish when it finishes.

    • A function can read global variables freely without special keywords.

    • The Local Assignment Rule: If a variable is assigned anywhere inside a function, Python marks that variable as local for the entire function. Attempting to read that variable before the assignment line causes an UnboundLocalError.

    • Modifying Globals: To modify a global variable from inside a function, it must be explicitly declared using the global keyword (e.g., global total).


Iteration: While Loops & For Loops
  • while Loops (Conditional Loops):

    • Best used when the number of iterations is unknown in advance.

    • Requires three structural components:

    1. Initialization: Setting a starting value before the loop.

    2. Condition Check: Evaluated before every single pass.

    3. Update Step: Changing the tracking variable inside the body to move toward a terminating condition.

    • If the update step is missing or flawed, the condition remains True forever, creating an infinite loop.

  • for Loops over Sequences:

    • By Character (for ch in s): Visits each character one-by-one from left to right. Ideal when only the characters themselves are needed.

    • By Index (for i in range(len(s))): Generates integer index positions. Ideal when character positions or adjacent elements are required.

    • Look-Ahead Guard: When comparing adjacent elements (e.g., s[i] == s[i + 1]), stop the range at range(len(s) - 1) so that i + 1 does not step out-of-bounds on the final loop pass.

  • The range() Function:

    • range(stop): Produces values from 00 up to stop−1\text{stop} - 1.

    • range(start, stop, step): Starts at start, steps by step, and excludes stop.

  • Standard Accumulation Patterns:

    • Counting: Set count = 0 before the loop; increment with count += 1 inside matching conditions.

    • Building: Set out = "" before the loop. Use out = out + ch to build forwards, or out = ch + out to build backwards (reversing).

    • Flags: Set found = False before the loop. Flip to found = True when a target is found. Never add an else: found = False inside the loop, or the flag will only reflect the very last item visited.


Lists: Mutability, Aliasing, and Best Practices
  • Lists vs. Strings:

    • Lists are ordered, mutable sequences ([1, 2, 3]).

    • In-Place Mutation: Lists can be changed directly in memory using index assignment (L[1] = 7) or L.append(value) without creating a new list object.

    • List Slicing: A slice of a list (L[1:3]) copies the selected elements into a brand-new list.

  • Aliasing vs. Assignment:

    • Aliasing (L2 = L1): Does not create a copy. Both variable names point to the exact same list in memory. Mutating the list via L2 immediately alters the list seen by L1.

    • Reassignment (L2 = [4, 5, 6]): Re-points the variable L2 to an entirely new list object in memory, leaving L1 unchanged.

    • Making Independent Copies: To modify a list without affecting the original, clone it explicitly using L.copy() or a full slice L[:].

  • Lists Passed to Functions:

    • Passing a list to a function passes the reference to that object. In-place mutations (L.append(val) or L[0] = val) permanently modify the caller's original list.

    • Reassigning the parameter variable inside the function (e.g., L = [1, 2]) only changes the local pointer and does not touch the caller's original list.

  • Edge Cases & Iteration Hazards:

    • Never add or remove elements from a list while iterating over it directly with a for loop, as this causes skipped elements and unexpected behavior.

    • Guarding Averages Against ZeroDivisionError: When calculating the average of filtered values, guard with if count == 0 rather than if total == 0, because valid collections can contain negative numbers that sum to 00 (e.g., [-2, 2]).


Formulas, mathematical identities, boundary rules, and precedence that you need to memorize:

1. Arithmetic & Remainder Formulas
  • Modulo (Remainder) Identity:remainder=a−(a//b)×b\text{remainder} = a - (a // b) \times b

    • The Sign Rule: In Python, the modulo remainder always takes the sign of the divisor (bb).

    • Examples: 7%2=17 \% 2 = 1, but 11%−4=−111 \% -4 = -1 (calculated as 11−(−3×−4)=11−12=−111 - (-3 \times -4) = 11 - 12 = -1).

  • Floor Division (////) Rule:a//b=⌊a/b⌋a // b = \lfloor a / b \rfloor

    • Divides and rounds down toward negative infinity (left on the number line), not toward zero.

    • Positive: 7//2=37 // 2 = 3

    • Negative: −7//2=−4-7 // 2 = -4 and 11//−4=−311 // -4 = -3

2. Slicing & Boundary Formulas
  • Slice Length Formula:Length of s[a:b]=b−a\text{Length of } s[a:b] = b - a

    • Slicing s[start:stop] always includes start and excludes stop.

  • Index Range:

    • Positive valid indices: 00 to len(s)−1\text{len}(s) - 1

    • Last valid index: len(s)−1\text{len}(s) - 1 (or −1-1)

  • Look-Ahead Range Boundary:

    • When comparing adjacent elements s[i] == s[i + 1], the loop range formula must stop at:     range(len(s) - 1)

    • This ensures that i+1i + 1 never steps past the final index to raise an IndexError.

3. Boolean Logic & De Morgan's Laws
  • De Morgan's Law for AND:not (p and q) is mathematically equivalent to (not p) or (not q)

  • De Morgan's Law for OR:not (p or q) is mathematically equivalent to (not p) and (not q)

  • Chained Comparisons Equivalence:a<b<ca < b < c is evaluated identically to a < b and b < c

4. Operator Precedence Hierarchy (Highest to Lowest)
  1. Parentheses: ()

  2. Exponentiation: ** (Evaluates right-to-left: −3∗∗2=−(32)=−9-3 ** 2 = -(3^2) = -9; 2∗∗3∗∗2=2∗∗9=5122 ** 3 ** 2 = 2 ** 9 = 512)

  3. Unary Signs: +x, -x

  4. Multiplication, Division, Floor Division, Modulo: *, /, //, \% (Evaluated left-to-right)

  5. Addition & Subtraction: +, - (Evaluated left-to-right)

  6. Comparison Operators: <, <=, >, >=, ==, !=

  7. Logical Operators: not, then and, then or (a or b and c ≡\equiv a or (b and c))

5. Essential Syntax Idioms & Safety Guards
  • Sequence Reversal: s[::-1]

  • Independent List Copy: L.copy() or L[:] (prevents unintended aliasing mutations)

  • Average Calculation Safety Guard:if count == 0: return 0.0

    • Always guard with count == 0 rather than total == 0, because valid non-empty lists can sum to zero (e.g., [-2, 2]).