Fundamentals of Python OOP
Welcome to this comprehensive module on Python OOP. Whether you are transitioning from procedural programming or starting fresh, this course will guide you through the core concepts,…

In Python, what is the primary purpose of the __init__() method inside a class?
Given the class definition below, what will be printed by the code snippet? class Car: wheels = 4 def __init__(self, color, style): self.color = color self.style = style c1 = Car('red', 'sedan') print(c1.wheels)
Which of the following code fragments correctly defines a method that can change the color attribute of a Car object?
What is the effect of modifying an instance attribute on one object while another object of the same class exists?
Consider the following class: class Rectangle: def __init__(self, length, width): self.length = length self.width = width def area(self): return self.length * self.width If rect = Rectangle(5, 3) is created, what does rect.area() return?
Which of the following statements about class attributes is FALSE?
What is the role of the 'self' parameter in a Python method definition?
If a class defines both a class attribute 'wheels = 4' and an instance attribute 'self.wheels = 6' in __init__, what will be printed by the following code? c = Car() print(c.wheels)
Which of the following best describes the relationship between a class and its objects in Python?
Fundamentals of Python Object‑Oriented Programming (OOP)
Welcome to this comprehensive module on Python OOP. Whether you are transitioning from procedural programming or starting fresh, this course will guide you through the core concepts, terminology, and practical patterns that make Python’s object‑oriented approach powerful and intuitive.
Why Object‑Oriented Programming Differs from Procedural Programming
Procedural programming organizes code around functions that manipulate data stored in variables. In contrast, object‑oriented programming (OOP) groups related data and the functions that operate on that data into objects. This encapsulation promotes reuse, readability, and easier maintenance.
- Procedural style: Functions are independent; data is passed as arguments.
- OOP style: Data (attributes) and behavior (methods) live together inside a class.
Think of a Car object: it has a color attribute and a drive() method. The method automatically knows which car it belongs to because of the self reference.
Understanding the __init__ Method
The __init__ method is Python’s constructor. It runs immediately after an object is created and is responsible for initializing instance attributes. This is analogous to handing each new student a personalized welcome kit.
class Car:
def __init__(self, color, style):
self.color = color # instance attribute
self.style = style # instance attribute
Key points about __init__:
- It receives the newly created object as the first parameter (
self). - It does not delete objects—deletion is handled by
__del__. - Class‑level attributes are defined outside
__init__, not inside it.
Class Attributes vs. Instance Attributes
Python distinguishes between two kinds of attributes:
- Class attributes are defined directly in the class body and are shared by every instance.
- Instance attributes are created inside
__init__(or other methods) and belong to a single object.
Example:
class Car:
wheels = 4 # class attribute – shared
def __init__(self, color):
self.color = color # instance attribute – unique per object
Accessing c1.wheels prints 4 because wheels is a class attribute. Changing c1.wheels = 6 creates a new instance attribute that shadows the class attribute for c1 only; other objects remain unchanged.
Defining Methods that Modify Object State
To change an attribute of a specific object, the method must accept self and assign to the attribute using the self. prefix.
class Car:
def __init__(self, color):
self.color = color
def change_color(self, new_color):
self.color = new_color # correctly updates the instance attribute
Incorrect patterns include returning a value without assigning it, omitting self, or trying to modify the class directly from an instance method.
Effect of Modifying an Instance Attribute
When you alter an instance attribute on one object, only that object reflects the change. Other instances retain their original values because each instance maintains its own attribute dictionary.
c1 = Car('red')
c2 = Car('blue')
c1.change_color('green')
print(c1.color) # green
print(c2.color) # blue – unchanged
This isolation is a cornerstone of encapsulation, preventing unintended side effects across objects.
Practical Example: Calculating the Area of a Rectangle
Consider a simple class that models a rectangle:
class Rectangle:
def __init__(self, length, width):
self.length = length
self.width = width
def area(self):
return self.length * self.width
rect = Rectangle(5, 3)
print(rect.area()) # Outputs 15
The area method accesses the instance attributes length and width and returns their product. Because the method includes a return statement, the caller receives the calculated value.
Common Misconceptions About Class Attributes
While class attributes are shared, they are not immutable. The following statement is FALSE:
"Class attributes must be accessed using the class name, not via an instance."
In reality, you can read a class attribute through an instance (e.g., c1.wheels), but assigning to it via the instance creates a new instance attribute that masks the original class attribute.
The Role of the self Parameter
The self parameter is a reference to the specific object on which a method is invoked. It allows the method to access or modify the object's attributes and call other methods.
def drive(self, distance):
self.mileage += distance
Without self, the method would have no context for which object's mileage to update, leading to errors.
Key Takeaways
- OOP groups data and behavior into objects, unlike procedural code which separates them.
__init__initializes instance attributes; it is not responsible for deletion or class‑level data.- Class attributes are shared; instance attributes are unique per object.
- Methods that modify state must use
selfto reference the correct instance. - Changing an attribute on one instance does not affect other instances.
- Accessing class attributes via an instance is allowed, but assigning through an instance creates a shadowing instance attribute.
Frequently Asked Questions (FAQ)
Can I change a class attribute for all existing objects?
Yes. Modifying the attribute directly on the class (e.g., Car.wheels = 6) updates the value for every instance that does not have an overriding instance attribute.
Do I always need to write self in method definitions?
For instance methods, self is mandatory because it provides the link to the object’s state. Static methods, marked with @staticmethod, omit self because they operate independently of any instance.
What happens if I forget to call super().__init__() in a subclass?
Skipping the call means the parent class’s initialization code will not run, potentially leaving essential attributes uninitialized. Always invoke super() when extending a class that defines its own __init__.
Next Steps
Now that you have mastered the fundamentals, practice by building small projects:
- Create a
BankAccountclass with deposit and withdraw methods. - Implement inheritance by defining a
SavingsAccountthat adds interest calculation. - Experiment with class vs. instance attributes to see how changes propagate.
These exercises will reinforce the concepts covered and prepare you for more advanced topics such as polymorphism, abstract base classes, and design patterns.
