🐍 Python Syntax Mastery Study Guide

Complete Reference for Ultimate Quiz Success

📚 Study Guide Contents

🔪 Slice Assignment
Basic & step slicing, size matching, negative steps
🔄 Iteration & Loops
Range edge cases, enumerate, zip, loop patterns
⚙️ Built-in Functions
All parameters, conversions, mathematical functions
📍 Indexing & Assignment
Negative indexing, star expressions, unpacking
🏗️ Object-Oriented
MRO, class variables, properties, magic methods
🚀 Advanced Features
Generators, decorators, context managers
⚠️ Error Handling
try/except/finally, exception chaining
💥 Edge Cases
Walrus operator, dict union, floating point
🔪 Slice Assignment Mastery

📋 Basic Slice Assignment: lst[start:stop] = new_values

Replaces a range of elements. The list can grow, shrink, or stay the same size.

# Replace 2 elements with 2 (same size) lst = [1, 2, 3, 4, 5] lst[1:3] = ['a', 'b'] # [1, 'a', 'b', 4, 5] # Replace 3 elements with 1 (shrink) lst = [1, 2, 3, 4, 5] lst[1:4] = ['x'] # [1, 'x', 5] # Replace 2 elements with 4 (grow) lst = [1, 2, 3, 4, 5] lst[1:3] = ['a', 'b', 'c', 'd'] # [1, 'a', 'b', 'c', 'd', 4, 5]
Key Point: Basic slice assignment can change list length. The number of new elements doesn't need to match the slice length.

⚡ Step Slice Assignment: lst[start:stop:step] = new_values

Replaces elements at intervals. Size must match exactly!

# Replace every 2nd element (3 positions need 3 values) lst = [10, 20, 30, 40, 50] lst[::2] = [1, 2, 3] # [1, 20, 2, 40, 3] # WRONG: Size mismatch causes ValueError lst = [1, 2, 3, 4, 5] lst[::2] = [10, 20] # ValueError! 3 positions, 2 values
Critical Rule: Step slice assignment requires exact size matching. Too few or too many values = ValueError.

🔄 Negative Step Assignment

lst = ['a', 'b', 'c', 'd', 'e'] lst[::-2] = ['x', 'y', 'z'] # Replaces indices 4, 2, 0 # Result: ['z', 'b', 'y', 'd', 'x']
Remember: Negative step goes backwards. Values are assigned in reverse order of indices.

🎯 Empty Slice Insertion

lst = [1, 2, 3] lst[1:1] = ['a', 'b'] # Insert at position 1 # Result: [1, 'a', 'b', 2, 3]
🔄 Iteration & Loop Mastery

📊 Range Function Complete Guide

range(start=0, stop, step=1)
range(stop)
From 0 to stop-1
range(start, stop)
From start to stop-1
range(start, stop, step)
From start to stop-1, incrementing by step
# Basic usage list(range(5)) # [0, 1, 2, 3, 4] list(range(2, 7)) # [2, 3, 4, 5, 6] list(range(0, 10, 2)) # [0, 2, 4, 6, 8] # Negative ranges list(range(10, 0, -1)) # [10, 9, 8, 7, 6, 5, 4, 3, 2, 1] list(range(-5, 5, 2)) # [-5, -3, -1, 1, 3] # Edge cases list(range(5, 10, 0)) # ValueError: step cannot be 0 list(range(1.5, 5.5)) # TypeError: float not allowed
Critical Errors:
• step=0 → ValueError
• Float arguments → TypeError
• Large step beyond range → Single element or empty

🔢 Enumerate Function

enumerate(iterable, start=0)
iterable
Any iterable object
start
Starting number (can be negative!)
# Basic usage list(enumerate(['a', 'b', 'c'])) # [(0, 'a'), (1, 'b'), (2, 'c')] list(enumerate(['a', 'b', 'c'], start=1)) # [(1, 'a'), (2, 'b'), (3, 'c')] # Negative start (perfectly valid!) list(enumerate(['a', 'b', 'c'], start=-1)) # [(-1, 'a'), (0, 'b'), (1, 'c')] # On strings list(enumerate("hi")) # [(0, 'h'), (1, 'i')] # Empty iterable list(enumerate([])) # []

🤝 Zip Function

# Basic usage - stops at shortest list(zip([1, 2, 3], ['a', 'b'])) # [(1, 'a'), (2, 'b')] # Empty iterable makes result empty list(zip([1, 2, 3], [], ['a', 'b'])) # [] # Single argument creates single-element tuples list(zip([1, 2, 3])) # [(1,), (2,), (3,)] # Zip with strings list(zip("abc", "12")) # [('a', '1'), ('b', '2')] # Unzipping with * operator pairs = [(1, 'a'), (2, 'b'), (3, 'c')] list(zip(*pairs)) # [(1, 2, 3), ('a', 'b', 'c')]
Key Behavior: zip() always stops at the shortest iterable. Empty iterable = empty result.

🔄 Loop Patterns

# for-else: else runs if NO break occurred for i in range(3): if i == 5: # Never true break else: print("Completed") # This prints # while-else: else runs if NO break occurred x = 0 while x < 3: x += 1 if x == 2: break else: x = 100 # Doesn't run because of break print(x) # Prints 2 # Nested loops with continue result = [] for i in range(2): for j in range(3): if i == j: continue # Skip when i equals j result.append((i, j)) # Result: [(0, 1), (0, 2), (1, 0), (1, 2)]
⚙️ Built-in Functions Mastery

🔢 Type Conversion Functions

Function Parameters Example Result
int() x=0, string, base=10 int("ff", 16) 255
float() x=0.0 float("inf") inf
str() object='', encoding, errors str(b'hi', 'utf-8') 'hi'
bool() x=False bool([]) False
# int() with different bases int("1010", 2) # 10 (binary) int("ff", 16) # 255 (hexadecimal) int("77", 8) # 63 (octal) int("123", 10) # 123 (decimal - default) # Number format functions bin(10) # "0b1010" oct(64) # "0o100" hex(255) # "0xff" # Character conversions chr(65) # "A" ord('A') # 65 ord('A') - ord('a') # -32 (case difference)

📐 Mathematical Functions

# Basic math abs(-5) # 5 round(2.675, 2) # 2.67 (banker's rounding!) divmod(17, 5) # (3, 2) - quotient and remainder # Advanced power function pow(2, 3) # 8 pow(2, 3, 5) # 3 (equivalent to (2**3) % 5) # Min/Max with options max([1, 2, 3]) # 3 max([], default=42) # 42 (no ValueError!) min('apple', 'banana', key=len) # 'apple' # Sum with start value sum([1, 2, 3]) # 6 sum([1, 2, 3], 10) # 16 (starts with 10)
Banker's Rounding: round(2.675, 2) = 2.67 (rounds to nearest even). This catches many people!

🔍 Filtering and Transformation

# filter() removes falsy values when function is None list(filter(None, [0, 1, 2, '', 'hello', False])) # [1, 2, 'hello'] # map() applies function to each element list(map(str, [1, 2, 3])) # ['1', '2', '3'] list(map(lambda x, y: x + y, [1, 2], [10, 20])) # [11, 22] # sorted() with key function sorted(['apple', 'pie', 'a'], key=len) # ['a', 'pie', 'apple'] sorted([3, 1, 4], reverse=True) # [4, 3, 1] # any() and all() any([False, False, True]) # True (at least one truthy) all([True, True, False]) # False (not all truthy) any([]) # False (empty is falsy) all([]) # True (vacuously true)

🔎 Inspection Functions

# Object inspection len([1, 2, 3]) # 3 type(123) # isinstance(123, (int, float)) # True id([1, 2, 3]) # Memory address # Attribute handling hasattr(obj, 'attr') # True/False getattr(obj, 'attr', 'default') # Get with default setattr(obj, 'attr', value) # Set attribute delattr(obj, 'attr') # Delete attribute # Directory and variables dir(str) # List of string methods vars(obj) # obj.__dict__ callable(print) # True hash((1, 2)) # Hash value (tuples are hashable)

📝 String and Formatting Functions

# Advanced string methods "hello".center(10, '-') # "--hello---" "hello".ljust(10, '.') # "hello....." "hello".rjust(10, '.') # ".....hello" # String case and validation "hello".capitalize() # "Hello" "hello world".title() # "Hello World" "HELLO".lower() # "hello" "hello".upper() # "HELLO" # Format function with advanced patterns format(42, '08b') # "00101010" (8-digit binary) format(3.14159, '.2f') # "3.14" format("hello", '^10') # " hello " (centered) # Advanced f-string formatting name, width = "Python", 10 f"{name:^{width}}" # " Python " (centered with variable width)

🔧 Advanced Built-ins

# Memory and binary operations memoryview(b"hello")[1] # 101 (byte value of 'e') frozenset([1, 2, 2, 3]) # frozenset({1, 2, 3}) # Evaluation functions (use carefully!) eval("2 + 3") # 5 exec("x = 5") # Executes code # Iterator functions iter([1, 2, 3]) # Creates iterator next(iterator) # Gets next value next(iterator, 'default') # Next with default reversed([1, 2, 3]) # Reverse iterator # Slice objects s = slice(1, 5, 2) [0, 1, 2, 3, 4, 5][s] # [1, 3] (slice as object)
📍 Indexing & Variable Assignment

🎯 Indexing Behavior

# Basic indexing lst = [1, 2, 3] lst[0] # 1 (first element) lst[-1] # 3 (last element) lst[-3] # 1 (first element via negative) # Beyond bounds behavior lst[5] # IndexError: list index out of range lst[-5] # IndexError: list index out of range # Slicing is forgiving lst[10:20] # [] (empty, no error) lst[-10:-5] # [] (empty, no error) lst[::100] # [1] (large step, gets first element)
Critical Difference: Direct indexing beyond bounds = IndexError. Slicing beyond bounds = empty list.

⭐ Star Expression Unpacking

# Basic star unpacking a, *b, c = [1, 2, 3, 4, 5] # a = 1, b = [2, 3, 4], c = 5 # Multiple star expressions in assignment = SyntaxError *a, *b = [1, 2, 3, 4] # SyntaxError! # Nested unpacking (a, b), (c, d) = [(1, 2), (3, 4)] # a = 1, b = 2, c = 3, d = 4 # Insufficient values a, b, c = [1, 2] # ValueError: not enough values to unpack # Star in function calls def func(a, b, c): return a + b + c func(*[1, 2, 3]) # 6 func(**{'a': 1, 'b': 2, 'c': 3}) # 6
Rules:
• Only ONE star expression per assignment
• Star captures middle/remaining elements
• Must have enough values for non-star variables

🔗 Assignment Patterns

# Chained assignment creates shared references a = b = [1, 2, 3] a.append(4) print(b) # [1, 2, 3, 4] - same object! # Tuple assignment (simultaneous) a, b = 10, 20 a, b = b, a + b # a = 20, b = 30 (Fibonacci step) # Swapping x, y = y, x # Complex assignment combinations a, (b, c) = 1, (2, 3) # a = 1, b = 2, c = 3

🎨 Advanced Slicing Patterns

# Complex negative slicing lst = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] lst[-3::-2] # [7, 5, 3, 1] (from index 7, step -2) lst[4:1:-1] # [4, 3, 2] (backwards from 4 to 2) # String slicing s = "abcdefgh" s[1:7:2] # "bdf" (every 2nd char from index 1-6) # Slice with None (equivalent to defaults) lst[None:None:None] # Same as lst[:] - full copy # Empty ranges lst[1:1] # [] (empty slice) lst[5:2] # [] (start > stop with positive step)
🏗️ Object-Oriented Programming

🔄 Method Resolution Order (MRO)

class A: def method(self): return "A" class B: def method(self): return "B" class C(A, B): # Inherits from A first, then B pass C().method() # "A" - follows MRO: C -> A -> B print(C.__mro__) # Shows the method resolution order
MRO Rule: Python searches left-to-right in inheritance list. First match wins.

📊 Class vs Instance Variables

class MyClass: class_var = [] # Shared among ALL instances! a = MyClass() b = MyClass() a.class_var.append(1) print(b.class_var) # [1] - they share the same list! # Correct way for instance variables class MyClass: def __init__(self): self.instance_var = [] # Each instance gets its own
Mutable Class Variables: Lists, dicts, sets at class level are shared. Often a bug source!

🎯 Properties and Decorators

class Circle: def __init__(self, radius): self._radius = radius @property def radius(self): return self._radius @radius.setter def radius(self, value): if value < 0: raise ValueError("Radius cannot be negative") self._radius = value @property def area(self): # Read-only computed property return 3.14159 * self._radius ** 2 # Usage c = Circle(5) print(c.area) # 78.53975 (computed) c.radius = 10 # Uses setter # c.area = 100 # AttributeError - no setter

🎭 Magic Methods (Dunder Methods)

class Point: def __init__(self, x, y): self.x, self.y = x, y def __str__(self): # For print() return f"Point({self.x}, {self.y})" def __repr__(self): # For debugging return f"Point(x={self.x}, y={self.y})" def __add__(self, other): # For + operator return Point(self.x + other.x, self.y + other.y) def __eq__(self, other): # For == comparison return self.x == other.x and self.y == other.y def __len__(self): # For len() return int((self.x**2 + self.y**2)**0.5) # Usage p1 = Point(1, 2) p2 = Point(3, 4) print(p1) # "Point(1, 2)" p3 = p1 + p2 # Point(4, 6) print(len(p1)) # 2

🏛️ Inheritance and Super

class Animal: def __init__(self, name): self.name = name def speak(self): return f"{self.name} makes a sound" class Dog(Animal): def __init__(self, name, breed): super().__init__(name) # Call parent constructor self.breed = breed def speak(self): # Override parent method return f"{self.name} barks" # Usage dog = Dog("Buddy", "Golden Retriever") print(dog.speak()) # "Buddy barks"
🚀 Advanced Features

🔄 Generators

# Generator function def countdown(n): while n > 0: yield n n -= 1 # Generator is consumed once gen = countdown(3) print(next(gen)) # 3 print(list(gen)) # [2, 1] - remaining values # Generator with send() def echo_gen(): value = None while True: value = yield value # Receive and send back print(f"Received: {value}") gen = echo_gen() next(gen) # Prime the generator gen.send("hello") # Prints: Received: hello # Generator expressions (memory efficient) squares = (x**2 for x in range(1000000)) # No list created sum_squares = sum(x**2 for x in range(1000))
Generator Key Points:
• Consumed once (like iterators)
• Memory efficient for large datasets
• Use send() to send values into generator

🎭 Decorators

# Basic decorator def timer(func): import time def wrapper(*args, **kwargs): start = time.time() result = func(*args, **kwargs) end = time.time() print(f"{func.__name__} took {end - start:.4f}s") return result return wrapper @timer def slow_function(): import time time.sleep(1) slow_function() # Prints timing info # Decorator with parameters def repeat(times): def decorator(func): def wrapper(*args, **kwargs): for _ in range(times): result = func(*args, **kwargs) return result return wrapper return decorator @repeat(3) def greet(): print("Hello!") greet() # Prints "Hello!" three times

🏠 Context Managers

# Basic context manager class FileManager: def __init__(self, filename, mode): self.filename = filename self.mode = mode def __enter__(self): self.file = open(self.filename, self.mode) return self.file def __exit__(self, exc_type, exc_val, exc_tb): self.file.close() return False # Don't suppress exceptions # Usage with FileManager("test.txt", "w") as f: f.write("Hello, World!") # Exception suppression class ErrorHandler: def __exit__(self, exc_type, exc_val, exc_tb): if exc_type is ValueError: print("Handled ValueError") return True # Suppress the exception return False with ErrorHandler(): raise ValueError("This will be caught")

📝 Comprehensions Advanced

# List comprehension with condition result = [x if x > 0 else 0 for x in range(-3, 4)] # [0, 0, 0, 0, 1, 2, 3] # Nested comprehensions matrix = [[i*j for j in range(1, 4)] for i in range(1, 4)] # [[1, 2, 3], [2, 4, 6], [3, 6, 9]] # Multiple iterables pairs = [(x, y) for x in [1, 2] for y in [10, 20]] # [(1, 10), (1, 20), (2, 10), (2, 20)] # Dictionary comprehension with filtering word_lengths = {word: len(word) for word in ['apple', 'pie', 'a'] if len(word) > 1} # {'apple': 5, 'pie': 3} # Set comprehension unique_squares = {x**2 for x in range(-5, 6)} # {0, 1, 4, 9, 16, 25}
⚠️ Error Handling

🎯 Try/Except/Else/Finally

try: result = risky_operation() except SpecificError: result = "error_value" else: # Runs only if NO exception occurred result = "success_value" finally: # ALWAYS runs, even if there's a return in try/except cleanup() # Finally overrides return values! def tricky_function(): try: return "try" finally: return "finally" # This wins! print(tricky_function()) # "finally"
Finally Override: Return statements in finally block override returns from try/except blocks!

🔗 Exception Chaining

# Chain exceptions with 'from' try: 1 / 0 except ZeroDivisionError as e: raise ValueError("Math error") from e # Suppress chaining with 'from None' try: 1 / 0 except ZeroDivisionError: raise ValueError("New error") from None # No traceback chain # Access chained exceptions try: # ... chained exception code ... except ValueError as e: print(e.__cause__) # Original exception print(e.__context__) # Implicit chaining

🎭 Multiple Exception Types

# Catch multiple exception types try: risky_operation() except (ValueError, TypeError) as e: print(f"Got {type(e).__name__}: {e}") # Different handling for different exceptions try: process_data() except ValueError: print("Data validation error") except TypeError: print("Type mismatch error") except Exception as e: print(f"Unexpected error: {e}") # Re-raising exceptions try: operation() except ValueError: log_error("Data error occurred") raise # Re-raise the same exception
💥 Edge Cases & Expert Knowledge

🎯 Walrus Operator (Python 3.8+)

# Assignment expressions if (n := len(items)) > 5: print(f"Too many items: {n}") # In comprehensions results = [result for item in items if (result := process(item)) > 0] # In while loops while (line := input("Enter command: ")) != "quit": process_command(line)
Walrus Usage: Assign and use in same expression. Great for avoiding duplicate calculations.

🔄 Dictionary Union Operator (Python 3.9+)

# Dictionary union with | dict1 = {'a': 1, 'b': 2} dict2 = {'b': 3, 'c': 4} merged = dict1 | dict2 # {'a': 1, 'b': 3, 'c': 4} # In-place union with |= dict1 |= dict2 # Modifies dict1 # Older equivalent merged = {**dict1, **dict2} # Same result, works in older Python

🔢 Floating Point Precision

# Floating point precision issues print(0.1 + 0.2 == 0.3) # False! print(0.1 + 0.2) # 0.30000000000000004 # Solutions from decimal import Decimal print(Decimal('0.1') + Decimal('0.2') == Decimal('0.3')) # True import math print(math.isclose(0.1 + 0.2, 0.3)) # True
Floating Point: Never use == for float comparisons. Use math.isclose() or Decimal for precision.

🧠 Memory and Identity

# Identity vs equality a = [1, 2, 3] b = a # Same object c = a.copy() # Different object, same content print(a is b) # True (same identity) print(a is c) # False (different identity) print(a == c) # True (same content) print(id(a) == id(b)) # True print(id(a) == id(c)) # False # Small integer caching x = 256 y = 256 print(x is y) # True (cached) x = 257 y = 257 print(x is y) # False (not cached) - implementation detail!

🎨 Advanced Slicing Edge Cases

# Complex slice deletion lst = [1, 2, 3, 4, 5, 6] del lst[::2] # Deletes indices 0, 2, 4 print(lst) # [2, 4, 6] # Overlapping slice assignment lst = [1, 2, 3, 4, 5] lst[1:4] = lst[2:5] # Replace with overlapping slice print(lst) # [1, 3, 4, 5] # Backward slicing edge cases lst = [1, 2, 3, 4, 5] print(lst[1:4:-1]) # [] (empty - start < stop with negative step) print(lst[4:1:-1]) # [5, 4, 3] (backwards from 4 to 2)

🔧 Mutable Default Arguments

# The classic Python gotcha def append_to_list(item, target_list=[]): # DANGEROUS! target_list.append(item) return target_list print(append_to_list(1)) # [1] print(append_to_list(2)) # [1, 2] - same list! # Correct approach def append_to_list(item, target_list=None): if target_list is None: target_list = [] target_list.append(item) return target_list
Mutable Defaults: Default arguments are evaluated once at function definition. Mutable defaults = shared state!

🎯 Quiz Success Tips

Focus Areas for High Scores:

• Master slice assignment size rules (basic vs step slicing)
• Know all built-in function parameters (especially range, enumerate, zip)
• Understand MRO and class variable sharing
• Practice star expression unpacking patterns
• Remember edge cases: banker's rounding, floating point precision
• Know when operations return iterators vs lists
• Understand exception handling execution order