Python Module 18
Advanced Python: data model, metaprogramming and internals interview questions
Interview questions on “Advanced Python: data model, metaprogramming and internals”.
Warm-up questions
Short questions with short answers, each answered in full.
-
Fresher to mid
What is the difference between __getattr__ and __getattribute__?
-
Fresher to mid
What is the difference between a data descriptor and a non-data descriptor?
Answered in the lesson Descriptors: how properties and methods work
-
Fresher to mid
What is a metaclass in Python, and which one do classes use by default?
Answered in the lesson Class creation: __init_subclass__ and metaclasses
-
Fresher
What is the difference between dir() and vars()?
Answered in the lesson Introspection: inspect, vars() and type()
Questions with model answers
What each question tests, the first step of a good answer and the follow-ups to expect.
-
Mid to senior
A wrapper class forwards attribute reads to self._target from __getattr__. It works everywhere until copy.copy() or pickle.loads() meets it, and both die with RecursionError. Explain the chain of calls, and fix the class so that copies and pickles work.
- What it tests
- How __getattr__ behaves on objects created without __init__, what copy and pickle look up, and writing a fallback that cannot recurse.
- First step
- Ask how copy and pickle build the new object: through __new__, without calling __init__, so it has no _target yet. Then trace the first attribute they read on it, __setstate__, which is missing, so __getattr__ runs and reads self._target, which is missing too.
- Follow-ups
-
- Why does the bug never show up in ordinary use?
- Would writing __getattribute__ instead of __getattr__ avoid it?
- Which test would keep it from coming back?
From the lesson Attribute lookup and the data model
Model answer, Pro. Model answers open with any pass; prices are in the notice on this page.Opening the model answers with your pass…
Model answers open with any MySmartCoPilot pass.
- Pro pass · 1 week₹79$2.99
- 30 days₹179$5.99
- 1 year₹1,699$45
One payment; a pass never renews. The price shown is the total you pay: no fees or charges are added at checkout.
Passes can’t be bought right now, so the model answers can’t be opened at the moment. If you have a pass, enter your licence key.
It also opens every other in-depth part in Learn and every Pro tool. Sales are final, except where the law gives you a right to withdraw, for example in the EU and the UK (refund policy).
In the answer:
- Model answer
- Follow-up answers
- Common mistakes
- Rubric
- Variants
- about 3 minutes
-
Mid to senior
A class has a property area and a __getattr__ that raises "no such field". Production logs show AttributeError: no such field 'area', although area is plainly defined. What is going on, how do you find the real bug, and how do you stop such errors from being hidden?
- What it tests
- Knowing that a property raising AttributeError counts as a failed lookup, debugging around __getattr__, and designing fallbacks that do not swallow real errors.
- First step
- Start from the rule that __getattr__ runs whenever the normal lookup raises AttributeError, and a property's getter raising AttributeError counts. So the getter of area failed inside, perhaps on a misspelt attribute, and __getattr__ replaced that error with its own message about area.
- Follow-ups
-
- How do you see the original error without changing the class?
- How should __getattr__ be written so that it cannot hide getter bugs?
- Can the same thing happen without any __getattr__?
From the lesson Attribute lookup and the data model
Model answer, Pro.In the answer:
- Model answer
- Follow-up answers
- Common mistakes
- Rubric
- Variants
- about 3 minutes
-
Senior to staff
Your team wrapped a vendor's payment client in a proxy: __getattr__ forwards every attribute and adds retries to methods named in a list. Months later, with client: fails, isinstance checks against the vendor's class fail, and a new vendor method runs without retries. What went wrong in the design, and what would you build instead?
- What it tests
- Special method lookup, isinstance and proxies, forwarding by name against an explicit interface, and choosing between a proxy, a subclass and an adapter.
- First step
- Separate the three failures by mechanism. with looks up __enter__ and __exit__ on the proxy's class, isinstance checks the proxy's class, and the retries cover only the names in the list. Then ask which operations the business needs from the client, because that list is the real interface.
- Follow-ups
-
- Would subclassing the vendor's client have been better?
- How can a proxy pass isinstance checks, and should it?
- How do you keep the wrapper honest for type checkers?
From the lesson Attribute lookup and the data model
Model answer, Pro.In the answer:
- Model answer
- Follow-up answers
- Common mistakes
- Rubric
- Variants
- about 3 minutes
-
Mid to senior
Twelve model classes have 40 properties between them, and almost all check "a positive number" or "a string of at most N characters". A teammate proposes descriptor classes instead. Make the case for and against, and say what you would do.
- What it tests
- Weighing properties against reusable descriptors: duplication, error messages, readability, performance, tooling and type checking, and knowing the library alternatives.
- First step
- Count the duplication first: 40 getter and setter pairs repeat perhaps five patterns. A descriptor writes each pattern once and learns its field's name from __set_name__, so the comparison is 40 hand-written properties against about five small classes and 40 one-line declarations.
- Follow-ups
-
- What do type checkers and editors make of a custom descriptor?
- Would dataclasses or a validation library be a better answer?
- How do you keep error messages as good as the hand-written ones?
From the lesson Descriptors: how properties and methods work
Model answer, Pro.In the answer:
- Model answer
- Follow-up answers
- Common mistakes
- Rubric
- Variants
- about 3 minutes
-
Senior to staff
Design the column attribute of a small ORM so that User.email gives an object you can build queries with, while user.email gives that user's value. Which descriptor methods do you write, what does each receive, and what breaks with __slots__ or with a subclass that redefines the column?
- What it tests
- Using the instance-is-None case of __get__, __set_name__ for column names, per-instance storage and change tracking, and descriptor behaviour under slots and inheritance.
- First step
- Start from the two calls Python makes: User.email calls __get__(None, User) and user.email calls __get__(user, User). Return a query expression in the first case and the stored value in the second, and let __set_name__ record the column's name and owner.
- Follow-ups
-
- How does User.email == 'a@example.com' build a query instead of returning False?
- Where do the loaded values live, and how do you know which ones changed?
- What happens when a subclass assigns a different Column to email?
From the lesson Descriptors: how properties and methods work
Model answer, Pro.In the answer:
- Model answer
- Follow-up answers
- Common mistakes
- Rubric
- Variants
- about 3 minutes
-
Mid to senior
Plugins register themselves through __init_subclass__. In the test suite the registry holds 14 plugins; in the production service it holds 3, and nothing raised an error. What is happening, and how do you make plugin discovery reliable?
- What it tests
- Knowing that registration happens when a class statement runs, which is when its module is imported, and designing discovery with explicit imports, entry points and checks at start-up.
- First step
- Registration runs when the class statement runs, and that happens when the plugin's module is imported. So compare which modules each process imports: the test suite probably imports every plugin module, while production imports only the three that some other code happens to use.
- Follow-ups
-
- How do entry points in pyproject.toml help?
- What check at start-up would have caught this?
- Can lazy imports make the problem worse?
From the lesson Class creation: __init_subclass__ and metaclasses
Model answer, Pro.In the answer:
- Model answer
- Follow-up answers
- Common mistakes
- Rubric
- Variants
- about 3 minutes
-
Senior to staff
A teammate proposes a metaclass that wraps every method of 30 service classes with logging and timing. What questions do you ask, which alternatives do you weigh, and when would you accept the metaclass?
- What it tests
- Judging a metaclass against __init_subclass__, class decorators and explicit decorators on inheritance, conflicts, debugging, performance and signatures.
- First step
- Ask what must be logged, for whom, and at what cost per call, then separate the mechanism from the policy: wrapping methods needs no metaclass, because __init_subclass__ can wrap the methods of each subclass and a decorator can wrap them explicitly.
- Follow-ups
-
- How do you keep signatures and docstrings intact for the wrapped methods?
- What happens to staticmethod, classmethod and property objects in the class body?
- How would you switch the logging off for one hot method?
From the lesson Class creation: __init_subclass__ and metaclasses
Model answer, Pro.In the answer:
- Model answer
- Follow-up answers
- Common mistakes
- Rubric
- Variants
- about 3 minutes
-
Mid to senior
Combining your framework's base class with abc.ABC raises "metaclass conflict". Explain the rule behind the error, and give two ways out with their costs.
- What it tests
- How Python picks the most derived metaclass, why unrelated metaclasses conflict, and the costs of a combined metaclass against removing the metaclass.
- First step
- State the rule: a class has exactly one metaclass, and Python picks the candidate that is a subclass of the metaclasses of all the bases. The framework's metaclass and ABCMeta are unrelated, so no candidate qualifies, and the class statement fails before any class is created.
- Follow-ups
-
- Why does a class with only abc.ABC and plain classes as bases never conflict?
- Can the framework fix this for all its users?
- What must a combined metaclass do with keyword arguments?
From the lesson Class creation: __init_subclass__ and metaclasses
Model answer, Pro.In the answer:
- Model answer
- Follow-up answers
- Common mistakes
- Rubric
- Variants
- about 3 minutes
-
Mid to senior
Your web framework builds request validators from handler signatures with inspect.signature(). After someone adds a timing decorator to every handler, all handlers accept any arguments and validation silently stops. Diagnose the bug, and make the framework robust against it.
- What it tests
- How decorators hide signatures, functools.wraps and __wrapped__, the follow_wrapped option, and failing loudly when a signature cannot be trusted.
- First step
- Print inspect.signature() of one decorated handler: it shows (args, *kwargs), the wrapper's own parameters, because the decorator did not use functools.wraps. Without __wrapped__, signature() has nothing to follow back to the real handler.
- Follow-ups
-
- Should you fix the decorator, or the framework?
- What should the framework do with a handler whose signature is (args, *kwargs)?
- How do you handle decorators from libraries you cannot change?
From the lesson Introspection: inspect, vars() and type()
Model answer, Pro.In the answer:
- Model answer
- Follow-up answers
- Common mistakes
- Rubric
- Variants
- about 3 minutes
-
Mid to senior
A health-check endpoint calls hasattr(client, 'session') on 40 client objects. After a library upgrade its response time jumps from 5 ms to 2 s, and sometimes it raises ConnectionError. Explain what is happening and how you would check for the attribute without side effects.
- What it tests
- Knowing that hasattr() reads the attribute and runs properties, which exceptions it turns into False, and static alternatives such as inspect.getattr_static.
- First step
- hasattr() is getattr() with the result thrown away, so if the upgrade made session a property, every check now runs the property's code, here opening a connection. Confirm it with inspect.getattr_static(client, 'session'), which returns the property object without running it.
- Follow-ups
-
- Why does ConnectionError escape while a missing attribute gives False?
- When is getattr_static the wrong tool?
- What would you change in the health check's design?
From the lesson Introspection: inspect, vars() and type()
Model answer, Pro.In the answer:
- Model answer
- Follow-up answers
- Common mistakes
- Rubric
- Variants
- about 3 minutes
-
Mid to senior
A rules feature shows users the source of their rule functions with inspect.getsource(). It works on developers' machines but raises OSError in the deployed app. Give the likely causes and the alternatives you would consider.
- What it tests
- Where getsource() finds source text, why it fails without source files or for code built with exec(), and designing features that do not depend on files being present.
- First step
- Recall where the text comes from: getsource() reads the file named in the function's code object, through linecache, starting at the recorded line. So ask how the app is deployed and how rules are built: only .pyc files, a frozen executable or rules made with exec() leave no readable file.
- Follow-ups
-
- Why does it work for some rules and fail for others?
- How would you keep the source available if the feature needs it?
- What are the risks of showing source code to users?
From the lesson Introspection: inspect, vars() and type()
Model answer, Pro.In the answer:
- Model answer
- Follow-up answers
- Common mistakes
- Rubric
- Variants
- about 3 minutes
-
Mid to senior
A service reads its feature flags at start-up with eval() on the text of flags.txt. A teammate says this is safe because the file lives on your own servers. Do you agree? What would you change, and how would you move the existing files over without an outage?
- What it tests
- Treating configuration as data that crosses a trust boundary, choosing literal_eval or a data format over eval, and migrating existing files safely.
- First step
- Ask who can write the file and how it reaches the server: a deploy pipeline, an admin tool, a shared volume. Whoever can change it can run code inside the service with the service's permissions, so today the file is code, not data, and the task is to make it data.
- Follow-ups
-
- The file holds values such as timedelta(minutes=5). How do you keep them?
- How do you find every other eval() and exec() in a large codebase?
- What limits would you put on the file itself?
From the lesson The ast module: read code without running it
Model answer, Pro.In the answer:
- Model answer
- Follow-up answers
- Common mistakes
- Rubric
- Variants
- about 4 minutes
-
Mid to senior
A product team wants users to type spreadsheet-style formulas such as price * qty * (1 - discount). An engineer proposes eval(formula, {'__builtins__': {}}, row). Explain why you would not ship that, and design the feature instead.
- What it tests
- Knowing that a restricted eval() is not a sandbox, evaluating an allowlist of node types, resource limits, and error messages that users can act on.
- First step
- Start from the documentation of eval(): overriding the built-ins is not a security mechanism, so no choice of globals makes user text safe to run. The boundary has to be what a formula may contain, and the formula's tree tells you that before anything runs.
- Follow-ups
-
- How do you stop 9 9 9 from tying up a server?
- Users want round() and max(). How do you add them safely?
- How would you test the evaluator against inputs nobody thought of?
From the lesson The ast module: read code without running it
Model answer, Pro.In the answer:
- Model answer
- Follow-up answers
- Common mistakes
- Rubric
- Variants
- about 4 minutes
-
Senior to staff
You must rename a deprecated function, get_rate(), to fetch_rate() across 2,000 files. A teammate writes an ast.NodeTransformer and saves ast.unparse() of each new tree. What goes wrong, and how would you make the change instead?
- What it tests
- What a tree keeps and loses, node positions and their UTF-8 offsets, names that refer to something else, and showing that a large automated change is correct.
- First step
- Run the transformer on one file and compare the output with the original: ast.unparse() writes code from the tree, which holds no comments and no layout, so every line of every file changes, not only the calls. Use the nodes only to find the calls, then edit the original text at their positions.
- Follow-ups
-
- How do you avoid renaming a different get_rate defined in another module?
- How do you show that the change is correct?
- What about calls made through getattr(module, 'get_rate')?
From the lesson The ast module: read code without running it
Model answer, Pro.In the answer:
- Model answer
- Follow-up answers
- Common mistakes
- Rubric
- Variants
- about 3 minutes
-
Mid to senior
After a deploy, a worker still behaves as if reports.py were the old version: a function added in the new version is missing, although the new file is on disk. How do you find which code the worker is running and why, and how do you stop it happening again?
- What it tests
- Using spec.origin and __file__, the order of sys.path, the module cache of a long-running process, and stray copies such as an old installed package.
- First step
- Ask the process itself: log reports.__file__ and reports.__spec__.origin, or call importlib.util.find_spec() in a fresh interpreter started the same way. Either the worker imported another copy earlier on sys.path, or it is an old process whose sys.modules still holds the module it imported before the deploy.
- Follow-ups
-
- The origin points into site-packages. How did an old copy get there?
- The worker was never restarted. Why does the new file not matter?
- Which check at start-up would catch this?
From the lesson Inside the import system
Model answer, Pro.In the answer:
- Model answer
- Follow-up answers
- Common mistakes
- Rubric
- Variants
- about 3 minutes
-
Mid to senior
Your team wants pricing rules, written as a Python module, to change in a long-running service without a restart, by calling importlib.reload() when the file changes. What breaks, and what would you build instead?
- What it tests
- Knowing what reload() keeps (from-imports, instances of old classes, module globals), its thread-safety, and designing rule changes as checked data with an atomic switch.
- First step
- List who holds the module's objects: other modules that did from pricing import ..., objects created from its classes, functions passed around as callbacks. reload() rebinds names inside the module only, so all of those keep the old objects while new code runs beside them.
- Follow-ups
-
- Two threads serve requests while the reload runs. What can they see?
- The new file has a syntax error. What happens?
- When is reload() the right tool?
From the lesson Inside the import system
Model answer, Pro.In the answer:
- Model answer
- Follow-up answers
- Common mistakes
- Rubric
- Variants
- about 3 minutes
-
Mid to senior
A command-line tool takes 1.8 seconds before it prints --help, and users notice. How do you find which imports cost the time, and what would you change, on Python 3.14 and on 3.15?
- What it tests
- Measuring import cost with -X importtime, telling self time from cumulative time, and the options: moving imports, LazyLoader, lazy imports in 3.15, and work done at import time.
- First step
- Measure before changing anything: run the tool with python -X importtime and sort the modules by cumulative time. The few largest entries, often a heavy library imported at module level for one subcommand, account for most of the 1.8 seconds.
- Follow-ups
-
- The slowest module is your own config module. What do you look for in it?
- What does -X importtime=2 add in Python 3.14?
- What do lazy imports cost you?
From the lesson Inside the import system
Model answer, Pro.In the answer:
- Model answer
- Follow-up answers
- Common mistakes
- Rubric
- Variants
- about 3 minutes