PCAP study materials
PCAP study resources

PCAP common mistakes

PCAP · Understand the traps and how to avoid them.

Mistake 1

Assuming that `import package.module` binds the name `module` in the current namespace and then calling `module.func()` directly.

The from ... import ... form binds the leaf name, but a plain import package.module binds only the top-level package name package; the submodule is reachable only as package.module. Candidates transfer the habit from from imports and get a NameError.

What to do instead

After import package.module, refer to package.module.func(). Use from package import module or from package.module import func when you want the shorter name bound directly.

Mistake 2

Expecting a module-level `__name__` check to behave the same whether the file is run as a script or imported, and writing code that executes on import.

When run directly, __name__ equals "__main__"; when imported, it equals the module's name. Candidates forget the guard and place side-effect code (prints, test runs) at module level, then are surprised it runs on import.

What to do instead

Wrap executable side effects in if __name__ == "__main__":. Remember imported modules run top-to-bottom once and are cached in sys.modules, so the guard prevents unwanted execution.

Mistake 3

Writing a bare `except:` or `except Exception:` that swallows everything, then expecting a later `else`/`finally` or the original traceback to still surface the error.

A bare except catches SystemExit and KeyboardInterrupt too, and catching broad exceptions hides bugs. Candidates also misorder else (runs only if no exception) and finally (always runs) and assume finally is skipped on return.

What to do instead

Catch the narrowest exception type that applies. Recall the order: try → matching except → else only when no exception occurred → finally always, even on return or an uncaught exception.

Mistake 4

Assuming an exception raised inside an `except` block replaces the original, or that `raise` without arguments re-raises the wrong exception.

During handling of one exception, a new exception chains onto it (__context__), so the traceback shows both. Candidates think the first is lost, or they use raise NewError and lose the original cause.

What to do instead

Use raise NewError(...) from original to set an explicit __cause__. A bare raise inside an except re-raises the currently handled exception; raise X from None suppresses chaining.

Mistake 5

Treating strings as mutable, e.g. attempting `s[0] = 'X'` or assuming `s.upper()` changes `s` in place.

Strings are immutable; methods like upper, strip, replace, and join return new strings. Candidates coming from mutable-sequence habits expect in-place modification and see the original unchanged.

What to do instead

Rebind the result: s = s.upper(). For character-level edits, build a new string (slice concatenation, str.join, or bytearray if you truly need mutation).

Mistake 6

Misreading slice semantics, especially negative steps, omitted bounds, and that `s[::-1]` reverses while `s[5:1:-1]` excludes index 1.

Slices are half-open [start:stop) and the stop bound is exclusive; with a negative step the defaults for start/stop swap. Candidates apply inclusive-stop logic from other languages and get off-by-one or empty results.

What to do instead

Memorise: start defaults to the beginning (or end when step < 0), stop is always exclusive, step defaults to 1. s[::-1] reverses; s[a:b] never includes index b.

Mistake 7

Confusing class attributes with instance attributes, expecting a mutable class-level list to be per-instance, or forgetting that methods defined without `self` cannot access instance state.

Names looked up on an instance fall back to the class, so a shared mutable class attribute is mutated across all instances. Candidates also forget self as the first parameter of instance methods and get a TypeError on call.

What to do instead

Create per-instance state in __init__ via self.attr = .... Declare instance methods with self first. Use @classmethod/@staticmethod deliberately and know that cls/no-arg methods do not receive the instance.

Mistake 8

Getting inheritance lookup wrong: assuming `super().__init__()` is optional, that `isinstance` fails for subclasses, or that overriding changes the parent's stored attribute.

If a subclass overrides __init__ without calling super().__init__(...), parent attributes are never set and later attribute access fails. Candidates also mix up isinstance (True for subclasses) with exact-type checks.

What to do instead

Call super().__init__(...) first in an overriding __init__. Remember isinstance(obj, Base) is True for subclasses; use type(obj) is Base only for exact-type tests. Overriding a method does not alter the parent class.

Mistake 9

Expecting a list comprehension's variables to leak into the enclosing scope, or expecting a generator expression to be re-iterable.

In Python 3 comprehension variables have their own scope and do not leak; a generator expression is exhausted after one pass. Candidates also assume a lambda captures a value at definition time rather than looking it up when called.

What to do instead

Assign the comprehension result to a name to keep it. Materialise a generator with list(...) if you need to iterate twice. For late-binding closure/lambda bugs, bind the current value with a default argument: lambda x=i: x.

Mistake 10

Mishandling file I/O: reading a file opened in text mode and expecting bytes, or relying on garbage collection to flush and close the file.

Text mode returns str and applies an encoding; binary mode returns bytes. Without a context manager, buffered writes may not be flushed and the file may stay open, and iterating a file yields lines including trailing newlines.

What to do instead

Use with open(path, mode, encoding=...) as f: so the file closes and flushes. Match mode to data ('r'/'w' text vs 'rb'/'wb' bytes) and strip newlines explicitly when reading lines.