PCPP2 cheat sheet
PCPP2 · Keep the key ideas within reach.
Testing principles and techniques (unittest and pytest)
unittest: tests subclass unittest.TestCase; assertions are methods (assertEqual, assertTrue, assertRaises); test discovery matches files named test*.py and methods prefixed test_.
pytest: plain assert statements; assertion rewriting reports the values that failed; test functions or classes named Test* are collected; fixtures inject dependencies via function parameters.
Fixture scoping in pytest: scope="function" (default, fresh per test), "class", "module", "package", "session"; use yield in a fixture for setup/teardown around the yield point.
parametrize: @pytest.mark.parametrize("a,b,expected", [(1,1,2),(2,3,5)]) generates one test per tuple; stack decorators to get the Cartesian product of parameter sets.
Mocking: unittest.mock.patch replaces an attribute for the duration of the with-block or decorated function; Mock records call_args, call_count, and return_value; autospec=True checks call signatures.
Exception testing: self.assertRaises(ValueError) as context in unittest; pytest.raises(ValueError, match="pattern") with a regex match on str(excinfo.value).
unittest lifecycle order: setUpClass (once per class) -> setUp -> test method -> tearDown -> tearDownClass; pytest equivalents are module/session fixtures plus function fixtures.
Test doubles vocabulary: stub returns canned data, mock verifies interactions, fake is a working lightweight implementation, spy records calls while delegating to the real object.
Design patterns (OOP principles and GoF patterns)
SOLID: Single responsibility, Open/closed, Liskov substitution, Interface segregation, Dependency inversion (depend on abstractions, not concretions).
Singleton: one instance per process; in Python implement with a class attribute plus new, or a module-level instance, since modules are cached in sys.modules.
Factory Method defers instantiation to subclasses; Abstract Factory groups related factories; a plain factory function returning one of several classes is the common Python form.
Facade: a single simplified interface over a subsystem of several classes; it does not add behavior, only reduces coupling for callers.
Proxy: same interface as the real subject, controls access (lazy load, permission check, remote call, caching); differs from Decorator, which adds behavior.
Observer: subject keeps a list of observers and calls update() on notify; Python alternative is callback registration or weakref.WeakSet to avoid leaks.
Command: encapsulate a request as an object with execute() (and often undo()); enables queues, logging, and macro recording of operations.
Template Method: base class defines the algorithm skeleton in a concrete method and calls abstract hook methods that subclasses override; State pattern delegates behavior to a state object that can replace itself.
Interprocess communication (multiprocessing, threading, subprocess, synchronization)
GIL: threads suit I/O-bound work and share memory; multiprocessing bypasses the GIL for CPU-bound work but each process has separate memory, so data must be pickled across the boundary.
multiprocessing primitives: Process(target=, args=), Pool.map/imap for fan-out, Queue and Pipe for messaging; objects passed must be picklable.
Synchronization: Lock (mutual exclusion), RLock (reentrant, same thread may re-acquire), Semaphore (counted permits), Event (flag with wait/set), Condition (wait/notify), Barrier (N parties meet).
threading.Lock supports the context manager (with lock:) and is non-reentrant; threading.RLock is required when a locked section re-enters the same lock.
subprocess.run(args, capture_output=True, text=True, check=True) is the modern API; check=True raises CalledProcessError on nonzero exit; shell=True invokes a shell and risks injection.
Pass argv as a list (never a concatenated string) when shell=False; use timeout= to bound runtime, which raises TimeoutExpired and kills the child.
multiprocessing start methods: fork (default on Linux, fast but unsafe with threads), spawn (default on macOS/Windows, fresh interpreter, requires main guard), forkserver.
Deadlock avoidance: acquire multiple locks in a fixed global order; use timeouts on acquire(); prefer queues over shared mutable state to avoid lost updates.
Network programming (sockets, automation, SDN, security)
socket.socket(AF_INET, SOCK_STREAM) is TCP; SOCK_DGRAM is UDP; server sequence is bind -> listen -> accept, client sequence is connect -> send/recv.
recv(n) returns at most n bytes and b'' means the peer closed; TCP is a byte stream with no message boundaries, so length-prefix or delimiter framing is required.
socket.create_connection((host, port), timeout=) handles address resolution and timeout; getaddrinfo(host, port, type=SOCK_STREAM) returns address tuples for IPv4/IPv6 portability.
SO_REUSEADDR lets a restarted server rebind a port in TIME_WAIT; SO_REUSEPORT allows multiple sockets to bind the same port for load distribution.
Automation libraries: netmiko/paramiko for SSH to devices, napalm for multi-vendor config, ncclient for NETCONF, requests for RESTCONF/HTTP APIs; config is idempotent when applied declaratively.
SDN separates control plane (controller computes forwarding) from data plane (switches forward); OpenFlow is the classic southbound protocol; northbound APIs are typically REST/JSON.
TLS: ssl.create_default_context() gives verified client/server contexts with hostname checking; never disable CERT_REQUIRED in production; wrap sockets with context.wrap_socket().
Security rules: validate and bound all input lengths, prefer TLS over plaintext, use hmac.compare_digest for secret comparison, and never interpolate untrusted data into shell commands.
Python-SQL and Python-NoSQL database access
DB-API flow: connect() -> cursor() -> execute(sql, params) -> fetchone/fetchall/fetchmany -> commit() -> close(); parameter placeholders are driver-specific (qmark ? for sqlite3, %s for psycopg).
Always pass parameters as a tuple to execute(); string concatenation enables SQL injection; executemany() batches inserts efficiently.
Transactions: connections default to implicit transactions in many drivers; call commit() to persist and rollback() on exception; use the connection as a context manager where supported.
ORM concepts: declarative model classes map to tables, Session/Unit of Work tracks changes and flushes them, lazy vs eager loading (selectinload/joinedload) controls query count.
SQLAlchemy Core uses select(), insert(), update(), delete() with expression objects; the ORM layer adds identity map, relationship(), and cascade rules.
NoSQL families: document (MongoDB), key-value (Redis), wide-column (Cassandra), graph (Neo4j); choose by access pattern, not by hype; schema-on-read shifts validation to the app.
pymongo: collection.insert_one/insert_many, find_one/find with query dicts, update_one with $set, delete_one; indexes via create_index support unique and compound keys.
N+1 query problem: one query for parents plus one per child; fix with a join, a batched IN query, or ORM eager loading; measure with query logging.
Testing principles and techniques (unittest and pytest)
unittest: tests subclass unittest.TestCase; assertions are methods (assertEqual, assertTrue, assertRaises); test discovery matches files named test*.py and methods prefixed test_.
pytest: plain assert statements; assertion rewriting reports the values that failed; test functions or classes named Test* are collected; fixtures inject dependencies via function parameters.
Fixture scoping in pytest: scope="function" (default, fresh per test), "class", "module", "package", "session"; use yield in a fixture for setup/teardown around the yield point.
parametrize: @pytest.mark.parametrize("a,b,expected", [(1,1,2),(2,3,5)]) generates one test per tuple; stack decorators to get the Cartesian product of parameter sets.
Mocking: unittest.mock.patch replaces an attribute for the duration of the with-block or decorated function; Mock records call_args, call_count, and return_value; autospec=True checks call signatures.
Exception testing: self.assertRaises(ValueError) as context in unittest; pytest.raises(ValueError, match="pattern") with a regex match on str(excinfo.value).
unittest lifecycle order: setUpClass (once per class) -> setUp -> test method -> tearDown -> tearDownClass; pytest equivalents are module/session fixtures plus function fixtures.
Test doubles vocabulary: stub returns canned data, mock verifies interactions, fake is a working lightweight implementation, spy records calls while delegating to the real object.
Design patterns (OOP principles and GoF patterns)
SOLID: Single responsibility, Open/closed, Liskov substitution, Interface segregation, Dependency inversion (depend on abstractions, not concretions).
Singleton: one instance per process; in Python implement with a class attribute plus new, or a module-level instance, since modules are cached in sys.modules.
Factory Method defers instantiation to subclasses; Abstract Factory groups related factories; a plain factory function returning one of several classes is the common Python form.
Facade: a single simplified interface over a subsystem of several classes; it does not add behavior, only reduces coupling for callers.
Proxy: same interface as the real subject, controls access (lazy load, permission check, remote call, caching); differs from Decorator, which adds behavior.
Observer: subject keeps a list of observers and calls update() on notify; Python alternative is callback registration or weakref.WeakSet to avoid leaks.
Command: encapsulate a request as an object with execute() (and often undo()); enables queues, logging, and macro recording of operations.
Template Method: base class defines the algorithm skeleton in a concrete method and calls abstract hook methods that subclasses override; State pattern delegates behavior to a state object that can replace itself.
Interprocess communication (multiprocessing, threading, subprocess, synchronization)
GIL: threads suit I/O-bound work and share memory; multiprocessing bypasses the GIL for CPU-bound work but each process has separate memory, so data must be pickled across the boundary.
multiprocessing primitives: Process(target=, args=), Pool.map/imap for fan-out, Queue and Pipe for messaging; objects passed must be picklable.
Synchronization: Lock (mutual exclusion), RLock (reentrant, same thread may re-acquire), Semaphore (counted permits), Event (flag with wait/set), Condition (wait/notify), Barrier (N parties meet).
threading.Lock supports the context manager (with lock:) and is non-reentrant; threading.RLock is required when a locked section re-enters the same lock.
subprocess.run(args, capture_output=True, text=True, check=True) is the modern API; check=True raises CalledProcessError on nonzero exit; shell=True invokes a shell and risks injection.
Pass argv as a list (never a concatenated string) when shell=False; use timeout= to bound runtime, which raises TimeoutExpired and kills the child.
multiprocessing start methods: fork (default on Linux, fast but unsafe with threads), spawn (default on macOS/Windows, fresh interpreter, requires main guard), forkserver.
Deadlock avoidance: acquire multiple locks in a fixed global order; use timeouts on acquire(); prefer queues over shared mutable state to avoid lost updates.
Network programming (sockets, automation, SDN, security)
socket.socket(AF_INET, SOCK_STREAM) is TCP; SOCK_DGRAM is UDP; server sequence is bind -> listen -> accept, client sequence is connect -> send/recv.
recv(n) returns at most n bytes and b'' means the peer closed; TCP is a byte stream with no message boundaries, so length-prefix or delimiter framing is required.
socket.create_connection((host, port), timeout=) handles address resolution and timeout; getaddrinfo(host, port, type=SOCK_STREAM) returns address tuples for IPv4/IPv6 portability.
SO_REUSEADDR lets a restarted server rebind a port in TIME_WAIT; SO_REUSEPORT allows multiple sockets to bind the same port for load distribution.
Automation libraries: netmiko/paramiko for SSH to devices, napalm for multi-vendor config, ncclient for NETCONF, requests for RESTCONF/HTTP APIs; config is idempotent when applied declaratively.
SDN separates control plane (controller computes forwarding) from data plane (switches forward); OpenFlow is the classic southbound protocol; northbound APIs are typically REST/JSON.
TLS: ssl.create_default_context() gives verified client/server contexts with hostname checking; never disable CERT_REQUIRED in production; wrap sockets with context.wrap_socket().
Security rules: validate and bound all input lengths, prefer TLS over plaintext, use hmac.compare_digest for secret comparison, and never interpolate untrusted data into shell commands.
Python-SQL and Python-NoSQL database access
DB-API flow: connect() -> cursor() -> execute(sql, params) -> fetchone/fetchall/fetchmany -> commit() -> close(); parameter placeholders are driver-specific (qmark ? for sqlite3, %s for psycopg).
Always pass parameters as a tuple to execute(); string concatenation enables SQL injection; executemany() batches inserts efficiently.
Transactions: connections default to implicit transactions in many drivers; call commit() to persist and rollback() on exception; use the connection as a context manager where supported.
ORM concepts: declarative model classes map to tables, Session/Unit of Work tracks changes and flushes them, lazy vs eager loading (selectinload/joinedload) controls query count.
SQLAlchemy Core uses select(), insert(), update(), delete() with expression objects; the ORM layer adds identity map, relationship(), and cascade rules.
NoSQL families: document (MongoDB), key-value (Redis), wide-column (Cassandra), graph (Neo4j); choose by access pattern, not by hype; schema-on-read shifts validation to the app.
pymongo: collection.insert_one/insert_many, find_one/find with query dicts, update_one with $set, delete_one; indexes via create_index support unique and compound keys.
N+1 query problem: one query for parents plus one per child; fix with a join, a batched IN query, or ORM eager loading; measure with query logging.