skip to content

OOP and Classes

What Python does when you write `class`: two-step construction, C3 method resolution, the dunder protocols, dataclasses, descriptors and metaclasses. Interviewers use it to find the edge of your object model.

part ofPythonoverview, primer and where to startread it →
on this pageshow

explore

questions

128 · 8 sections

What is the difference between a class attribute and an instance attribute in Python?

level: juniorimportance: must knowfreq 78%
basics
~20 s

A class attribute is created in the class body and stored on the class, so every instance sees the same object. An instance attribute is created by assigning to self.name and lives in that one object's own namespace.

open as a page

How do a plain method, a @classmethod and a @staticmethod differ in Python?

level: juniorimportance: must knowfreq 82%
basics
~20 s

A plain method takes the instance as its first argument, self. A classmethod takes the class as cls, so a subclass gets its own class. A staticmethod receives nothing implicitly; it is just a function stored in the class namespace.

open as a page

In Python, what does `_name` mean versus `__name` on a class attribute?

level: juniorimportance: must knowfreq 70%
basics
~20 s

A single leading underscore is pure convention: it marks an attribute as internal and the interpreter ignores it. A double leading underscore inside a class body is rewritten by the compiler to _ClassName__name, which avoids subclass collisions rather than blocking access.

open as a page

Why does a mutable list defined in a Python class body end up shared by every instance?

level: middleimportance: must knowfreq 66%
basics
~20 s

The list is built once, when the class body runs, and stored on the class. Every instance reaches that same object through self.name, so an append from one is visible from all. Build the list in init instead.

open as a page

What is the difference between `weakref.ref` and `weakref.proxy` in Python?

level: middleimportance: must knowfreq 38%
basics
~20 s

Both refer to an object without keeping it alive. A weakref.ref is a callable: call it to get the object, or None once it is gone. A weakref.proxy forwards attribute access through, and raises ReferenceError once it is gone.

open as a page

What is a mixin class in Python, and how does it differ from a base class?

level: juniorimportance: must knowfreq 60%
basics
~20 s

A mixin is a small class that contributes methods to other classes through multiple inheritance and is never instantiated on its own. A base class says what an object is; a mixin only adds a slice of behaviour.

open as a page

In Python, if `class D(B, C)` and both B and C override `run()`, which one executes?

level: juniorimportance: must knowfreq 68%
basics
~10 s

B's run executes. Python flattens the hierarchy into one method resolution order — D, B, C, then their shared base, then object — and calls the first class in that list that defines run.

open as a page

What does a zero-argument super() call inside a method resolve to at runtime?

level: juniorimportance: must knowfreq 72%
basics
~10 s

A zero-argument super() builds a proxy that starts attribute lookup at the class after the one where the method was defined, following the receiver's method resolution order rather than the class's declared base.

open as a page

Why does d.copy() on a dict subclass return a plain dict while copy.copy(d) does not?

level: middleimportance: must knowfreq 45%
basics
~20 s

dict.copy(), dict(d) and {**d} are built-in constructors: they allocate an exact dict and copy the items, so your class and its instance attributes are lost. copy.copy(d) goes through the reduction protocol and rebuilds an instance of your subclass.

open as a page

When does Python call a base class's `__init_subclass__`, and what is `cls` bound to inside it?

level: middleimportance: must knowfreq 40%
basics
~20 s

Python calls init_subclass on the nearest parent every time a new subclass of it is created, passing the brand-new subclass as cls. It is implicitly a classmethod, and it never runs for the class that defines it.

open as a page

Which dunder methods make a custom class work with len(), obj[i] and `in`?

level: juniorimportance: must knowfreq 60%
basics
~20 s

len answers len(obj), getitem answers obj[i] and slicing, and contains answers x in obj. Defining them on any class makes the built-ins work, because Python looks for the methods, not for a particular base class.

open as a page

What is the difference between `__repr__` and `__str__` on a Python class?

level: juniorimportance: must knowfreq 78%
basics
~10 s

__repr__ returns the unambiguous developer-facing view, used by repr(), the REPL and debuggers. __str__ returns the readable user-facing view used by print() and str(). A missing __str__ falls back to __repr__, never the reverse.

open as a page

What contract must `__hash__` satisfy relative to `__eq__` in Python?

level: middleimportance: must knowfreq 64%
basics
~10 s

Objects that compare equal must return equal hash values, and an object's hash must not change while it lives. The converse is not required: equal hashes are a collision, resolved by ==.

open as a page

Why does defining `__eq__` make a class's instances unusable as dict keys?

level: middleimportance: must knowfreq 72%
basics
~10 s

When a class body defines eq without hash, Python sets hash to None in that class, so hash() raises TypeError: unhashable type. Define hash over the same fields to restore it.

open as a page

Why does assigning `obj.__len__ = lambda: 5` leave `len(obj)` raising TypeError?

level: middleimportance: must knowfreq 48%
basics
~20 s

Because len() resolves len on type(obj), not on the object. The lambda lands in the instance dictionary, which implicit special method lookup never reads, so the class still has no len and len() raises TypeError.

open as a page

In a typing.NamedTuple class, how do you give fields defaults and add methods?

level: juniorimportance: must knowfreq 65%
basics
~20 s

Write the record as a class: an annotated name with an assigned value becomes that field's default, and ordinary def statements become real methods on the generated tuple subclass. Defaulted fields must come last, exactly as with function parameters.

open as a page

What does the @dataclass decorator generate, and which class attributes become fields?

level: juniorimportance: must knowfreq 72%
basics
~10 s

@dataclass writes init, repr and eq from the class's annotated attributes, in declaration order. Only annotated names become fields; a plain assignment with no annotation stays an ordinary class attribute.

open as a page

What does @dataclass(frozen=True) actually prevent, and what does it not?

level: juniorimportance: must knowfreq 62%
basics
~20 s

Setting or deleting an attribute on a frozen dataclass instance raises dataclasses.FrozenInstanceError, because the decorator generates blocking setattr and delattr methods. It does not freeze what the fields point at: a list field can still be mutated in place.

open as a page

When does __post_init__ run in a Python dataclass, and what is it for?

level: juniorimportance: must knowfreq 55%
basics
~10 s

@dataclass generates an init that assigns every field, and if the class defines post_init that generated constructor calls it as its final statement. It is the hook for validating arguments and computing derived values.

open as a page

Why does @dataclass raise ValueError for `tags: list = []`, and what is the fix?

level: middleimportance: must knowfreq 66%
basics
~10 s

A default in the class body is created once and would be shared by every instance, so @dataclass rejects it at class-creation time. Use dataclasses.field(default_factory=list), which calls the factory per instance.

open as a page

What does Python's @property decorator do, and how do you add a setter to it?

level: juniorimportance: must knowfreq 78%
basics
~20 s

The @property decorator turns a method into a read-only attribute, so callers write obj.title rather than obj.title(). Decorating a second method of the same name with @title.setter makes assignment work and gives you one place to validate the value.

open as a page

Why does `self._data = value` inside `__setattr__` recurse forever, and what is the fix?

level: middleimportance: must knowfreq 45%
basics
~20 s

Because an assignment inside setattr is itself an attribute assignment, so it calls setattr again and never terminates, ending in RecursionError. Break the cycle by delegating to object.setattr or super().setattr, or by writing straight into the instance dictionary.

open as a page

How does `__getattribute__` differ from `__getattr__` on a Python class?

level: middleimportance: must knowfreq 55%
basics
~20 s

getattribute is called for every attribute access on an instance and performs the whole lookup itself; getattr is only the fallback the runtime calls when that lookup fails with AttributeError. Overriding getattribute touches every access and every internal read.

open as a page

How does functools.cached_property make every read after the first skip the descriptor?

level: middleimportance: must knowfreq 48%
basics
~20 s

It is a non-data descriptor: the first read runs the function and stores the result in the instance __dict__ under the same attribute name. Later reads find that entry first, so the descriptor never runs again.

open as a page

How do data and non-data descriptors differ in Python's attribute lookup order?

level: middleimportance: must knowfreq 55%
basics
~20 s

A data descriptor defines set or delete and outranks the instance dict; a non-data descriptor defines only get and loses to it. So instance data can shadow a non-data descriptor but never a data one.

open as a page

Why does instantiating a subclass of abc.ABC that leaves an @abstractmethod unimplemented raise TypeError?

level: juniorimportance: must knowfreq 65%
basics
~20 s

abc.ABCMeta collects every still-abstract name into a frozenset stored on the class as abstractmethods, and object construction refuses to run while that set is non-empty. The failure therefore lands at instantiation, not at class definition.

open as a page

What does defining `__slots__` on a Python class change about its instances?

level: juniorimportance: must knowfreq 45%
basics
~10 s

Instances lose their per-instance __dict__. Only the names listed in __slots__ can be assigned, each into a fixed storage slot, and assigning any other attribute raises AttributeError instead of silently creating it.

open as a page

What does the three-argument `type(name, bases, namespace)` call create in Python?

level: middleimportance: must knowfreq 55%
basics
~10 s

It creates and returns a brand-new class object at runtime. type('Duck', (Bird,), {'legs': 2}) produces the same kind of object a class Duck(Bird) statement would. The one-argument form, type(obj), instead reports an object's class.

open as a page

How must @abc.abstractmethod be stacked with @property or @classmethod, and why?

level: middleimportance: should knowfreq 40%
basics
~10 s

Put @abstractmethod innermost, directly above the def, with @property, @classmethod or @staticmethod outside it. The reverse order raises AttributeError while the class body runs, because those wrapper objects expose a read-only isabstractmethod.

open as a page

When a class uses `metaclass=Meta`, in what order do `__prepare__`, `__new__` and `__init__` run?

level: middleimportance: should knowfreq 40%
basics
~20 s

Meta.__prepare__ runs first and returns the mapping the class body executes into. The body then runs, and the filled mapping is handed to Meta.__new__, which creates the class object, and finally to Meta.__init__, which configures it.

open as a page

How do you combine enum.Flag members with | and test one with &?

level: juniorimportance: must knowfreq 45%
basics
~20 s

The | operator unions two enum.Flag members into one composite member carrying both bits, and & intersects them. Test a single flag with perms & Perm.READ, which is truthy only when that bit is set.

open as a page

How do enum.Enum members compare with `is` and `==`, and why?

level: juniorimportance: must knowfreq 60%
basics
~20 s

Each enum.Enum member is created once and cached, so every access returns the same object and is compares them exactly. A plain member equals only itself: Color.RED == 1 is False, and so is a same-valued member of another enum.

open as a page

Why does an Enum member defined with a duplicate value never appear in iteration?

level: juniorimportance: must knowfreq 45%
basics
~20 s

A duplicate value creates an alias, not a new member: enum.Enum binds the second name to the member that already owns that value. Iterating the class yields only canonical members, so the alias name never shows up.

open as a page

When should you use enum.IntEnum or enum.StrEnum instead of a plain enum.Enum?

level: middleimportance: must knowfreq 50%
basics
~20 s

Only when a member must still work as a raw int or string somewhere you cannot change - a wire format, a stored column, a C constant. Those members really are int and str subclasses: a compatibility shim, not a default.

open as a page

What values does enum.auto() assign inside an enum.Flag subclass?

level: middleimportance: should knowfreq 35%
basics
~20 s

Inside a Flag or IntFlag subclass, auto() assigns successive powers of two — 1, 2, 4, 8 — rather than the 1, 2, 3 it gives a plain Enum, so every member owns a distinct bit and members can be combined.

open as a page

How does a class control what `copy.copy()` and `copy.deepcopy()` return?

level: juniorimportance: must knowfreq 40%
basics
~10 s

Define copy(self) for copy.copy() and deepcopy(self, memo) for copy.deepcopy(). Each returns the finished new object, so the class itself decides which attributes are duplicated, which are shared, and which are rebuilt.

open as a page

What must a class's `__reduce__` return, and how does `pickle` use it to rebuild the object?

level: middleimportance: must knowfreq 40%
basics
~20 s

reduce returns either a string naming a module-level global to look up, or a tuple whose first item is a callable and whose second is a tuple of arguments. Loading imports that callable by name and calls it with those arguments.

open as a page

How do __getstate__ and __setstate__ let you pickle an object holding a threading.Lock?

level: middleimportance: must knowfreq 55%
basics
~10 s

getstate returns a copy of the instance dict with the lock key removed, so it is never serialised. setstate merges the stored data back and creates a fresh threading.Lock on the restored object.

open as a page

Why does `pickle.dumps` reject a lambda but accept a module-level function?

level: juniorimportance: should knowfreq 45%
basics
~20 s

pickle does not serialise a function's code. It stores the function's module name and qualified name and looks that pair up again on load. A lambda's qualified name is <lambda>, which resolves to nothing, so dumping raises pickle.PicklingError.

open as a page

What do __getstate__ and __setstate__ do when Python pickles and unpickles an object?

level: juniorimportance: should knowfreq 40%
basics
~10 s

getstate returns the data written into the pickle, which by default is the instance dict. setstate receives that data on unpickling and rebuilds the object from it. Unpickling never calls init.

open as a page