This lesson covers five focused Python interview topics: advanced dataclass features (frozen, order, kw_only, post_init, field defaults), how dataclasses compare with attrs and validation libraries like pydantic, the distinction between abstract classes and interfaces in Python, how polymorphism is achieved, and what mixins are. Each question includes beginner-friendly explanations, real-world analogies, runnable code examples, and expected terminal output so you can try them in your editor.
Dataclasses provide a concise way to declare classes primarily used to store data. Beyond the basics, they offer features that change mutability, comparison behavior, argument style, and lifecycle hooks. Think of a dataclass as a ready-made filing box: the options let you choose whether the lid locks (frozen), how you compare boxes (order), whether you must name fields when putting things in the box (kw_only), and a short checklist you run after packing (post_init). `field()` customizes defaults and factory functions.
__init__, useful for validation or derived attributes.Example showing each feature and expected output:
from dataclasses import dataclass, field
@dataclass(order=True, frozen=True)
class Point:
x: int
y: int = field(default=0)
label: str = field(default="origin", compare=False)
def __post_init__(self):
# Even though frozen=True, __post_init__ can set attributes using object.__setattr__
if not isinstance(self.x, int) or not isinstance(self.y, int):
raise TypeError("x and y must be integers")
# Normalize label to lowercase
object.__setattr__(self, "label", self.label.lower())
p1 = Point(1, 2, label="A")
p2 = Point(2, 3)
print("p1:", p1)
print("p2:", p2)
print("p1 < p2:", p1 < p2)
# Attempt to mutate (will raise)
try:
p1.x = 10
except Exception as e:
print("Mutation error:", type(e).__name__, str(e))Expected output when run:
# Output:
p1: Point(x=1, y=2, label='a')
p2: Point(x=2, y=3, label='origin')
p1 < p2: True
Mutation error: FrozenInstanceError cannot assign to field 'x' Notes and tips:
default_factory in field() for mutable defaults (e.g., lists).compare=False excludes a field from generated comparison methods.frozen, use object.__setattr__ inside __post_init__ for controlled initialization logic.Dataclasses, attrs, and pydantic overlap but serve slightly different needs. Imagine three carpentry tools: dataclasses are a solid hammer (built-in, simple), attrs is a power-tool set with plugins (more features, flexible), and pydantic is a specialized tool that validates and transforms inputs automatically (great for external data). Choose based on simplicity, flexibility, and validation needs.
Example illustrating the difference with simple validation of input that may be a string:
# dataclass: manual validation
from dataclasses import dataclass
@dataclass
class Manual:
age: int
def __post_init__(self):
if not isinstance(self.age, int):
raise TypeError("age must be int")
# pydantic: automatic coercion and validation (conceptual snippet)
# from pydantic import BaseModel
# class Person(BaseModel):
# age: int
#
# Person(age="30") # pydantic converts "30" -> 30 automatically and validates
# attrs: built-in validators and converters (conceptual snippet)
# import attr
#
# @attr.s
# class User:
# age = attr.ib(converter=int, validator=attr.validators.instance_of(int))
Expected dataclass run (for the manual example):
# Output:
# Creating Manual(age=30) succeeds if age is int; Manual(age="30") raises TypeError in **post_init** Guidance:
Python does not have a separate language-level "interface" keyword like Java, but the concept exists. Use the Abstract Base Classes (ABCs) from the abc module to define abstract classes and interface-like contracts. Think of an abstract class as a blueprint house that may include some built-in fixtures (concrete methods), while an interface is a contract listing only required methods. In Python, ABCs can act as either.
Example showing ABC acting like an interface:
from abc import ABC, abstractmethod
class Serializer(ABC):
@abstractmethod
def serialize(self, obj) -> str:
pass
@abstractmethod
def deserialize(self, data: str):
pass
class JsonSerializer(Serializer):
def serialize(self, obj) -> str:
import json
return json.dumps(obj)
def deserialize(self, data: str):
import json
return json.loads(data)
js = JsonSerializer()
print(js.serialize({"a": 1}))
print(js.deserialize('{"a":1}')) Expected output:
# Output:
{"a": 1}
{'a': 1} Polymorphism means "many forms": the same operation works on different types. Python achieves polymorphism primarily through duck typing and inheritance. In practice you write code that expects a behavior (methods/attributes), not a specific class. A real-world analogy: multiple payment methods (cash, card, mobile) - you call pay() and each method handles it.
Example demonstrating polymorphism via duck typing and inheritance:
class Dog:
def speak(self):
return "woof"
class Cat:
def speak(self):
return "meow"
def animal_sound(a):
# We only require that 'a' has a .speak() method
print(a.speak())
animal_sound(Dog())
animal_sound(Cat())
# Inheritance example
class Animal:
def speak(self):
return "..."
class Cow(Animal):
def speak(self):
return "moo"
print(Cow().speak()) Expected output:
# Output:
woof
meow
moo Mixins are small classes intended to provide a focused piece of behavior that you can "mix in" with other classes via multiple inheritance. Think of mixins as accessory tools you can add to a basic device (like adding a camera or GPS to a smartphone). Mixins usually do not represent a standalone entity and are not meant to be instantiated on their own.
Example: a LoggingMixin that adds a simple log() method to several classes:
class LoggingMixin:
def log(self, message):
print(f"[{self.__class__.__name__}] {message}")
class Service(LoggingMixin):
def run(self):
self.log("service started")
class Worker(LoggingMixin):
def work(self):
self.log("working...")
s = Service()
s.run()
w = Worker()
w.work()Expected output:
# Output:
[Service] service started
[Worker] working... Final notes: In interviews, be explicit about trade-offs: dataclasses are convenient but limited for rich validation (use attrs or pydantic when needed); ABCs provide formal contracts but Python's duck typing is often enough; polymorphism is natural and idiomatic in Python; and mixins are a lightweight way to share behavior - use them carefully to avoid complex MRO issues. Practice writing small examples like the ones above and walk an interviewer through the expected outputs.
3 questions · no sign-up, nothing stored
Progress is stored in this browser only - no sign-up, nothing sent anywhere.