Python Practice (Fundamentals)
Intuition
Learning through practice: Practice problems are like training drills, they help you apply knowledge and identify areas that need more study.
Why it matters: Regular practice builds confidence and reveals patterns in how concepts are tested. Each problem reinforces key programming concepts.
The key insight: Making mistakes during practice is valuable, each error points to a concept that needs clarification.
Worked Examples
Example 1: List Comprehension vs Generator
Problem: What’s the difference between these two?
squares_list = [x**2 for x in range(1000000)]
squares_gen = (x**2 for x in range(1000000))
Solution: Step 1: squares_list is a list, all 1,000,000 values are computed and stored in memory immediately Step 2: squares_gen is a generator expression, values are computed lazily, one at a time, when iterated Step 3: Memory: list uses ~8 MB, generator uses ~200 bytes (just the expression and iterator state) Step 4: Speed: first iteration is similar, but generator wins for large datasets due to lower memory overhead
Key insight: Use list comprehensions when you need the data multiple times or need list methods. Use generators for large datasets or single-pass processing.
Example 2: Decorator Pattern
Problem: Write a decorator that logs function calls with their arguments.
Solution:
def log_calls(func):
def wrapper(*args, **kwargs):
print(f"Calling \{func.__name__\} with \{args\}, \{kwargs\}")
result = func(*args, **kwargs)
print(f"\{func.__name__\} returned \{result\}")
return result
return wrapper
@log_calls
def add(a, b):
return a + b
add(3, 5)
## Output:
## Calling add with (3, 5), \{\}
# add returned 8
Step 1: Define wrapper that captures *args and **kwargs Step 2: Call original function and capture result Step 3: Log before and after, return result Step 4: Use @decorator syntax to apply
Key insight: functools.wraps(func) should be used in the wrapper to preserve the original function’s metadata (name, docstring, etc.).
Example 3: Context Manager
Problem: Why use with open('file.txt') as f: instead of f = open('file.txt')?
Solution: Step 1: with statement calls __enter__ when entering the block and __exit__ when leaving Step 2: If an exception occurs, __exit__ is still called (guaranteed cleanup) Step 3: File is automatically closed even if an exception occurs Step 4: Without withyou need try/finally to ensure the file is closed
# Without context manager (error-prone):
f = open('file.txt')
try:
data = f.read()
finally:
f.close()
# With context manager (safe):
with open('file.txt') as f:
data = f.read()
# File is automatically closed here
Key insight: Context managers guarantee cleanup. Use them for files, database connections, locks, and any resource that needs deterministic release.
Types and Variables
Q1. What is the output of type([1, 2, 3]) == type((1, 2, 3))?
A. True B. TypeError C. False D. SyntaxError
Show answer, C
Answer: C, type([1, 2, 3]) is \<class 'list'> and type((1, 2, 3)) is \<class 'tuple'>. These are different types, so == returns False. Lists use square brackets and tuples use parentheses.
Q2. Which of the following is a mutable built-in type in Python?
A. list B. str C. int D. tuple
Show answer, A
Answer: A, Lists are mutable — you can append, remove, or modify elements in place. Strings, integers, and tuples are all immutable: any operation that appears to modify them actually creates a new object.
Functions and Scope
Q3. What does def f(a, b=[]): b.append(a); return b return when called as f(1); f(2)?
A. [1], [2] B. [2] C. TypeError D. [1, 2]
Show answer, D
Answer: D, Mutable default arguments are evaluated once at function definition time and shared across all calls. After f(1), b is [1]. After f(2), b is [1, 2]. The function returns [1, 2] on the second call. Use None as the default and create the list inside the function body to avoid this.
Q4. What does the LEGB rule stand for in Python variable resolution?
A. Local, Enclosing, Global, Built-in B. Local, External, Global, Base C. Loop, Enclosing, Global, Built-in D. Local, Enclosed, Generic, Built-in’,
Show answer, A
Answer: A, Python resolves variable names using the LEGB scope order: Local (inside the current function), Enclosing (in enclosing functions for nested functions), Global (module level), and Built-in (the builtins module). The first match in this order is used.
Object-Oriented Programming
Q5. Given class A: x = 1 and class B(A): passwhat does B.x return?
A. 1 B. AttributeError C. None D. 0
Show answer, A
Answer: A, Class attributes are inherited. B.x is not found in B’s namespace, so Python looks up the MRO and finds x = 1 in A. Assigning B.x = 2 afterwards would shadow the inherited attribute without modifying A.x.
Q6. What is the MRO of class D(B, C) where class B(A) and class C(A)?
A. D, C, B, A, object B. D, A, B, C, object C. D, B, A, C, object D. D, B, C, A, object
Show answer, D
Answer: D, Python uses C3 linearization for MRO. It respects the left-to-right order of base classes (B before C) while ensuring each class appears only once. The result is D -> B -> C -> A -> object. You can verify with D.__mro__ or D.mro().
Decorators and Generators
Q7. What does @functools.lru_cache(maxsize=2) do when applied to a recursive function?
A. Caches up to 2 most recent distinct call results, eliminating redundant computation B. Caches all call results indefinitely C. Limits the function to 2 total calls D. Raises an error when the cache is full’,
Show answer, A
Answer: A, The LRU (Least Recently Used) cache stores up to maxsize distinct argument-result pairs. When the cache is full, the least recently used entry is evicted. For recursive functions like Fibonacci, this transforms exponential time complexity into linear by avoiding recomputation of subproblems.
Q8. What happens when a generator function uses return value instead of yield?
A. It raises StopIteration with the value and terminates the generator B. It yields the value and continues C. It returns the value normally like a regular function D. It raises SyntaxError’,
Show answer, A
Answer: A, In a generator function, return value raises StopIteration(value) to signal the generator is exhausted. The value is attached to the exception and can be retrieved via the return value of next() (using a try/except StopIteration) or by capturing it from yield from. The generator cannot be resumed after return.
Error Handling and Context Managers
Q9. What is the output of try: 1/0 except ValueError: print('caught')?
A. ZeroDivisionError is raised and not caught’, “‘caught’ is printed”, “‘ZeroDivisionError: division by zero’ is printed”, ‘None’,
Show answer, A
Answer: A, Dividing by zero raises ZeroDivisionErrorwhich is a subclass of ArithmeticError and Exceptionbut NOT of ValueError. Since the except clause only catches ValueErrorthe exception propagates uncaught. The program would terminate with a traceback unless caught by an outer handler.
Async and Internals
Q10. What happens when you call an async def function without using await?
A. It returns a coroutine object without executing the function body B. It runs the function synchronously C. It raises SyntaxError D. It returns None’,
Show answer, A
Answer: A, Calling an async def function does not execute its body. It creates and returns a coroutine object. To actually run the coroutine, you must await it inside another async function, or schedule it with asyncio.run(), asyncio.create_task()or asyncio.gather(). The coroutine must be awaited or scheduled exactly once.
Q11. What is the effect of defining __slots__ = ('x', 'y') on a class?
A. It replaces the per-instance dict with fixed-size slots, preventing dynamic attribute assignment and reducing memory usage by 40-60% B. It makes the class abstract C. It enables multiple inheritance D. It adds type checking to attributes x and y’,
Show answer, A
Answer: A, __slots__ tells the metaclass to allocate fixed attribute descriptors instead of a per-instance __dict__. This reduces memory overhead significantly for classes with many instances. Instances cannot have attributes not listed in __slots__catching typos at runtime. All classes in the hierarchy must define __slots__ for the full benefit.
Cross-References
- Python Flashcards: Interactive flashcards covering core Python concepts.
- Python Interactive Practice: Advanced practice with async and functional patterns.
- Packaging and Distribution: Package management and distribution best practices.
Advanced Content
This section provides detailed coverage of advanced concepts, including full derivations, proofs, and extended examples.
Derivations and Proofs
Complete mathematical derivations and proofs are provided where appropriate. Each step is explained to ensure understanding of the underlying reasoning.
Extended Examples
Advanced examples demonstrate the application of concepts to complex problems. These examples go beyond standard exam questions to develop deeper understanding.
Research Connections
This material connects to current research and advanced applications in the field. Understanding these connections provides context for the study material.
Prerequisites
Ensure you have mastered the prerequisite material before attempting this advanced content.
Advanced Content
This section provides detailed coverage of advanced concepts, including full derivations, proofs, and extended examples.
Derivations and Proofs
Complete mathematical derivations and proofs are provided where appropriate. Each step is explained to ensure understanding of the underlying reasoning.
Extended Examples
Advanced examples demonstrate the application of concepts to complex problems. These examples go beyond standard exam questions to develop deeper understanding.
Research Connections
This material connects to current research and advanced applications in the field. Understanding these connections provides context for the study material.
Prerequisites
Ensure you have mastered the prerequisite material before attempting this advanced content.
Q1. What is the output of type([1, 2, 3]) == type((1, 2, 3))?
A. True B. TypeError C. False D. SyntaxError
Answer: C
type([1, 2, 3])is<class 'list'>andtype((1, 2, 3))is<class 'tuple'>. These are different types, so==returnsFalse. Lists use square brackets and tuples use parentheses. Difficulty: medium
Q2. Which of the following is a mutable built-in type in Python?
A. list B. str C. int D. tuple
Answer: A
- Lists are mutable — you can append, remove, or modify elements in place. Strings, integers, and tuples are all immutable: any operation that appears to modify them actually creates a new object. Difficulty: medium
Q3. What does def f(a, b=[]): b.append(a); return b return when called as f(1); f(2)?
A. [1], [2] B. [2] C. TypeError D. [1, 2]
Answer: D
- Mutable default arguments are evaluated once at function definition time and shared across all calls. After
f(1), bis[1]. Afterf(2), bis[1, 2]. The function returns[1, 2]on the second call. UseNoneas the default and create the list inside the function body to avoid this. Difficulty: medium
Q4. What does the LEGB rule stand for in Python variable resolution?
A. Local, Enclosing, Global, Built-in B. Local, External, Global, Base C. Loop, Enclosing, Global, Built-in D. Local, Enclosed, Generic, Built-in’,
Answer: A
- Python resolves variable names using the LEGB scope order: Local (inside the current function), Enclosing (in enclosing functions for nested functions), Global (module level), and Built-in (the
builtinsmodule). The first match in this order is used. Difficulty: medium
Q5. Given class A: x = 1 and class B(A): passwhat does B.x return?
A. 1 B. AttributeError C. None D. 0
Answer: A
- Class attributes are inherited.
B.xis not found inB’s namespace, so Python looks up the MRO and findsx = 1inA. AssigningB.x = 2afterwards would shadow the inherited attribute without modifyingA.x. Difficulty: medium
Q6. What is the MRO of class D(B, C) where class B(A) and class C(A)?
A. D, C, B, A, object B. D, A, B, C, object C. D, B, A, C, object D. D, B, C, A, object
Answer: D
- Python uses C3 linearization for MRO. It respects the left-to-right order of base classes (
BbeforeC) while ensuring each class appears only once. The result isD -> B -> C -> A -> object. You can verify withD.__mro__orD.mro(). Difficulty: medium
Q7. What does @functools.lru_cache(maxsize=2) do when applied to a recursive function?
A. Caches up to 2 most recent distinct call results, eliminating redundant computation B. Caches all call results indefinitely C. Limits the function to 2 total calls D. Raises an error when the cache is full’,
Answer: A
- The LRU (Least Recently Used) cache stores up to
maxsizedistinct argument-result pairs. When the cache is full, the least recently used entry is evicted. For recursive functions like Fibonacci, this transforms exponential time complexity into linear by avoiding recomputation of subproblems. Difficulty: medium
Q8. What happens when a generator function uses return value instead of yield?
A. It raises StopIteration with the value and terminates the generator B. It yields the value and continues C. It returns the value normally like a regular function D. It raises SyntaxError’,
Answer: A
- In a generator function,
return valueraisesStopIteration(value)to signal the generator is exhausted. Thevalueis attached to the exception and can be retrieved via the return value ofnext()(using a try/except StopIteration) or by capturing it fromyield from. The generator cannot be resumed afterreturn. Difficulty: hard
Q9. What happens when you call an async def function without using await?
A. It returns a coroutine object without executing the function body B. It runs the function synchronously C. It raises SyntaxError D. It returns None’,
Answer: A
- Calling an
async deffunction does not execute its body. It creates and returns a coroutine object. To actually run the coroutine, you mustawaitit inside another async function, or schedule it withasyncio.run(), asyncio.create_task()orasyncio.gather(). The coroutine must be awaited or scheduled exactly once. Difficulty: hard
Q10. What is the effect of defining __slots__ = ('x', 'y') on a class?
A. It replaces the per-instance dict with fixed-size slots, preventing dynamic attribute assignment and reducing memory usage by 40-60% B. It makes the class abstract C. It enables multiple inheritance D. It adds type checking to attributes x and y’,
Answer: A
__slots__tells the metaclass to allocate fixed attribute descriptors instead of a per-instance__dict__. This reduces memory overhead significantly for classes with many instances. Instances cannot have attributes not listed in__slots__catching typos at runtime. All classes in the hierarchy must define__slots__for the full benefit. Difficulty: hard