PCPP1 study materials
PCPP1 study resources

PCPP1 common mistakes

PCPP1 · Understand the traps and how to avoid them.

Mistake 1

Calling `super().__init__()` from every class in a multiple-inheritance hierarchy and assuming each parent initializer runs once, producing duplicated state or skipped initializers in diamond-shaped hierarchies.

Candidates learn the single-inheritance mental model where super() means 'my parent', then transfer it unchanged to cooperative multiple inheritance, where super() actually means 'the next class in the instance's MRO'. They also forget that a base class at the end of the chain must eventually call object.__init__ implicitly or explicitly, and that any class breaking the chain by calling a base by name silently truncates the MRO walk.

What to do instead

Draw the MRO with ClassName.__mro__ for every hierarchy you write, and verify that each __init__ calls super().__init__(...) exactly once with only the arguments it does not consume. Test the diamond case explicitly and assert that shared state is initialized once. Remember that super() with no arguments works only inside a method body (it relies on the __class__ cell), so inside a comprehension or a nested function you must pass the explicit two-argument form.

Mistake 2

Treating `@staticmethod`, `@classmethod`, and instance methods as interchangeable, or forgetting that `classmethod` receives `cls` and is the correct hook for alternative constructors and polymorphic factories.

The three decorators look like cosmetic variations in short examples, so candidates memorize definitions rather than the calling conventions. The failure surfaces in questions that require subclass-aware construction (a factory that must return the subclass, not the base) or that call a method on the class rather than an instance.

What to do instead

Anchor each decorator to a calling convention: instance method gets self, classmethod gets cls and is callable on both class and instance, staticmethod gets neither and is just a namespaced function. Use classmethod whenever a method must respect the actual subclass (cls(...) inside the body). Practice rewriting a __init__-heavy class to expose from_... classmethod constructors and confirm the returned type on a subclass.

Mistake 3

Implementing only `__eq__` (or only `__hash__`) on a custom class and then using instances as dictionary keys or set members, expecting value-based lookup to work.

Python's default __hash__ is identity-based, and defining __eq__ in a class sets __hash__ to None, making instances unhashable. Candidates who test only == never see the problem, and the resulting TypeError: unhashable type or 'key not found' looks like a container bug rather than a dunder contract violation.

What to do instead

Whenever you define __eq__, define __hash__ in the same class using the same fields, and keep the objects immutable in those fields. Verify with a quick script: put two equal instances in a set, assert length 1, and use one as a dict key then retrieve with the other. Also confirm __ne__ is derived correctly (Python 3 derives it from __eq__), and remember that mutating a hashed field after insertion corrupts the container.

Mistake 4

Writing `except Exception:` or bare `except:` around broad blocks and swallowing errors, or catching an exception and then re-raising a different one without chaining, losing the original traceback.

Broad handlers make failing tests pass locally and feel defensive, so candidates adopt them as a habit. Exam scenarios test whether the candidate can distinguish recoverable from programming errors and whether they preserve diagnostic context, which broad handlers destroy.

What to do instead

Catch the narrowest exception that the try block can actually raise, and let everything else propagate. When you must translate an exception, use raise NewError(...) from original to preserve the chain, and use raise with no argument to re-raise inside a handler. For cleanup that must always run, prefer try/finally or a context manager over catching. Audit any except Exception and justify it in a comment or remove it.

Mistake 5

Using `assert` statements for runtime validation of user input or public API arguments, and relying on them in optimized mode (`python -O`), where they are stripped out.

Assertions read like validation and are the first tool taught for checking conditions, so candidates use them for argument checking. They do not realize assertions are a debugging aid that the interpreter removes under -O, so the check vanishes in production.

What to do instead

Reserve assert for internal invariants that indicate a programming bug, and use explicit if not condition: raise ValueError(...) for anything that validates external input. Know that -O also sets __debug__ to False, so any code guarded by if __debug__: disappears too. When reviewing code, treat every assert on a parameter as a defect unless the parameter is internal and documented as such.

Mistake 6

Ignoring PEP 8 naming and layout conventions in favor of personal style, and being unable to state the conventional names for module-level constants, classes, functions, and 'internal use' attributes.

Candidates write code that works and stop there, treating style as subjective. The exam tests concrete, checkable conventions (naming case, blank-line counts, import ordering, line length guidance), which are objective and easy to get wrong from memory.

What to do instead

Memorize the naming table: snake_case for functions, methods, variables, and modules; CapWords for classes; UPPER_CASE for module-level constants; a single leading underscore for internal use; double leading underscore for name-mangled attributes. Also learn the layout rules: imports on separate lines grouped standard library, third party, local; two blank lines around top-level definitions; spaces around operators but not inside brackets. Run a linter and fix every report until the rules are automatic.

Mistake 7

Building a Tkinter application that calls `time.sleep()` or a long loop on the main thread, then wondering why the window freezes and never repaints.

Candidates reason about GUI code as sequential script code. They do not internalize that Tkinter runs a single-threaded event loop (mainloop) that must regain control to process redraw and input events, so any blocking call on that thread stalls the whole interface.

What to do instead

Keep the main thread free: use after(ms, callback) for periodic work, split long jobs into chunks scheduled with after, or run blocking work in a worker thread and hand results back through a queue.Queue polled by after. Never touch widgets from a worker thread. Test by resizing the window during the operation; if it does not repaint, the main thread is blocked.

Mistake 8

Mixing geometry managers in one container (calling `pack()` and `grid()` on children of the same parent), or forgetting to call `mainloop()` and expecting the window to stay open.

Each geometry manager is simple in isolation, and examples online use them interchangeably. Tkinter raises TclError when two managers claim the same container, and a script without mainloop() creates and destroys the window immediately, so the mistake is easy to miss in a headless test.

What to do instead

Pick one geometry manager per parent widget and enforce it by wrapping each logical region in its own Frame with its own manager. Always end the application with root.mainloop() and build widgets before that call. When debugging layout, print winfo_manager() for each child to confirm a single manager per container, and use grid_rowconfigure/grid_columnconfigure with weight for resizing instead of switching managers.

Mistake 9

Calling `response.json()` without checking the status code, and not setting `timeout` on `requests` calls, so error payloads are parsed as success and a stalled server hangs the program indefinitely.

Tutorial examples show requests.get(url).json() in one line, which works only on the happy path. Candidates copy the pattern and never see the failure modes: a 404 returning HTML raises a JSON decode error, and a missing timeout blocks forever.

What to do instead

Write the full pattern every time: pass timeout=(connect, read), then branch on response.status_code or call response.raise_for_status() inside a try that catches requests.HTTPError, requests.Timeout, and requests.ConnectionError. Only call .json() after confirming a 2xx and a JSON content type, and prefer response.json() guarded by that check over blind parsing. Test against a deliberately wrong URL and a slow endpoint.

Mistake 10

Sending a JSON body without `json=`/`Content-Type: application/json`, or mishandling pagination and rate limits by assuming the first response contains every record.

Candidates focus on the URL and forget that REST semantics live in headers, methods, and status codes. They also test against small datasets where page 1 is the whole result, so pagination and 429 handling never appear until the real API is used.

What to do instead

Use requests.post(url, json=payload) so the library serializes the body and sets the header, and verify with response.request.headers. For list endpoints, read the pagination contract (cursor, Link header, or page/offset parameters), loop until the next-page indicator is empty, and add a safety cap. Handle 429 by honoring Retry-After with exponential backoff, and treat 5xx as retryable while 4xx (except 429) is not.