Python in Python is best learned by connecting the rule to an automation script. Start with the smallest function or script, observe the output, and then add one realistic constraint so the concept becomes practical.
The key habit for this lesson is to watch input value and returned object as it changes. That makes the topic easier to debug, easier to explain in interviews, and easier to use in real code without memorizing isolated syntax.
Python is an object-oriented language. A class is a blueprint for creating objects. An object is an instance of a class with its own data (attributes) and behavior (methods).
class Dog:
# Class attribute - shared by all instances
species = "Canis familiaris"
# __init__ is the constructor
def __init__(self, name: str, age: int):
# Instance attributes - unique to each object
self.name = name
self.age = age
# Instance method
def bark(self) -> str:
return f"{self.name} says: Woof!"
def describe(self) -> str:
return f"{self.name} is {self.age} years old"
# Create objects (instances)
dog1 = Dog("Buddy", 3)
dog2 = Dog("Max", 5)
print(dog1.bark()) # Buddy says: Woof!
print(dog2.describe()) # Max is 5 years old
print(dog1.species) # Canis familiaris
print(Dog.species) # Canis familiaris (via class)
Methods with double underscores like __init__, __str__, __repr__ are called dunder (magic) methods. They define how objects behave with built-in operations.
class Point:
def __init__(self, x: float, y: float):
self.x = x
self.y = y
def __str__(self) -> str:
"""Called by print() and str()"""
return f"Point({self.x}, {self.y})"
def __repr__(self) -> str:
"""Called in REPL and for debugging"""
return f"Point(x={self.x}, y={self.y})"
def __add__(self, other: "Point") -> "Point":
"""Called by + operator"""
return Point(self.x + other.x, self.y + other.y)
def __eq__(self, other: "Point") -> bool:
"""Called by == operator"""
return self.x == other.x and self.y == other.y
def __len__(self) -> int:
"""Called by len()"""
import math
return int(math.sqrt(self.x**2 + self.y**2))
p1 = Point(1, 2)
p2 = Point(3, 4)
print(p1) # Point(1, 2)
print(p1 + p2) # Point(4, 6)
print(p1 == p2) # False
print(p1 == Point(1, 2)) # True
A child class inherits attributes and methods from a parent class. Use super() to call the parent's methods.
class Animal:
def __init__(self, name: str):
self.name = name
def speak(self) -> str:
return f"{self.name} makes a sound"
class Dog(Animal):
def speak(self) -> str:
return f"{self.name} says: Woof!"
class Cat(Animal):
def speak(self) -> str:
return f"{self.name} says: Meow!"
class GuideDog(Dog):
def __init__(self, name: str, owner: str):
super().__init__(name) # call parent __init__
self.owner = owner
def describe(self) -> str:
return f"{self.name} guides {self.owner}"
animals = [Dog("Buddy"), Cat("Whiskers"), GuideDog("Rex", "Alice")]
for animal in animals:
print(animal.speak())
# isinstance and issubclass
print(isinstance(animals[0], Dog)) # True
print(isinstance(animals[0], Animal)) # True
print(issubclass(Dog, Animal)) # True
class Circle:
PI = 3.14159
def __init__(self, radius: float):
self._radius = radius # _ prefix = "private by convention"
@property
def radius(self) -> float:
"""Getter"""
return self._radius
@radius.setter
def radius(self, value: float):
"""Setter with validation"""
if value < 0:
raise ValueError("Radius cannot be negative")
self._radius = value
@property
def area(self) -> float:
return Circle.PI * self._radius ** 2
@classmethod
def from_diameter(cls, diameter: float) -> "Circle":
"""Alternative constructor"""
return cls(diameter / 2)
@staticmethod
def is_valid_radius(r: float) -> bool:
"""Utility - doesn't need self or cls"""
return r >= 0
c = Circle(5)
print(c.area) # 78.53975
c.radius = 10 # uses setter
print(c.radius) # 10
c2 = Circle.from_diameter(20)
print(c2.radius) # 10.0
print(Circle.is_valid_radius(-1)) # False
The @dataclass decorator auto-generates __init__, __repr__, and __eq__ for you.
from dataclasses import dataclass, field
@dataclass
class Student:
name: str
age: int
grades: list[int] = field(default_factory=list)
def average(self) -> float:
return sum(self.grades) / len(self.grades) if self.grades else 0.0
s = Student("Alice", 20, [90, 85, 92])
print(s) # Student(name='Alice', age=20, grades=[90, 85, 92])
print(s.average()) # 89.0
print(s == Student("Alice", 20, [90, 85, 92])) # True
# Frozen dataclass (immutable)
@dataclass(frozen=True)
class Point:
x: float
y: float
p = Point(1.0, 2.0)
# p.x = 5 # FrozenInstanceError!
Use Python when the program needs a clear answer to a specific problem, not because the keyword looks familiar. In a real Python task, first name the input, then name the transformation, then name the output. This small discipline shows whether the topic is being used correctly or only copied from an example.
A reliable practice flow is: create the smallest working function or script, add one normal case, add one edge case such as missing, repeated, empty, or boundary input, and then confirm the result with traceback and printed inspection. If the result surprises you, reduce the code until the behavior is visible again.
The most common trap here is copying the syntax before understanding the behavior. Avoid it by writing one sentence before the code that explains why Python is the right choice. After the code runs, verify the lesson by doing this: change one input and explain the changed output.
Copying the syntax before understanding the behavior.
Write the expected behavior first, then make the example prove it.
Practicing only the perfect input.
Also test missing, repeated, empty, or boundary input before considering the lesson complete.
Looking only at the final output.
Trace input value and returned object through each important step.
Use it when the problem matches the behavior shown in the example and when the result can be verified through traceback and printed inspection.
Start with a tiny case, then test missing, repeated, empty, or boundary input. The main warning sign is copying the syntax before understanding the behavior.
Trace input value and returned object, predict the result, run the example, and compare your prediction with the actual output.
Explore 500+ free tutorials across 20+ languages and frameworks.