Loading...
Loading...
Python interview guide
Comprehensions, generators, decorators, context managers and typing. Every answer is short enough to say aloud, and every question links to the lesson that teaches it.
45 questionsLast updated
It wraps the function and stores results in a dictionary keyed by the call arguments, so arguments must be hashable. With maxsize set, the least recently used result is dropped when the cache is full; maxsize=None (or functools.cache) never evicts. It is thread-safe for its own bookkeeping, supports cache_info() and cache_clear(), and should only be used on pure functions.
The cache key includes self, so the cache holds a reference to every instance it has seen, which stops them from being garbage collected, and two equal-looking objects are separate keys unless __eq__ and __hash__ are defined. Prefer a module-level function, functools.cached_property for one value per instance, or a per-instance dictionary.
lru_cache and functools.cache?functools.cache (Python 3.9+) is the same as lru_cache(maxsize=None): a simple unbounded cache that is a little faster because it never evicts. lru_cache with a size bound is safer for long-running programs with many different inputs because memory use stays limited.
The plain recursion recalculates the same fib(k) values many times, giving O(2^n) calls. With a cache each fib(k) is computed once and later calls return the stored result, so there are O(n) distinct calls. The cost is O(n) memory for the cache and for the recursion depth.
When the decorator needs readable state (such as calls or a results list), several helper methods, or configuration stored as attributes. The class takes the function in __init__, implements __call__, and uses functools.update_wrapper(self, func) to keep the name and docstring. For simple wrapping, a function decorator is shorter.
@deco and @deco(arg)?Make the first parameter optional: def deco(func=None, *, times=1). If func is given, the decorator was used without brackets, so return the wrapper directly. If it is None, it was called with arguments, so return a function that accepts the function. This is a common pattern but makes the code harder to read, so use it only when the flexibility is worth it.
Use a decorator factory: retry(times, exceptions=(ConnectionError,), delay=0). The wrapper loops, catches only the listed exceptions, waits between attempts (time.sleep), re-raises the last error when attempts run out, returns the first success, and uses @wraps. Avoid retrying non-idempotent actions such as payments unless they are made safe to repeat.
The wrapper replaced the original and did not copy its metadata. Add @functools.wraps(func) to the wrapper. This restores __name__, __doc__, and __wrapped__, which also helps logging, debugging and frameworks that use function names.
@repeat(3) decorates ping.Python evaluates repeat(3), which returns decorator with times = 3 in its closure. Python then calls decorator(ping), which returns wrapper, and binds the name ping to wrapper. Each later ping() call runs the wrapper, which calls the original three times.
When results depend on anything but the arguments (time, files, random numbers, database state), because stale values will be returned. Also when arguments are unhashable or huge, when the number of distinct inputs is unbounded and maxsize=None is used (memory grows), and on methods where self in the key keeps objects alive.
A closure fits a tiny amount of private state behind one function. A decorator fits behaviour that wraps many functions the same way. A class fits state with several methods, readable attributes and a clear identity. Start with the simplest and move to a class when you find yourself adding more closures that share state.
Lesson: Decorators and Closures CheckpointA context manager is an object that defines what happens on entering and leaving a with block (__enter__ and __exit__). It guarantees cleanup, such as closing a file or releasing a lock, even if an exception occurs, and removes repeated try/finally code. Files, locks, database connections and temporary directories all support it.
A with statement packages the same guarantee as try/finally: the cleanup always runs. The manager owns the cleanup code, so each use is one line and the cleanup cannot be forgotten. try/finally is still right for one-off cleanup that has no manager to reuse.
Lesson: The with Statement and Resource CleanupThe context manager's __exit__ runs first with the exception details. If __exit__ returns a true value, the exception is suppressed. Otherwise (the usual case), the exception continues to the caller after the cleanup is finished.
In reverse order of entering, like a stack: with A() as a, B() as b: enters A then B, and exits B then A. This matters when B depends on A, for example a cursor that depends on a connection.
No. It is whatever __enter__ returns. For files that is the file itself, but tempfile.TemporaryDirectory() returns the folder name and a decimal.localcontext() returns the new context. A manager may also return nothing, so as is optional.
The file may not be flushed or closed. Buffered data can be lost, the handle stays open until garbage collection, and on some systems the file cannot be deleted or opened for writing elsewhere. In long-running programs, leaked handles can reach the operating system limit.
Lesson: The with Statement and Resource CleanupNo. Variables assigned inside a with block, and the as name, remain available after it, as with if and for. Only the resource is cleaned up; the name still refers to the (closed) object.
Define __enter__(self) for setup, returning the object that as should receive, and __exit__(self, exc_type, exc, tb) for cleanup. Python calls __exit__ whenever the block ends, normally or by an exception. For short managers, contextlib.contextmanager is often neater.
__exit__ and what should it return?They are the exception type, the exception instance and the traceback, or None, None, None if the block finished normally. Return a true value only to suppress the exception. Returning None or False lets it propagate after cleanup.
__exit__?Only for a specific, expected error, such as ignoring "file not found" while deleting. Check the exception type before returning True. Suppressing everything hides bugs and makes failures hard to find. contextlib.suppress(FileNotFoundError) makes the intent clear.
__exit__ itself raises an exception?The new exception replaces the one from the block, and the original is attached as __context__, so you see both in the traceback. This can hide the original problem, so keep cleanup code simple and guard each step.
__enter__ raises an exception?The block is never entered and __exit__ is not called, because the manager was not set up. If __enter__ acquires several things, it must release the first ones itself when a later step fails, or use ExitStack.
__enter__ starts the transaction and returns the connection or cursor. __exit__ commits if exc_type is None and rolls back otherwise, then returns False so the error still reaches the caller. Always close the cursor or connection in the same method.
A context manager wraps a block of code inside a function, with with. A decorator wraps a whole function. contextlib.ContextDecorator lets one object work as both: @recorder() applies the manager around every call.
contextlib.contextmanager work?It wraps a generator function in an object that implements __enter__ and __exit__. __enter__ runs the generator up to its single yield and returns the yielded value. __exit__ resumes the generator; if the block raised an error, it is thrown into the generator at the yield. The decorator also checks that the generator ends after one yield.
If the block raises, the exception is raised at the yield line. Code after the yield that is not in finally is skipped, so the cleanup (closing a file, restoring a setting) would not happen. try/yield/finally mirrors what __exit__ guarantees.
Choose a class when the manager needs to be reused or re-entered, expose attributes or methods, be inherited, or control exception suppression in detail. Choose @contextmanager for short, single-use setup/cleanup, which needs fewer lines.
suppress(*exceptions) ignores the listed exception types raised in the block and continues after it. The risk is hiding real problems if the list is too broad or the block is too large, because code after the failing line in the block is skipped. Keep the block to one line and the list specific.
It manages a number of context managers that is only known at run time, such as opening every file in a list, or optional resources that are only sometimes acquired. It also lets you register arbitrary cleanup callbacks, and it unwinds correctly if one of the later steps fails.
Lesson: contextlib: @contextmanager and HelpersA class that inherits from contextlib.ContextDecorator can be used as with Recorder(): and as @Recorder() above a function, which then runs inside the manager on each call. A @contextmanager function is also usable as a decorator in the same way.
They use async with and the methods __aenter__ and __aexit__, so setup and cleanup can await things such as network connections. contextlib.asynccontextmanager builds them from async generators. The behaviour mirrors the synchronous version.
It evaluates the expression to get the manager, calls __enter__ and binds its return value to the as name, runs the block, and then calls __exit__ with the exception details or three None values. If __exit__ returns a true value after an exception, the exception is suppressed; otherwise it continues after the cleanup.
Save the old value, apply the new one, yield, and restore the old value in finally. Using a generator-based manager or a class, the restore must run on every exit path, including exceptions. Do not use a global without saving, and keep the restore simple so it cannot fail.
Use a small fake manager that records __enter__ and __exit__ calls (and the exception arguments), or unittest.mock.MagicMock, which supports the protocol. Check that cleanup ran after a failure and that the right exception reached the caller. Temporary directories (tempfile) help for file code.
with suppress(Exception) a code smell?It hides every ordinary error in the block, including bugs such as typos in names and type errors, so failures disappear silently. Suppress only a specific expected exception, and keep the protected block as small as possible.
Lesson: Context Managers Checkpointthreading.Lock (and RLock), tempfile.TemporaryDirectory, decimal.localcontext, unittest.TestCase.assertRaises, contextlib.suppress, sqlite3.Connection (commit or rollback) and zipfile.ZipFile. Each pairs a setup with a guaranteed cleanup.
No. Python stores them in __annotations__ and never checks them. Tools such as mypy, and editors, read them to find mistakes before the program runs. Libraries can also read them at runtime, for example to validate data, but that is their own code, not the language.
They document what a function expects, let editors offer better completion, and let a checker catch wrong calls early. In a large codebase, they make refactoring safer. They cost little, and you can add them gradually.
Lesson: Annotating Functions and ClassesYou may annotate some code and leave the rest alone. A checker verifies the annotated parts and treats the rest as unknown. This lets a team add hints to a big project one file at a time.
Lesson: Annotating Functions and Classes-> None and no return annotation?-> None states that the function returns nothing and lets the checker flag any use of its result. With no annotation the function is untyped, and mypy by default does not check its body.
Use Self from typing on Python 3.11 and later. Older code uses the class name in quotes, such as -> "Cart", because the class does not exist yet while its body runs.
x: int and x: int = 0?The first only declares the type and creates no value, so reading x raises an error. The second declares the type and assigns 0.
Function annotations live in func.__annotations__. Class and module level annotations live in __annotations__ on the class or module. All of them are ordinary dictionaries.
list[int] rather than List[int]?Since Python 3.9 the built-in list, dict, set and tuple accept type arguments directly. typing.List and friends still work but are the old style and are no longer needed in new code.
Each lesson has quizzes, practice and more interview questions on the same topic.
Open the first lesson