WorksheetsPCAP Practical Study Questions
Total questions: 50
Worksheet time: 25mins
Truthiness — Which values evaluate to False in Python without being explicitly False?
Empty sequences/collections (e.g., '', [], {}, set())
Zero of numeric types (e.g., 0, 0.0, 0j)
None
NaN from the math or decimal modules
Custom objects whose __bool__ returns False
is vs == — When should you use is vs == in comparisons (for example, with None, small ints, or strings)?
Use is for identity checks (e.g., None), and == for value equality; do not rely on interning of small ints/strings.
Use is for strings and numbers, and == for objects like None.
Use == for identity checks and is for value equality.
Use is and == interchangeably because CPython interns all small values.
None checks — What is the Pythonic way to test for None and why?
Compare with == None because it is readable and reliable.
Use is None / is not None because None is a singleton and identity is intended.
Call bool(None) to check falsiness because None is falsey.
Use type(x) is NoneType to avoid truthiness pitfalls.
Short-circuit — Given a() and b(), when will b() not execute, and what about a() or b()?
In a() and b(), b() is skipped if a() is truthy; in a() or b(), b() is skipped if a() is falsy.
In a() and b(), b() always executes; in a() or b(), b() never executes.
In a() and b(), b() is skipped if a() is falsy; in a() or b(), b() is skipped if a() is truthy.
In both cases b() always executes.
Slicing basics — What does s[::-1] do, what does s[:], and why does s[:3] not raise if s is shorter?
s[::-1] reverses a sequence; s[:] makes a shallow copy; slicing is clamped to bounds so s[:3] returns the available elements.
s[::-1] rotates by one; s[:] returns the same reference; s[:3] raises IndexError if out of range.
s[::-1] sorts descending; s[:] deep-copies; s[:3] pads with None.
s[::-1] drops last element; s[:] copies metadata only; s[:3] repeats elements to fill length.
Slice assignment — How can slice assignment modify multiple elements at once in a list?
By assigning an iterable to a slice range, replacing that section with elements of the iterable.
By calling list.replace on the slice.
By using a for loop with enumerate only.
By using list.extend on a single index.
Copy pitfalls — Why does list2 = list1 not copy, and how do you properly copy shallow vs deep?
list2 = list1 aliases the same object; use list1[:] or list(list1) for shallow copy, and copy.deepcopy for deep copy.
list2 = list1 creates a new list; use list.copy for deep copy only.
list2 = list1 copies values but not length; use tuple(list1) for deep copy.
list2 = list1 fails due to scope; use eval to copy dynamically.
Mutable defaults — Why are mutable default arguments dangerous and how do you avoid the trap?
They are re-evaluated each call causing extra allocations; avoid by using global variables.
They persist across calls and accumulate mutations; avoid by using None as default and create a new object inside.
They cause syntax errors at runtime; avoid by using try/except.
They prevent recursion; avoid by passing *args.
Argument passing — How does Python pass arguments (objects by assignment/aliasing)?
Pass-by-value for immutable and pass-by-reference for mutable.
Pass-by-assignment (object references are passed; names are bound to the same object).
Call-by-name semantics like some functional languages.
Copy-on-write for all arguments.
*args/**kwargs — What do *args and **kwargs capture and what is the typical ordering in a signature?
*args captures keyword arguments and **kwargs captures positional ones; order is **kwargs before *args.
*args captures extra positional arguments and **kwargs captures extra keyword arguments; order is positional, *args, keyword-only, **kwargs.
*args captures defaults while **kwargs captures requireds; order is **kwargs, *args.
Both capture only required parameters; order is arbitrary.
Parameter kinds — What are positional-only, positional-or-keyword, keyword-only, and var-pos/var-key parameters?
Four kinds: positional-only (before /), positional-or-keyword, keyword-only (after *), and var-pos (*args)/var-key (**kwargs).
Only two kinds: positional and keyword; var-pos/var-key are not parameters.
Three kinds: positional-only, keyword-only, and optional-only; var-pos/var-key are decorators.
Five kinds including type-only and default-only parameters.
Unpacking — How do starred expressions work for unpacking (for example, a, *mid, b = seq)?
The star target collects any number of middle items into a list; ends bind to first and last items.
Starred unpacking assigns each element to separate names only if lengths match exactly.
Starred unpacking creates tuples automatically for all names.
Starred unpacking is allowed only on the right side of assignment.
for-else — When does the else clause on a loop execute, and what is a common use case?
It runs whenever the loop body runs at least once; use it to print after iteration.
It runs only if the loop terminates normally without a break; common for search-not-found logic.
It runs only when an exception occurs; used for error handling.
It runs on every iteration; used for logging.
while-else — How does while ... else differ from for ... else in practice?
They are identical; else runs only if no break occurs in either form.
while ... else runs regardless of break, while for ... else depends on exceptions.
for ... else runs only with continue statements; while ... else runs only without continue.
while ... else runs only when the condition is initially False; for ... else runs only when the iterable is empty.
Exception order — Why should except Exception come last, and how can you catch multiple exceptions?
Specific handlers must follow general ones; catch multiples using a tuple in a single except.
General handlers must come first to short-circuit; catch multiples using a list.
Place except Exception first to ensure cleanup; catch multiples with bitwise OR.
Order does not matter; catch multiples with multiple except blocks separated by commas.
try/except/else/finally — What is the exact execution order and when does else run?
try runs; if no exception, else runs; finally runs always; except runs if an exception occurs.
finally runs first; else runs on exceptions; try runs last.
except runs before try; else runs when an exception occurs; finally runs only if no exception.
try is skipped; else runs on syntax errors; finally runs never.
Raising exceptions — How do you re-raise the current exception, and when should you use raise from?
Use raise with no arguments to re-raise; use raise from to chain exceptions with an explicit cause.
Use return to re-raise; use raise from only in logging.
Use raise(e) always; never use raise from.
Use throw(e) to re-raise; use raise from to suppress context.
Context managers — What protocol enables with statements and how do you write one manually?
The iterator protocol; implement __iter__ and __next__.
The descriptor protocol; implement __get__ and __set__.
The context manager protocol; define __enter__ and __exit__ or use contextlib.contextmanager.
The buffer protocol; implement __buffer__ methods.
File I/O safety — What guarantees does with open(...) as f provide and why is it preferred?
It ensures the file is closed even if exceptions occur; preferred for deterministic resource cleanup.
It speeds up reads by caching; preferred for performance.
It prevents file corruption by locking; preferred for concurrency.
It converts text to UTF-8 automatically; preferred for portability.
Enumerate — How do you iterate with indexes safely using enumerate, and how can you change the starting index?
Use enumerate(iterable, start=0) by default; set start to a custom integer like 1 to begin at a different index.
Use range(len(iterable)) always; cannot change starting index.
Use enumerate only on dicts; start is a boolean.
Use enumerate(iterable) but indexes are 1-based and fixed.
Zip — What length does zip produce for unequal inputs, and how do you zip longest?
zip stops at the shortest input length; use itertools.zip_longest to zip to the longest.
zip pads missing items with None; use zip_longest to stop at shortest.
zip always produces the longest length by repeating last items; use chain to stop early.
zip truncates to median length; use map to extend.
Comprehension scope — Why do list comprehension variables not leak into the outer scope in Python 3?
They execute in their own implicit function scope.
They execute in a separate local scope for the comprehension expression, not the surrounding scope.
They are compiled as globals and then deleted.
They are replaced by temporary names in the surrounding scope.
Dict views — How are dict.keys(), dict.values(), and dict.items() dynamic during mutation?
They snapshot at creation and never change.
They are dynamic view objects that reflect subsequent mutations of the dict.
They convert to lists automatically when mutated.
They raise exceptions if the dict changes.
Dict ordering — From which Python version are dicts insertion-ordered, and why does it matter?
Since Python 2.7; matters for hashing performance.
Since Python 3.7 (as a language guarantee after 3.6 behavior); matters for predictable iteration order and APIs.
Since Python 3.3; matters only for memory usage.
Since Python 3.11; matters for typing annotations.
Set behavior — Why are sets unordered and how do they handle duplicates and unhashables?
Sets are hash-based with no defined order; they automatically remove duplicates and cannot contain unhashable items.
Sets preserve insertion order; duplicates are allowed and removed only on request.
Sets are tree-based with sorted order; they accept any objects.
Sets are list-like; duplicates are kept and order matters.
Hashability — Why can tuples be set/dict keys but lists cannot, and when is a tuple unhashable?
Tuples are immutable and hashable; lists are mutable; a tuple is unhashable if it contains any unhashable (e.g., a list) inside.
Tuples are smaller; lists are larger; tuples are unhashable when empty.
Tuples are always hashable regardless of contents; lists are hashable only when frozen.
Lists are immutable; tuples are mutable; tuples become unhashable when nested.
Sorting basics — What is the difference between sorted() and list.sort(), and what about stability and the key parameter?
sorted returns a new list; list.sort sorts in place; both are stable; key functions customize sort order.
sorted sorts in place; list.sort returns a new list; both are unstable.
sorted returns a generator; list.sort returns None; key is unsupported.
Both return new lists; stability depends on random seed.
Sorting tuples — How does sorted(list_of_tuples) order by default, and how can you change the key?
By lexicographic order of tuple elements; change using key=lambda t: t[i] or another function.
By tuple length only; change using reverse=True exclusively.
By hash value; change using cmp parameter.
By random order; change using shuffle.
Min/max with key: How do you find the minimum item by a computed metric using key?
Use min(items, key=metric) so the item with the smallest metric is returned
Map metric over items and call min() on the mapped values, then look up the original item
Sort items by metric and take the first element only
Use functools.reduce to track the smallest metric manually
Ranges: What does range(a, b, s) include or exclude, and how is membership tested efficiently?
Includes a and excludes b; x in range(a, b, s) uses arithmetic checks in O(1) without materializing
Includes both a and b; x in range(a, b, s) builds a list to check containment
Excludes both a and b; x in range(a, b, s) expands the sequence fully
Includes a and includes b if (b − a) is divisible by s; membership is O(n)
Iterators vs iterables: What distinguishes an iterator from an iterable and which methods are required?
An iterator implements __iter__ returning self and __next__; an iterable implements __iter__ returning a fresh iterator
An iterator only implements __next__; an iterable only implements __len__
An iterator implements __iter__ returning a new iterator; an iterable implements __next__
An iterator and iterable are identical; both require only __getitem__
Generator basics: How do generators save memory and how can values or errors be sent into them?
Generators yield lazily so they don’t store all results; use .send() to pass in values and .throw() to inject exceptions
Generators precompute all outputs; use .put() to pass values and .raise() for errors
Generators save memory by caching results; use .append() and .error()
Generators stream from files only; values and errors cannot be injected
Yield from: What does yield from do for generator composition?
Delegates to a subgenerator, forwarding values, exceptions, and the subgenerator’s return value
Concatenates two generators into one list
Buffers yielded values to improve performance only
Forces eager evaluation of a nested generator
Deep vs shallow: How does copy.deepcopy handle nested objects differently from copy.copy?
deepcopy recursively copies the entire structure; copy performs a shallow copy that reuses nested references
deepcopy and copy are identical for immutable types only
deepcopy copies references while copy copies values
deepcopy is faster but less accurate than copy for nested containers
String immutability: What are the consequences of strings being immutable for operations like concatenation?
Every change creates a new string; repeated concatenation in loops can be costly due to repeated allocations
Immutability allows in‑place edits that are O(1)
Concatenation mutates the original string without allocating
Immutability prevents slicing or joining altogether
Joining/splitting: When should "sep".join(seq) be preferred over concatenation in loops?
Prefer join for assembling many substrings because it is linear time and avoids repeated reallocations
Prefer join only when the separator is an empty string
Concatenation in loops is faster than join for long lists
Join is only for splitting strings, not building them
F-strings: How do you format numbers (width, precision) and debug with f'{var=}'?
Use format specifiers like f"{x:8.2f}" for width and precision; f"{var=}" shows the name and value
Width and precision are unsupported; f"{var=}" prints only the variable name
Use f"{x:%8.2}"; f"{var=}" shows the type only
Use f"{x:prec=2,width=8}"; f"{var=}" requires logging
LEGB: Summarize LEGB scope resolution and a common pitfall with nonlocal and global.
Names resolve in Local, Enclosing, Global, Builtins; assigning to an enclosing/global name requires nonlocal/global declarations
LEGB means Local, External, General, Base; nonlocal/global are optional and inferred
LEGB applies only to classes; functions ignore it
Names resolve in Global first; nonlocal/global declarations are never needed
Name shadowing: How can inner variables shadow outer ones and how do you avoid accidental shadowing?
An inner assignment hides the outer name in its scope; avoid by using distinct names or explicit nonlocal/global
Shadowing happens only across modules; avoid by reimporting
Inner scopes cannot shadow outer ones in Python
Avoid shadowing by turning all variables into attributes
MRO basics: What is method resolution order and why does it matter in multiple inheritance?
MRO is the linearized order Python uses to look up attributes; it ensures consistent multiple inheritance via C3 and is inspectable with Class.mro()
MRO is a runtime type checker for methods only
MRO determines only which __init__ runs and ignores other attributes
MRO randomizes the lookup order to improve speed
Class vs instance attrs: How do class attributes differ from instance attributes and what are common gotchas?
Class attributes are shared across instances; instance attributes live per object; mutable class attributes are shared and can surprise
Class attributes are copied into each instance automatically
Instance attributes override class attributes only for methods
Class attributes cannot be accessed from instances
__repr__ vs __str__: When is each called and what is a good rule of thumb for implementing them?
__repr__ is for an unambiguous developer representation and is used by the interactive prompt; __str__ is user‑friendly; implement __repr__ first and let __str__ fall back if needed
__str__ is used by repr(); __repr__ prints user‑friendly output only
Implement only __str__; __repr__ is deprecated
Both are identical and always return the same string
Equality hooks: How do you implement __eq__ and why also implement __hash__ for dict/set keys?
Define __eq__ for logical equality and provide a consistent __hash__ (usually from immutable fields) so equal objects hash the same for dict/set keys
Implement __eq__ only; hashing is automatic and unrelated
Use __hash__ without __eq__ so equality always falls back to identity
Define __eq__ to compare types only and never values
Iterable protocol: Which dunder methods enable iteration and containment checks?
Iteration uses __iter__ and __next__; containment prefers __contains__ and otherwise iterates via __iter__
Only __len__ is required for both iteration and containment
Containment uses __hash__ and __eq__ exclusively
Iteration relies on __getitem__ with integer indexes only
Contextlib utilities: What do contextlib.contextmanager and suppress() provide?
contextmanager turns a generator into a context manager; suppress() creates a context that ignores specified exceptions
contextmanager registers global handlers; suppress() logs exceptions
contextmanager yields a thread; suppress() retries operations
Both are deprecated in favor of withopen()
Pathlib vs os: Why prefer pathlib for file paths and what are simple examples of common operations?
Pathlib provides object‑oriented paths like Path('a') / 'b', Path.read_text(), Path.iterdir(); os uses string paths and separate functions
Pathlib is slower and should be avoided; os always returns Path objects
Pathlib only works on Windows; os works cross‑platform
Pathlib cannot read files; it only builds paths
Time complexity: What are the Big‑O costs for membership in list, set, dict; sorting; slicing; append/pop?
Membership: list O(n), set/dict average O(1); sorting O(n log n); slicing O(k); append amortized O(1); pop from end O(1)
Membership: list O(1), set/dict O(n); sorting O(n); slicing O(1); append O(n); pop O(n)
Membership: all O(log n); sorting O(n log n); slicing O(n^2); append O(log n); pop O(log n)
Membership: list , set/dict ; sorting ; slicing ; append ; pop
Unit tests: What does unittest.TestCase provide and how can you run selected tests?
TestCase provides assertions and setup/teardown hooks; run selected tests with python -m unittest test_module.TestClass.test_method
TestCase is only a data container; tests run via python -m pytest -k
TestCase requires a main guard to run; selection is impossible
TestCase provides file I/O helpers; run with python tests.py only
Main guard: Why use if __name__ == "__main__": and how does it help testing or modules?
It prevents code from running on import so modules can be reused; it provides an entry point for CLI/test harnesses
It forces code to run twice for debugging
It is required for every function definition
It only affects class methods, not modules
Imports: How do relative imports work inside packages and when should they be avoided?
Use leading dots (from .subpkg import mod) for intra‑package imports; avoid relative imports in scripts executed as the main module and prefer absolute imports to reduce ambiguity
Relative imports are faster and always preferred
Relative imports require setting PYTHONPATH manually
Relative imports work only with namespace packages and should always be avoided
