Skip to content
Cover image for: Inheritance & Polymorphism in Python OOP
#pythonIntermediate

Inheritance & Polymorphism in Python OOP

May 6, 2026
Updated May 14, 2026
30 min read

AI Insights

Powered by GPT-4o-mini

Verified Context: inheritance-polymorphism-in-python-oop

Master inheritance and polymorphism in Python with practical examples. Learn single inheritance for code reuse, multiple inheritance and the diamond problem resolution, Method Resolution Order (MRO) using C3 linearization, the super() function for cooperative multiple inheritance, abstract base classes with the ABC module, duck typing for polymorphic behavior, and the composition vs inheritance tradeoff with a complete Customer and Address object modeling example.

Quick Summary

-Inheritance is a fundamental concept in object-oriented programming (OOP) that allows a class (called a child or derived class) to inherit attributes and methods from another class (called a parent or base class).

Class Relationships

  • Aggregation
  • Inheritance

Aggregation (Has-A relationship)

Python class inheritance and object composition with customer address management

Explanation

  • Defines a Customer class that accepts a name, gender, and address object as parameters during initialization
  • Creates an Address class to store location details including city, pin code, and state information
  • Demonstrates object composition by passing an Address instance as a parameter to the Customer constructor
  • Shows how to access nested object attributes through method calls to display complete address information
  • Illustrates the relationship between parent and child classes through encapsulated data structures
python

Output

text

Understanding Class Composition and Private Attributes in Python

Explanation

  • The Customer class is initialized with a name, gender, and an Address object, demonstrating composition.
  • The Address class contains private and public attributes for city, pin, and state, showcasing encapsulation.
  • The print_address method in the Customer class attempts to access the private attribute __city from the Address class, illustrating the concept of private access modifiers.
  • An instance of Address is created and passed to the Customer instance, highlighting how objects can be composed in Python.
  • The comment at the end emphasizes that private attributes cannot be accessed directly outside their class, reinforcing the importance of encapsulation.
python

Output

text

This code defines a Customer class that utilizes an Address class to manage and display customer addresses.

Explanation

  • The Customer class initializes with a name, gender, and an instance of the Address class.
  • The print_address method in the Customer class retrieves and prints the city, pin, and state from the associated Address instance.
  • The Address class encapsulates address details, including a private city attribute, which is accessed through the get_city method.
  • An instance of Address is created with specific values, and a Customer instance is initialized with this address.
  • Finally, the print_address method is called to display the customer's address information.
python

Output

text

This code defines a Customer class that manages customer profiles and their associated addresses.

Explanation

  • The Customer class initializes with a name, gender, and an Address object, allowing for structured customer data.
  • The print_address method retrieves and prints the city, pin, and state from the associated Address object using the get_city method.
  • The edit_profile method allows updating the customer's name and modifying the address details through the edit_address method of the Address class.
  • The Address class encapsulates address details, using private attributes for the city to restrict direct access, promoting data encapsulation.
  • An instance of Address is created and linked to a Customer, demonstrating how to create and manipulate customer profiles and their addresses.
python

Output

text

Aggregation class diagram

Inheritance

  • What is inheritance
  • Example
  • What gets inherited?
Inheritance and it's benefits

-Inheritance is a fundamental concept in object-oriented programming (OOP) that allows a class (called a child or derived class) to inherit attributes and methods from another class (called a parent or base class).

Demonstrating Inheritance and Method Access in Python Classes

Explanation

  • Defines a parent class User with an initializer that sets a name attribute and a login method.
  • Introduces a child class Student that inherits from User, allowing access to its attributes and methods.
  • The Student class has its own initializer that sets a rollno attribute and an enroll method.
  • Instances of both classes are created, showcasing how the Student instance can access the inherited name attribute and the login method from the User class.
  • The output demonstrates the functionality of both classes by printing the name, rollno, and invoking the login and enroll methods.
python

Output

text

Demonstrating inheritance and method access in Python classes

Explanation

  • The User class serves as a parent class with a constructor that initializes the name attribute and a login method.
  • The Student class inherits from the User class, allowing it to access the name attribute and login method.
  • An instance of User is created as u, while an instance of Student is created as s.
  • The code prints the name attribute from the Student instance, calls the inherited login method, and invokes the enroll method specific to the Student class.
  • This demonstrates the concept of inheritance, where the child class can utilize properties and methods of the parent class.
python

Output

text

Class Diagram (Triangle)

What gets inherited?
  • Constructor
  • Non Private Attributes
  • Non Private Methods

This code defines a Phone class and a SmartPhone subclass to demonstrate object-oriented programming in Python.

Explanation

  • The Phone class is initialized with three attributes: price, brand, and camera, which are set during object creation.
  • The __init__ method prints a message indicating the constructor is being executed.
  • The buy method in the Phone class allows for simulating the purchase of a phone by printing a message.
  • The SmartPhone class inherits from the Phone class, allowing it to use the same properties and methods without redefining them.
  • An instance of SmartPhone is created with specific attributes, and the buy method is called to demonstrate functionality.
python

Output

text

Understanding Constructor Behavior in Inheritance with Python Classes

Explanation

  • The Phone class defines a constructor that initializes three attributes: price, brand, and camera, while also printing a message when an instance is created.
  • The SmartPhone class inherits from Phone but defines its own constructor that initializes os and ram, effectively overriding the parent class's constructor.
  • When an instance of SmartPhone is created, the Phone constructor is not executed, demonstrating that the child class's constructor replaces the parent's.
  • The s.brand line accesses the brand attribute from the Phone class, but since the Phone constructor is not called, brand remains uninitialized unless explicitly set in the SmartPhone constructor.
  • This code snippet illustrates the concept of constructor overriding in Python's class inheritance model.
python

Output

text

Understanding Private Member Access in Inheritance with Python Classes

Explanation

  • The Phone class has a private attribute __price, which is not directly accessible from child classes.
  • The SmartPhone class inherits from Phone and attempts to access the private attribute using the name mangling technique (_Phone__price).
  • The show method in the Phone class serves as a getter to print the private __price attribute.
  • When an instance of SmartPhone is created, it can access the private member using the mangled name but cannot access it directly as self.__price.
  • This demonstrates how Python's name mangling protects private attributes from being accessed directly in subclasses.
python

Output

text

Accessing and displaying the brand attribute of an object in Python

Explanation

  • The code snippet uses the print() function to output the value of the brand attribute from an object s.
  • It assumes that s is an instance of a class that has a defined attribute called brand.
  • If the brand attribute exists, its value will be printed to the console; otherwise, an AttributeError will occur.
  • This is a common practice in object-oriented programming to retrieve and display object properties.
python

Output

text

This code snippet invokes a method to perform a check on an object or system.

Explanation

  • The check() method is called on the object s, which likely performs a validation or diagnostic operation.
  • The specific functionality of check() depends on the implementation of the class or module to which s belongs.
  • This method may return a boolean value, raise exceptions, or produce output indicating the status of the check.
  • It is commonly used in scenarios where preconditions need to be verified before proceeding with further operations.
python

Output

text

Understanding private member access in Python classes with inheritance

Explanation

  • The Phone class has a private attribute __price, which cannot be accessed directly outside the class.
  • The SmartPhone class inherits from Phone, allowing it to access the private attribute using name mangling (_Phone__price).
  • The __show method in Phone is a private method and cannot be called directly from an instance of SmartPhone.
  • The constructor of SmartPhone initializes the parent class Phone with price, brand, and camera parameters.
  • The code attempts to call s.show(), which will raise an error since show is not defined in SmartPhone or Phone.
python

Output

text

Demonstrating inheritance and encapsulation in Python classes

Explanation

  • The Parent class initializes a private attribute __num through its constructor, ensuring encapsulation.
  • The get_num method in the Parent class provides a way to access the private attribute __num.
  • The Child class inherits from the Parent class, allowing it to access the get_num method.
  • The show method in the Child class prints a message, demonstrating additional functionality in the subclass.
  • An instance of Child is created with the value 100, and both the inherited method get_num and the show method are called, showcasing polymorphism.
python

Output

text

Demonstrating inheritance and encapsulation in Python classes

Explanation

  • The Parent class initializes a private attribute __num and provides a method get_num to access it.
  • The Child class inherits from Parent but initializes its own private attribute __val without calling the parent's initializer, which can lead to an error when accessing __num.
  • An instance of Child is created with values 100 and 10, but it will raise an error when trying to access get_num() since the parent constructor is not invoked.
  • The get_val method in the Child class allows access to the private attribute __val.
  • The print statements attempt to display both the parent's and child's values, illustrating the use of encapsulation and method overriding in class design.
python

Output

text

Demonstrating inheritance and encapsulation in Python classes

Explanation

  • The Parent class initializes a private attribute __num and provides a method get_num to access it.
  • The Child class inherits from Parent but initializes its own private attribute __val without calling the parent's constructor.
  • The get_val method in the Child class allows access to the private attribute __val.
  • An instance of Child is created with values 100 and 10, but only __val is accessible through get_val.
  • The output displays the value of __val from the Child instance, demonstrating encapsulation.
python

Output

text

Demonstrating inheritance and method usage in Python classes

Explanation

  • The code defines two classes, A and B, where B inherits from A.
  • Class A has an initializer that sets an instance variable var1 to 100 and a method display1 that prints this variable.
  • Class B contains a method display2 that also prints the inherited var1 from class A.
  • An object of class B is created, and both display1 and display2 methods are called, showcasing how inherited properties can be accessed.
  • The output will show the value of var1 from class A, which remains 100, regardless of the argument passed to the methods.
python

Output

text

Demonstrating inheritance and method usage in Python classes

Explanation

  • The code defines two classes, A and B, where B inherits from A.
  • Class A has an initializer that sets an instance variable var1 to 100 and a method display1 that prints its argument.
  • Class B contains a method display2 that prints its argument, but it does not override any methods from class A.
  • An instance of class B is created, and both display1 and display2 methods are called with different arguments, demonstrating method functionality across inherited classes.
  • The output will show the values passed to each display method, illustrating how inheritance allows access to methods from the parent class.
python

Output

text

Understanding inheritance and method overriding in Python classes

Explanation

  • The code defines two classes, A and B, where B inherits from A.
  • Class A has an initializer that sets an instance variable var1 to 100 and a method display1 that updates var1 and prints it.
  • Class B has a method display2 that prints the current value of var1 inherited from class A.
  • An object of class B is created, and display1 is called with the argument 200, updating var1 to 200.
  • Finally, display2 is called, which prints the updated value of var1 from class A.
python

Output

text

Demonstrating method overriding in Python inheritance with phone and smartphone classes

Explanation

  • The code defines a base Phone class with a constructor that initializes price, brand, and camera attributes while printing a constructor message
  • The SmartPhone class inherits from Phone and overrides the buy method with its own implementation that prints a different message
  • When creating a SmartPhone instance and calling its buy method, the overridden version executes instead of the parent class method due to polymorphism
  • This showcases how child classes can modify parent class behavior through method overriding while maintaining the same method signature
  • The output demonstrates "Buying a smartphone" rather than "Buying a phone" because the child class explicitly redefines the buy functionality
python

Output

text

Super Keyword

This code defines a Phone class and a SmartPhone subclass with overridden methods for purchasing behavior.

Explanation

  • The Phone class initializes with attributes for price, brand, and camera, while printing a message during construction.
  • The buy method in the Phone class outputs a message indicating a phone purchase.
  • The SmartPhone class inherits from Phone and overrides the buy method to provide a specific message for smartphone purchases.
  • The super().buy() call within the SmartPhone class allows access to the parent class's buy method, ensuring both messages are printed.
  • An instance of SmartPhone is created with specific attributes, and calling s.buy() demonstrates the overridden behavior.
python

Output

text

Demonstrating the incorrect usage of super() outside a class context in Python

Explanation

  • The code defines a base class Phone with an initializer that sets price, brand, and camera attributes.
  • A derived class SmartPhone inherits from Phone and overrides the buy method to provide specific functionality.
  • An instance of SmartPhone is created with specific attributes, but the attempt to call super().buy() outside the class context results in an error.
  • The correct usage of super() should be within a method of the child class to access methods from the parent class.
  • This snippet highlights the importance of understanding the scope and context in which super() can be utilized.
python

Output

text

Understanding super() method usage and parent class attribute access in Python inheritance

Explanation

  • The code demonstrates inheritance where SmartPhone class extends Phone class and overrides the buy method
  • When calling super().brand inside the child class buy method, it attempts to access the brand attribute from the parent class
  • However, this will raise an AttributeError because brand is not a method but a public instance variable, and super() can only access methods, not direct attributes
  • The code structure shows proper class initialization with private price attribute and public brand/camera attributes
  • The output would display "Buying a smartphone" followed by an AttributeError when trying to access super().brand
python

Output

text

Understanding Inheritance and Constructor Initialization in Python Classes

Explanation

  • The Phone class is defined with an initializer that sets the price, brand, and camera attributes while printing a message indicating the constructor is called.
  • The SmartPhone class inherits from Phone and has its own initializer that adds operating system and RAM attributes.
  • The super() function is used to call the parent class's constructor, ensuring that the Phone attributes are initialized properly.
  • An instance of SmartPhone is created with specific values, demonstrating how attributes from both the parent and child classes are set.
  • The final two print statements output the operating system and brand of the smartphone instance, showcasing attribute access from the derived class.
python

Output

text
Inheritance in summary
  • A class can inherit from another class.

  • Inheritance improves code reuse

  • Constructor, attributes, methods get inherited to the child class

  • The parent has no access to the child class

  • Private properties of parent are not accessible directly in child class

  • Child class can override the attributes or methods. This is called method overriding

  • super() is an inbuilt function which is used to invoke the parent class methods and constructor

Demonstrating inheritance and encapsulation in Python classes

Explanation

  • The Parent class initializes a private attribute __num and provides a method get_num() to access its value.
  • The Child class inherits from Parent, calling its constructor with super() to initialize __num and adds a private attribute __val.
  • The Child class includes a method get_val() to return the value of its private attribute __val.
  • An instance of Child is created with values 100 and 200, and both get_num() and get_val() methods are called to print the respective values.
  • This code illustrates the principles of encapsulation by using private attributes and inheritance to extend functionality.
python

Output

text

Demonstrating inheritance and method overriding in Python classes

Explanation

  • The Parent class initializes an attribute num with a value of 100.
  • The Child class inherits from Parent and calls the parent's constructor using super(), initializing num and adding its own attribute var with a value of 200.
  • The show method in the Child class prints both the inherited num and the var attributes.
  • An instance of Child is created and the show method is called, displaying the values of num and var.
python

Output

text

Demonstrating inheritance and private attributes in Python classes

Explanation

  • The Parent class initializes a private attribute __num with a value of 100.
  • The show method in the Parent class prints the value of __num.
  • The Child class inherits from Parent and initializes its own private attribute __var with a value of 10.
  • The show method in the Child class calls the show method of the Parent class to display __num, followed by printing its own __var.
  • An instance of Child is created, and calling obj.show() outputs both the parent's and child's private attributes.
python

Output

text

Demonstrating inheritance and encapsulation in Python classes

Explanation

  • The Parent class initializes a private variable __num set to 100 and has a method show to print its value.
  • The Child class inherits from Parent and initializes its own private variable __var set to 10, overriding the show method to print __var.
  • The super().__init__() call in the Child class constructor ensures that the Parent class's constructor is executed, initializing __num.
  • When an instance of Child is created and show is called, it prints "Child: 10", demonstrating method overriding.
  • The private variables __num and __var cannot be accessed directly outside their respective classes, showcasing encapsulation.
python

Output

text

Types of Inheritance

  • Single Inheritance
  • Multilevel Inheritance
  • Hierarchical Inheritance
  • Multiple Inheritance(Diamond Problem)
  • Hybrid Inheritance

Demonstrating single inheritance in a Python class structure for a phone and smartphone

Explanation

  • The Phone class is defined with an initializer that sets the price, brand, and camera specifications.
  • The buy method in the Phone class allows for simulating the purchase of a phone.
  • The SmartPhone class inherits from the Phone class, gaining its properties and methods without additional modifications.
  • An instance of SmartPhone is created with specific attributes, and the buy method is called to demonstrate functionality.
python

Output

text

Demonstrating multilevel inheritance in a product hierarchy with Python classes

Explanation

  • The Product class serves as a base class with a method to print customer reviews.
  • The Phone class inherits from Product, adding attributes for price, brand, and camera, and includes a method to simulate buying a phone.
  • The SmartPhone class inherits from Phone, allowing it to utilize both the buy method and the inherited review method from Product.
  • An instance of SmartPhone is created with specific attributes, and both the buy and review methods are called to demonstrate functionality.
python

Output

text

This code defines a hierarchical class structure for different types of phones in Python.

Explanation

  • The Phone class initializes with attributes for price, brand, and camera specifications.
  • The buy method in the Phone class allows for simulating the purchase of a phone.
  • SmartPhone and FeaturePhone are subclasses of Phone, inheriting its properties and methods.
  • Instances of SmartPhone and FeaturePhone are created with specific attributes and invoke the buy method to demonstrate functionality.
  • The use of encapsulation is indicated by the double underscore in self.__price, suggesting that this attribute is intended to be private.
python

Output

text

Python multiple inheritance with constructor chaining and method resolution order

Explanation

  • The code demonstrates multiple inheritance where SmartPhone class inherits from both Phone and Product parent classes
  • The Phone constructor initializes private price attribute, brand, and camera attributes while printing a constructor message
  • When creating SmartPhone instance, the constructor is called only once from the first parent class (Phone) due to Python's Method Resolution Order (MRO)
  • The buy() method is inherited from Phone class and executes successfully on the SmartPhone object
  • The review() method is inherited from Product class and works correctly through multiple inheritance mechanism
python

Output

text

This code demonstrates the creation of a smartphone class that inherits from both phone and product classes.

Explanation

  • The Phone class initializes with attributes for price, brand, and camera, and includes a method to simulate buying a phone.
  • The Product class has a method for customer reviews, which takes a rating as an argument.
  • The SmartPhone class inherits from both Phone and Product, allowing it to utilize methods and properties from both parent classes.
  • An instance of SmartPhone is created with specific attributes, and both the buy and review methods are called on this instance.
  • The code illustrates basic principles of inheritance and method usage in Python.
python

Output

text

This code defines a smartphone class that inherits features from both phone and product classes.

Explanation

  • The Phone class initializes with attributes for price, brand, and camera, and includes a method to simulate buying a phone.
  • The Product class has a method for reviewing a product based on a rating.
  • The SmartPhone class inherits from both Phone and Product, allowing it to access methods from both parent classes.
  • An instance of SmartPhone is created with specific attributes, and methods from both parent classes are called to demonstrate functionality.
  • The output showcases the constructor message from Phone, followed by the buying action and customer review.
python

Output

text

This code demonstrates the creation of a smartphone class that inherits properties from both a phone and a product class.

Explanation

  • The Phone class initializes with attributes for price, brand, and camera, and includes a method to simulate buying a phone.
  • The Product class contains a method for reviewing a product based on a rating.
  • The SmartPhone class inherits from both Phone and Product, allowing it to utilize methods and properties from both parent classes.
  • The constructor of SmartPhone calls the parent Phone constructor using super(), ensuring proper initialization of inherited attributes.
  • An instance of SmartPhone is created with specific values, and methods from both parent classes are called to demonstrate functionality.
python

Output

text

Understanding the diamond problem in Python through class inheritance and method resolution order

Explanation

  • The code defines a Phone class with an initializer that sets price, brand, and camera attributes, and includes a buy method.
  • A Product class is created with its own buy method that prints a different message.
  • The SmartPhone class inherits from both Phone and Product, demonstrating multiple inheritance.
  • When an instance of SmartPhone is created, the buy method from the Product class is called due to Python's method resolution order (MRO), which prioritizes the first class listed in the inheritance.
  • The output confirms that the Product's buy method is executed, illustrating how Python handles the diamond problem by following the MRO.
python

Output

text

Understanding method overriding and inheritance in Python classes

Explanation

  • The code defines three classes: A, B, and C, where B inherits from A, and C inherits from B.
  • Class A has a method m1 that returns the integer 20.
  • Class B overrides the m1 method to return 30 and introduces a new method m2 that returns 40.
  • Class C overrides the m2 method to return 20, while it inherits the overridden m1 method from class B.
  • The final print statement calculates the sum of m1 from obj1 (20), m1 from obj3 (30), and m2 from obj3 (20), resulting in a total of 70.
python

Output

text

Understanding method overriding and super calls in a class hierarchy

Explanation

  • The code defines three classes: A, B, and C, where B inherits from A and C inherits from B.
  • Class A has a method m1 that returns the integer 20.
  • Class B overrides the m1 method, calling the parent class's m1 using super() and adding 30 to the result, returning 50.
  • Class C also overrides the m1 method, but it mistakenly calls self.m1(), which leads to infinite recursion since it keeps calling its own m1 method.
  • When an object of class C is created and m1 is called, it will result in a RecursionError due to the infinite loop in method calls.
python

Output

text

This code demonstrates method overriding and the use of super() in a class hierarchy.

Explanation

  • Class A defines a method m1 that returns the integer 20.
  • Class B inherits from A and overrides the m1 method to add 30 to the result of the superclass's m1 method, resulting in 50.
  • Class C inherits from B and further overrides the m1 method, adding 20 to the result of B's m1, yielding a final result of 70.
  • An instance of class C is created, and calling m1 on this instance returns 70, demonstrating the cumulative effect of method overrides in the inheritance chain.
python

Output

text

Polymorphism

  • Method Overriding
  • Method Overloading
  • Operator Overloading

Polymorphism means "many forms". It refers to the ability of an entity (like a function or object) to perform different actions based on the context.

Technically, in Python, polymorphism allows same method, function or operator to behave differently depending on object it is working with. This makes code more flexible and reusable.

Demonstrating method overriding and polymorphism in Python classes

Explanation

  • The Animal class defines a method sound() that returns a generic sound.
  • The Dog and Cat classes inherit from Animal and override the sound() method to return specific sounds ("Bark" and "Meow", respectively).
  • A list named animals contains instances of Dog, Cat, and Animal.
  • A loop iterates through the animals list, calling the sound() method on each instance, showcasing polymorphic behavior where the method called depends on the object's class.
  • This code illustrates how method overriding allows subclasses to provide specific implementations of methods defined in a parent class.
python

Output

text

Understanding Method Overloading Behavior in Python Classes

Explanation

  • The Shape class defines two methods named area, intended to calculate the area of different shapes based on the parameters provided.
  • The first area method calculates the area of a circle using the formula πr², while the second calculates the area of a rectangle using the formula length × breadth.
  • In Python, method overloading does not function as it does in some other languages; the second definition of area overrides the first, meaning only the rectangle calculation is retained.
  • The output of print(s.area(5)) will result in an error since the first area method is not accessible, while print(s.area(5, 6)) will correctly return 30.
  • This behavior highlights the need for alternative approaches, such as using default parameters or variable-length arguments, to achieve similar functionality.
python

Output

text

This code defines a Shape class that calculates the area of a circle or rectangle based on provided dimensions.

Explanation

  • The Shape class contains a method area that calculates the area based on one or two parameters.
  • If only one argument a is provided, it calculates the area of a circle using the formula πr² (approximated as 3.14).
  • If two arguments a and b are provided, it calculates the area of a rectangle using the formula length × width.
  • An instance of the Shape class is created, and the area method is called twice: once for a circle and once for a rectangle.
  • The results of the area calculations are printed to the console.
python

Output

text

This code snippet demonstrates string concatenation using operator overloading in Python.

Explanation

  • The + operator is used to concatenate two strings, 'hello' and 'world'.
  • Python's operator overloading allows the + operator to be redefined for different data types, such as strings.
  • The result of this operation is a single string, 'helloworld', which combines both input strings without any separator.
  • This feature enhances code readability and allows for intuitive operations on user-defined types as well.
python

Output

text

This code performs a simple arithmetic addition of two integers.

Explanation

  • The code adds the integers 4 and 5 together.
  • The result of the addition is 9.
  • This operation demonstrates basic arithmetic functionality in Python.
  • It can be used as a building block for more complex calculations.
python

Output

text

This code snippet demonstrates how to concatenate two lists in Python.

Explanation

  • The code uses the + operator to combine two lists, [1, 2, 3] and [4, 5].
  • The result of the operation is a new list that contains all elements from both lists in the order they were added.
  • The original lists remain unchanged after the concatenation.
  • This method is commonly used for merging lists in Python programming.
python

Output

text

Next in this series: Python Abstraction: Master Abstract Classes & Interfaces →

Frequently Asked Questions

The Customer class demonstrates a Has-A relationship with the Address class through object composition, where an Address instance is passed as a parameter to the Customer constructor.
The Customer class uses the printaddress method to access and print the city, pin, and state attributes from the associated Address instance.
The Address class encapsulates location details, including city, pin code, and state information, showcasing encapsulation.
No, private attributes cannot be accessed directly outside their class, which is emphasized in the blog post to reinforce the importance of encapsulation.
Passing an Address instance to the Customer class demonstrates object composition in Python, where objects are composed to form more complex structures.

How was this tutorial?