Python OOP Advanced Concepts: Class Variables and Inheritance for Real-World Programming

Master advanced Python OOP with class variables and inheritance. Learn to create parent-child class relationships, use super(), override methods, and build scalable object-oriented applications with practical examples.

16 min read

Building on the fundamentals of Python classes and objects, it's time to explore more advanced OOP concepts that make your code more efficient, organized, and scalable. In this tutorial, we'll dive into class variables and inheritance - two powerful features that distinguish object-oriented programming from simple function-based programming.

๐Ÿ’ก

๐ŸŽฏ What You'll Learn: In this advanced OOP tutorial, you'll master:

  • The difference between class variables and instance variables
  • How to create and use shared class attributes
  • Understanding inheritance and parent-child class relationships
  • Creating subclasses that extend parent functionality
  • Using the super() function to call parent methods
  • Method overriding for customizing inherited behavior
  • Real-world inheritance examples with electric cars
  • Best practices for designing class hierarchies

๐Ÿ”„ Continuing Our OOP Journey

In our previous tutorial, we created basic Car classes with instance variables and methods. Now we'll extend this knowledge with more sophisticated OOP concepts that professional developers use every day.

Prerequisites

Before we begin, make sure you have:

  • Understanding of Python classes, objects, and methods from Part 1
  • Python 3 installed and working on your system
  • Completed the basic Car class tutorial
  • A text editor for creating Python files

๐Ÿ—๏ธ Understanding Class Variables vs Instance Variables

Let's start by exploring the difference between class variables (shared by all objects) and instance variables (unique to each object).

Creating Our Second OOP File

touch oop2.py

Let's examine what happens when we add class variables to our Car class:

nano oop2.py

Let's create a Car class with a class variable:

class Car:
    wheels = 4  # This is a class variable - shared by all cars

    def __init__(self, brand, model):
        self.brand = brand  # These are instance variables
        self.model = model  # Unique to each car object

    def drive(self):
        print(f"The {self.brand} {self.model} is now driving.")

Let's view our file:

cat oop2.py

Output:

class Car:
    wheels = 4

    def __init__(self, brand, model):
        self.brand = brand
        self.model = model

    def drive(self):
        print(f"The {self.brand} {self.model} is now driving.")

Testing Class Variables

Let's add code to test how class variables work:

nano oop2.py

Our file now includes a test:

class Car:
    wheels = 4  # Class variable

    def __init__(self, brand, model):
        self.brand = brand
        self.model = model

    def drive(self):
        print(f"The {self.brand} {self.model} is now driving.")

# Test class variable access
print(f"Number of wheels: {Car.wheels}")

Let's run this to see class variables in action:

python oop2.py

Output:

Number of wheels: 4
โœ…

โœ… Class Variables: Notice that we accessed Car.wheels directly from the class, without creating any objects! Class variables belong to the class itself, not to individual objects.

๐Ÿ“Š Class Variables vs Instance Variables Comparison

AspectClass VariablesInstance Variables
DefinitionDefined in class body, outside methodsDefined inside init with self
SharingShared by all objects of the classUnique to each object instance
AccessClassName.variable or object.variableobject.variable only
MemoryOne copy for entire classSeparate copy for each object
Use CaseConstants, counters, shared propertiesObject-specific data

๐Ÿš— Building the Foundation for Inheritance

Before diving into inheritance, let's create a robust parent class. We'll start with our third file:

ls

Output:

oop1.py  oop2.py
touch oop3.py

Let's build a comprehensive Car class that will serve as our parent class:

nano oop3.py

We'll start with a basic parent class:

class Car:
    def __init__(self, make, model, year):
        self.make = make
        self.model = model
        self.year = year

    def describe_car(self):
        print(f"{self.year} {self.make} {self.model}")

Let's view our initial parent class:

cat oop3.py

Output:

class Car:
    def __init__(self, make, model, year):
        self.make = make
        self.model = model
        self.year = year

    def describe_car(self):
        print(f"{self.year} {self.make} {self.model}")

๐Ÿ”„ Creating Your First Inheritance Relationship

Now comes the exciting part - creating a child class that inherits from the parent class. Let's add an ElectricCar class:

nano oop3.py

Our file now includes inheritance:

class Car:
    def __init__(self, make, model, year):
        self.make = make
        self.model = model
        self.year = year

    def describe_car(self):
        print(f"{self.year} {self.make} {self.model}")

class ElectricCar(Car):  # ElectricCar inherits from Car
    def __init__(self, make, model, year, battery_size=75):
        super().__init__(make, model, year)  # Call parent constructor
        self.battery_size = battery_size  # Add electric-specific attribute

    def describe_battery(self):
        print(f"This car has a {self.battery_size}-kWh battery.")

Let's view our inheritance structure:

cat oop3.py

Output:

class Car:
    def __init__(self, make, model, year):
        self.make = make
        self.model = model
        self.year = year

    def describe_car(self):
        print(f"{self.year} {self.make} {self.model}")

class ElectricCar(Car):
    def __init__(self, make, model, year, battery_size=75):
        super().__init__(make, model, year)
        self.battery_size = battery_size

    def describe_battery(self):
        print(f"This car has a {self.battery_size}-kWh battery.")

๐Ÿ” Understanding Inheritance Components

Let's break down the inheritance syntax:

ComponentSyntaxPurpose
Parent Classclass Car:Base class that provides common functionality
Child Classclass ElectricCar(Car):Inherits all methods and attributes from Car
super()super().init(make, model, year)Calls the parent class constructor
Additional Attributesself.battery_size = battery_sizeChild-specific attributes not in parent
โœ…

โœ… The super() Function: This special function allows child classes to call methods from their parent class. It's essential for properly initializing inherited objects.

๐ŸŽฏ Method Overriding: Customizing Inherited Behavior

One of the most powerful features of inheritance is the ability to override parent methods to provide specialized behavior. Let's add method overriding:

nano oop3.py

Our enhanced file now includes method overriding:

class Car:
    def __init__(self, make, model, year):
        self.make = make
        self.model = model
        self.year = year

    def describe_car(self):
        print(f"{self.year} {self.make} {self.model}")

class ElectricCar(Car):
    def __init__(self, make, model, year, battery_size=75):
        super().__init__(make, model, year)
        self.battery_size = battery_size

    def describe_battery(self):
        print(f"This car has a {self.battery_size}-kWh battery.")

    def describe_car(self):  # Override parent method
        super().describe_car()  # Call parent method first
        self.describe_battery()  # Add electric-specific info

Let's view the complete inheritance example:

cat oop3.py

Output:

class Car:
    def __init__(self, make, model, year):
        self.make = make
        self.model = model
        self.year = year

    def describe_car(self):
        print(f"{self.year} {self.make} {self.model}")

class ElectricCar(Car):
    def __init__(self, make, model, year, battery_size=75):
        super().__init__(make, model, year)
        self.battery_size = battery_size

    def describe_battery(self):
        print(f"This car has a {self.battery_size}-kWh battery.")

    def describe_car(self):
        super().describe_car()
        self.describe_battery()

๐Ÿงช Testing Our Inheritance Implementation

Let's add object creation and method calls to test our inheritance:

nano oop3.py

Our complete file now includes testing:

class Car:
    def __init__(self, make, model, year):
        self.make = make
        self.model = model
        self.year = year

    def describe_car(self):
        print(f"{self.year} {self.make} {self.model}")

class ElectricCar(Car):
    def __init__(self, make, model, year, battery_size=75):
        super().__init__(make, model, year)
        self.battery_size = battery_size

    def describe_battery(self):
        print(f"This car has a {self.battery_size}-kWh battery.")

    def describe_car(self):
        super().describe_car()
        self.describe_battery()

my_tesla = ElectricCar('Tesla', 'Model S', 2022)
my_tesla.describe_car()

Let's view our complete implementation:

cat oop3.py

Output:

class Car:
    def __init__(self, make, model, year):
        self.make = make
        self.model = model
        self.year = year

    def describe_car(self):
        print(f"{self.year} {self.make} {self.model}")

class ElectricCar(Car):
    def __init__(self, make, model, year, battery_size=75):
        super().__init__(make, model, year)
        self.battery_size = battery_size

    def describe_battery(self):
        print(f"This car has a {self.battery_size}-kWh battery.")

    def describe_car(self):
        super().describe_car()
        self.describe_battery()

my_tesla = ElectricCar('Tesla', 'Model S', 2022)
my_tesla.describe_car()

Now let's test our inheritance:

python oop3.py

Output:

2022 Tesla Model S
This car has a 75-kWh battery.
โœ…

๐ŸŽ‰ Inheritance Working! The ElectricCar object successfully called both the inherited describe_car() method from Car and its own describe_battery() method.

๐Ÿ”‹ Testing with Custom Battery Size

Let's test with a custom battery size to see how parameters work in inheritance:

nano oop3.py

Let's modify the test to use a custom battery size:

class Car:
    def __init__(self, make, model, year):
        self.make = make
        self.model = model
        self.year = year

    def describe_car(self):
        print(f"{self.year} {self.make} {self.model}")

class ElectricCar(Car):
    def __init__(self, make, model, year, battery_size=75):
        super().__init__(make, model, year)
        self.battery_size = battery_size

    def describe_battery(self):
        print(f"This car has a {self.battery_size}-kWh battery.")

    def describe_car(self):
        super().describe_car()
        self.describe_battery()

my_tesla = ElectricCar('Tesla', 'Model S', 2022, 100)  # Custom battery size
my_tesla.describe_car()

Let's view the updated test:

cat oop3.py

Output:

class Car:
    def __init__(self, make, model, year):
        self.make = make
        self.model = model
        self.year = year

    def describe_car(self):
        print(f"{self.year} {self.make} {self.model}")

class ElectricCar(Car):
    def __init__(self, make, model, year, battery_size=75):
        super().__init__(make, model, year)
        self.battery_size = battery_size

    def describe_battery(self):
        print(f"This car has a {self.battery_size}-kWh battery.")

    def describe_car(self):
        super().describe_car()
        self.describe_battery()

my_tesla = ElectricCar('Tesla', 'Model S', 2022, 100)
my_tesla.describe_car()

Let's run the test with custom battery:

python oop3.py

Output:

2022 Tesla Model S
This car has a 100-kWh battery.

๐Ÿ”„ Understanding the Inheritance Flow

Let's trace through what happens when we create and use an ElectricCar object:

StepCodeWhat Happens
1my_tesla = ElectricCar('Tesla', 'Model S', 2022, 100)Python creates ElectricCar object
2ElectricCar.init calledChild constructor executes
3super().init(make, model, year)Parent constructor called
4self.battery_size = 100Child-specific attribute set
5my_tesla.describe_car()Overridden method called
6super().describe_car()Parent method executes
7self.describe_battery()Child-specific method executes

๐ŸŒŸ Real-World Inheritance Example

Let's create a more comprehensive example that shows the power of inheritance:

class Vehicle:
    """Base class for all vehicles"""

    total_vehicles = 0  # Class variable to count all vehicles

    def __init__(self, make, model, year, color="White"):
        self.make = make
        self.model = model
        self.year = year
        self.color = color
        self.is_running = False
        Vehicle.total_vehicles += 1  # Increment class variable

    def start_engine(self):
        if not self.is_running:
            self.is_running = True
            print(f"The {self.make} {self.model} engine has started.")
        else:
            print(f"The {self.make} {self.model} is already running.")

    def stop_engine(self):
        if self.is_running:
            self.is_running = False
            print(f"The {self.make} {self.model} engine has stopped.")
        else:
            print(f"The {self.make} {self.model} is already off.")

    def describe_vehicle(self):
        status = "running" if self.is_running else "parked"
        print(f"{self.year} {self.color} {self.make} {self.model} - Currently {status}")

    @classmethod
    def get_total_vehicles(cls):
        return cls.total_vehicles

class Car(Vehicle):
    """Standard gasoline car"""

    def __init__(self, make, model, year, color="White", fuel_capacity=50):
        super().__init__(make, model, year, color)
        self.fuel_capacity = fuel_capacity
        self.fuel_level = fuel_capacity  # Start with full tank

    def refuel(self):
        self.fuel_level = self.fuel_capacity
        print(f"The {self.make} {self.model} has been refueled to {self.fuel_capacity} liters.")

    def describe_vehicle(self):
        super().describe_vehicle()
        print(f"Fuel: {self.fuel_level}/{self.fuel_capacity} liters")

class ElectricCar(Vehicle):
    """Electric vehicle with battery"""

    def __init__(self, make, model, year, color="White", battery_capacity=75):
        super().__init__(make, model, year, color)
        self.battery_capacity = battery_capacity
        self.battery_level = battery_capacity  # Start fully charged
        self.charging = False

    def charge_battery(self):
        if not self.charging:
            self.charging = True
            print(f"Started charging the {self.make} {self.model}...")
            self.battery_level = self.battery_capacity
            self.charging = False
            print(f"Charging complete! Battery: {self.battery_capacity} kWh")
        else:
            print(f"The {self.make} {self.model} is already charging.")

    def describe_vehicle(self):
        super().describe_vehicle()
        print(f"Battery: {self.battery_level}/{self.battery_capacity} kWh")
        if self.charging:
            print("Currently charging...")

class Motorcycle(Vehicle):
    """Two-wheeled motorcycle"""

    def __init__(self, make, model, year, color="Black", engine_size=600):
        super().__init__(make, model, year, color)
        self.engine_size = engine_size
        self.has_sidecar = False

    def add_sidecar(self):
        if not self.has_sidecar:
            self.has_sidecar = True
            print(f"Sidecar added to the {self.make} {self.model}.")
        else:
            print(f"The {self.make} {self.model} already has a sidecar.")

    def describe_vehicle(self):
        super().describe_vehicle()
        print(f"Engine: {self.engine_size}cc")
        if self.has_sidecar:
            print("Equipped with sidecar")

# Test the vehicle hierarchy
print("=== Creating Vehicles ===")
regular_car = Car("Toyota", "Camry", 2023, "Blue", 60)
electric_car = ElectricCar("Tesla", "Model 3", 2023, "Red", 80)
motorcycle = Motorcycle("Harley-Davidson", "Sportster", 2023, "Black", 883)

print(f"\nTotal vehicles created: {Vehicle.get_total_vehicles()}")

print("\n=== Testing Vehicle Operations ===")
for vehicle in [regular_car, electric_car, motorcycle]:
    print(f"\n--- {vehicle.make} {vehicle.model} ---")
    vehicle.describe_vehicle()
    vehicle.start_engine()
    vehicle.describe_vehicle()

    # Test specific methods
    if isinstance(vehicle, Car) and not isinstance(vehicle, ElectricCar):
        vehicle.refuel()
    elif isinstance(vehicle, ElectricCar):
        vehicle.charge_battery()
    elif isinstance(vehicle, Motorcycle):
        vehicle.add_sidecar()

    vehicle.stop_engine()

๐ŸŽฏ Best Practices for Inheritance

PracticeWhy It MattersExample
Use super() consistentlyEnsures proper initialization chainsuper().init(args)
Follow IS-A relationshipElectricCar IS-A Car makes logical senseCar โ†’ ElectricCar, Vehicle โ†’ Car
Keep inheritance shallowDeep hierarchies become hard to maintainMax 3-4 levels deep
Override thoughtfullyMaintain expected behaviorCall super() then add functionality

โš ๏ธ Common Inheritance Pitfalls

1. Forgetting to Call super() in Constructor

class ElectricCar(Car):
    def __init__(self, make, model, year, battery_size):
        # Missing super().__init__() - parent attributes won't be set!
        self.battery_size = battery_size
class ElectricCar(Car):
    def __init__(self, make, model, year, battery_size):
        super().__init__(make, model, year)  # Initialize parent first
        self.battery_size = battery_size

2. Incorrect Method Overriding

class ElectricCar(Car):
    def describe_car(self):
        print(f"Battery: {self.battery_size} kWh")  # Lost car info!
class ElectricCar(Car):
    def describe_car(self):
        super().describe_car()  # Keep parent functionality
        print(f"Battery: {self.battery_size} kWh")  # Add new info

3. Breaking the IS-A Relationship

class Car(Wheel):  # This doesn't make logical sense
    pass
class Car:
    def __init__(self):
        self.wheels = [Wheel(), Wheel(), Wheel(), Wheel()]

๐ŸŽฏ Key Takeaways

โœ… Remember These Points

  1. Class Variables: Shared by all instances, defined at class level
  2. Inheritance: Child classes inherit all attributes and methods from parents
  3. super(): Essential for calling parent class methods properly
  4. Method Overriding: Customize inherited behavior while maintaining functionality
  5. IS-A Relationship: Inheritance should represent a logical "is a" relationship

๐Ÿงช Practice Challenge

Try creating your own inheritance hierarchy:

  1. Create a Shape base class with area() and perimeter() methods
  2. Create Rectangle and Circle child classes that inherit from Shape
  3. Override the area and perimeter methods in each child class
  4. Add specific methods like is_square() to Rectangle
  5. Test creating objects and calling both inherited and overridden methods

๐Ÿš€ What's Next?

Continue your Python OOP journey with these advanced topics:

  • Multiple Inheritance: Inheriting from multiple parent classes
  • Abstract Base Classes: Creating interfaces and enforcing method implementation
  • Property Decorators: Creating getter/setter methods for controlled attribute access
  • Class Methods and Static Methods: Alternative method types for different use cases
โœ…

๐ŸŽ‰ Congratulations! You've mastered Python inheritance and class variables. You can now create sophisticated object hierarchies that share common functionality while adding specialized features. These concepts are fundamental to building large, maintainable applications.

๐Ÿ“– Summary

In this comprehensive tutorial, you learned:

  • Class Variables: How to create shared attributes across all instances
  • Inheritance Fundamentals: Creating parent-child class relationships
  • Constructor Chaining: Using super() to properly initialize inherited objects
  • Method Overriding: Customizing inherited methods for specialized behavior
  • Real-World Applications: Building vehicle hierarchies with multiple inheritance levels
  • Best Practices: Following IS-A relationships and proper inheritance design
  • Common Pitfalls: Avoiding mistakes that break inheritance functionality

Ready to become a Python OOP expert? Explore our complete Python Programming Series for more advanced tutorials including multiple inheritance, abstract classes, and design patterns.

Owais

Written by Owais

I'm an AIOps Engineer with a passion for AI, Operating Systems, Cloud, and Securityโ€”sharing insights that matter in today's tech world.

I completed the UK's Eduqual Level 6 Diploma in AIOps from Al Nafi International College, a globally recognized program that's changing careers worldwide. This diploma is:

  • โœ… Available online in 17+ languages
  • โœ… Includes free student visa guidance for Master's programs in Computer Science fields across the UK, USA, Canada, and more
  • โœ… Comes with job placement support and a 90-day success plan once you land a role
  • โœ… Offers a 1-year internship experience letter while you studyโ€”all with no hidden costs

It's not just a diplomaโ€”it's a career accelerator.

๐Ÿ‘‰ Start your journey today with a 7-day free trial

Related Articles

Continue exploring with these handpicked articles that complement what you just read

More Reading

One more article you might find interesting