Lesson 18: OOP ( Part 2) - CodeAcademy-Online/python-level-1 GitHub Wiki
As we already know, Python
is an Object-Oriented Programming language. Everything in Python is an object. Like other Object-Oriented languages, when creating objects using classes, there are 4 basic principles for writing clean and concise code. These principles are called the four pillars of object-oriented programming (OOP). These four pillars are: Inheritance, Polymorphism, Encapsulation and Abstraction.
A few to name:
- It makes it so much easier to maintain and update existing code.
- Provides code reusability.
- Provides a modular structure.
- It is easy to debug.
In most of the object-oriented languages access modifiers are used to limit the access to the variables and functions of a class. Most of the languages use three types of access modifiers, they are - private, public and protected.
Just like any other object oriented programming language, access to variables or functions can also be limited in python using the access modifiers. Python makes the use of underscores
to specify the access modifier for a specific data member and member function in a class.
Access modifiers play an important role to protect the data from unauthorized access as well as protecting it from getting manipulated. When inheritance (look at the following chapter β¬οΈ ) is implemented there is a huge risk for the data to get destroyed(manipulated) due to transfer of unwanted data from the parent class to the child class. Therefore, it is very important to provide the right access modifiers
for different data members and member functions depending upon the requirements.
There are 3 types of access modifiers for a class in Python. These access modifiers define how the members of the class can be accessed.
These are:
- Public - the members declared as
Public
are accessible from outside theclass
through an object of theclass
. - Protected - the members declared as
Protected
are accessible from outside theclass
but only in aclass
derived from it that is in the child or subclass. - Private - these members are only accessible from within the class. No outside
Access
is allowed.
Public
# defining a class Employee
class Employee:
# constructor
def __init__(self, name: str, sal: int):
self.name = name # Public attribute
self.sal = sal
---- OUTPUT ----
>>> emp = Employee("Ironman", 999000);
>>> emp.sal
999000
π¨βπ« β Attention β By default, all the variables and member functions of a class are public in a python program.
Protected
# defining a class Employee
class Employee:
# constructor
def __init__(self, name: str, sal: int):
self._name = name # Protected attribute
self._sal = sal # Protected attribute
---- OUTPUT ----
>>> emp = Employee("Captain", 10000);
>>> emp._sal
10000
Private
# defining a class Employee
class Employee:
# constructor
def __init__(self, name: str, sal: int):
self.__name = name # Private attribute
self.__sal = sal # Private attribute
---- OUTPUT ----
>>> emp = Employee("Bill", 10000);
>>> emp.__sal;
AttributeError: 'employee' object has no attribute '__sal'
βΉοΈ There are private and protected methods too. The working mechanism of access modifiers applies the same as for the class attributes: A private method can be only called inside the particular class, a protected method inside the particular class and by its child classes.
Private method
class Example:
def __init__(self):
self.__a = "This is a private variable" def __can_not_run(self):
print("Can't be executed outside of this class")
>>> ex = Example()
>>> print(ex.__a)
Traceback (most recent call last):
File "<input>", line 1, in <module>
AttributeError: 'Example' object has no attribute '__a'>>> ex.__can_not_run()
Traceback (most recent call last):
File "<input>", line 1, in <module>
AttributeError: 'Example' object has no attribute '__can_not_run'
Protected method
class User:
def __init__(self,username) -> None:
self.username = username
def _check_account_existence(self):
print(f"Checking if {self.username} has signed up already.")
---- OUTPUT ----
>>> user = User("cowboy")
>>> user._check_account_existence()
Checking if cowboy has signed up already.
π Reminder:
- Protected methods use one underscore as their prefix, while private methods use two underscores as their prefix.
- We can call protected methods outside the class directly by using their method names. However, calling private methods requires name mangling.
Besides these two obvious differences between protected and private methods, the major difference between these two kinds of methods_ pertains to their accessibility within their subclasses_. That is, protected methods are accessible within the subclasses, while private methods are not accessible within the subclasses (although the reason is also due to name mangling). βΉοΈ
Inheritance
is the process by which one class inherits the properties of another class. This newly formed class is called a child class and the other class is called a parent class. It provides code reusability because we are using an existing class to increase the properties of a new class. A child class can access all the data members and functions of the parent class.
In Python, to inherit
a class, we use ChildClass(ParentClass)
at the time of defining the class: π½
class Employee:
def __init__(self, name: str,age: int, exp: int, salary: int): ## Defining The Constructor With Common data
## Instance Attributes ## instance attributes that are only accessible by the object of the class
self.name = name
self.age = age
self.exp = exp
self.salary = salary
def show(self) -> None: ## A Simple method that prints the data
print(self.name,self.age,self.exp,self.salary) ## Printing all the variables
class Engineers(Employee): ## Parent class Employee inherit by child Enginners
def __init__(self, name: str,age: int, exp: int, salary: int, level: int): ## Constructor of Enginners class(child)
super().__init__(name,age,exp,salary) ## accessing the parent class constructor and instance attributes with the help of super method
self.level = level ## Assigning a new attribute level for the Enginner class
def print_data(self): ## Creating a New Method for the Enginner class that is only accessable by the object of Enginner class
print(self.level) ## Printing the level of the Enginner
class Designers(Employee): ## Parent class Employee inherit by child Designers
def __init__(self,name,age,exp,salary,position):
super().__init__(name,age,exp,salary)
self.position = position ## Extending the attributes of parent class by adding a new attribute position
def show(self): ## A Simple Method Belong to the Designer Class that is used to print the data
super().show() ## Accessing parent class method and extending it to print the position also
print(self.position) ## Printing the position of the Designer
obj_E = Engineers("Alex",35,10,50000,'JR Developer') ## Creating an object for the Enginners class, Need to paas arguments because It is a inherit class
obj_E.show() ## accessing parent method show with the help of the child class object
obj_E.print_data() ## accessing child class method debug, that is only access by the Designers object
obj_D = Designers("Karl",45,20,55000,"UI Designer")
obj_D.show() ## printing all the data
'''
obj_E.debug() ## AttributeError: 'Engineers' object has no attribute 'debug' ,debug is not a member of the Enginners class
Employee('Garry',35,10,50000).show() ## Garry 35 10 50000 ,because a class can access class attributes without the need of an object
obj = Employee('Garry',35,10,50000)
obj.debug() ## AttributeError: 'Employee' object has no attribute 'debug' , A parent can not access a child class
'''
At first, we have a parent class Employee
that has some basic information: name
, age
, exp
(experience), and salary
. Employee
class has a constructor that contains all the instance attributes. Lastly, we also have a method named show that only has a print statement and is used for printing the information.
Engineer
and Designer
both are the child classes of the parent class Employee
.
The ** super()
method** helps a child class to access the members of the parent class. Engineers
class accesses name
, age
, exp
, and salary
information from the parent class.
Encapsulation is the process of hiding the data, providing security to data by making the variable protected. The protected member can only be accessed by the class member. If you try to access it outside the class normally, by creating an object. it will result in an error. To access the protected member you need to use object._protectedmember
.
In Python, to create a protected member, we use the convention of prefixing the name of the member by a single underscore, e.g., _name.
Encapsulation protects an object from unwanted access by clients. It reduces the chances of human error and also simplifies the maintenance of the application. Encapsulation allows access to a level without revealing the details: π½
class Parent: ## Creating a class name Parent
def __init__(self): ## Constructor of parent class
# protected member
self._mobilenumber = 5555551234 ## Protected member of the class Parent
class Child(Parent): ## Child class inhering properties from the Parent class
def __init__(self): ## Constructor of the class name
Parent.__init__(self) ## accessing members of the Parent class, another way is to used super()
print("Calling Protected Member")
print(self._mobilenumber) ## accessing protected member using the class member
obj = Child() ## creating the object
print(obj.mobilenumber) ## AttributeError: 'Child' object has no attribute 'mobilenumber'
print(obj._mobilenumber) ## Prints mobilenumber, explicitly allowing the access to protected member
_mobilenumber
is a protected member of the class that can only be accessed by the class members and object after giving explicit permission to the object.
Polymorphism means having different forms. It refers to the ability of a function with the same name to carry a different functionality altogether. One of the best examples of inbuild polymorphism is the len()
function. When we use it for a list, it returns the count of number elements in the list. When we use it with a dictionary, it returns the count of keys. When we use it with a string, it returns the number of characters in the string. Letβs see an example of polymorphism.
Simple example: β¬
def add(x: int, y: int, z: int = 0) -> int:
return x+y+z
print(add(5, 6))
print(add(5, 7, 4))
----------OUTPUT--------------
11
16
The above code shows how a single function can be used to perform two number and three number addition at the same time. Now letβs see polymorphism in a class method:
class Rectangle:
def __init__(self, l, b):
self.l = l
self.b = b
def area(self):
return self.l * self.b
class Square:
def __init__(self, side):
self.s = side
def area(self):
return self.s ** 2
rec = Rectangle(10, 20)
squ = Square(10)
for data in (rec, squ):
print(data.area())
-----------------------------OUTPUT----------------------------
200
100
In the code above, area()
is performing two tasks in different instances of the program. On one side it is calculating the area
of the rectangle
, and on the other side it is calculating the area of the square in the same program.
Abstraction is used to hide the internal functionality of the function from the users. By applying abstraction
, each object is only exposed to a high-level mechanism for using it. A method that only has a declaration and not a definition is called an abstract method
. An abstract method
doesnβt have anything inside the body. A class that has an abstract method is called an abstract class
.
Python by default does not support abstract classes,
but there is a module named abc
that allows Python to create abstract methods
and classes
.
More on Abstraction: π Real Phyton : Python Interfaces
π§ : Repeat the Conditional Statements, Loops, Functions to finish these task.
-
Create a few examples of yourself where you show four pillars of OOP in action.
-
Write a class called
CoffeeShop
, which has three instance variables:-
name
: a string (basically, of the shop) -
menu
: a list of items (of dict type), with each item containing the item (name of the item), type (whether a food or a drink) and price. -
orders
: an empty list
and seven methods:
-
add_order
: adds the name of the item to the end of the orders list if it exists on the menu, otherwise, return "This item is currently unavailable!" -
fulfill_order
: if the orders list is not empty, return "The {item} is ready!". If the orders list is empty, return "All orders have been fulfilled!" -
list_orders
: returns the item names of the orders taken, otherwise, an empty list. -
due_amount
: returns the total amount due for the orders taken. -
cheapest_item
: returns the name of the cheapest item on the menu. -
drinks_only
: returns only the item names of type drink from the menu. -
food_only
: returns only the item names of type food from the menu.
IMPORTANT: Orders are fulfilled in a FIFO (first-in, first-out) order. Examples:
tcs.add_order("hot cocoa") β "This item is currently unavailable!" # Tesha's coffee shop does not sell hot cocoa tcs.add_order("iced tea") β "This item is currently unavailable!" # specifying the variant of "iced tea" will help the process tcs.add_order("cinnamon roll") β "Order added!" tcs.add_order("iced coffee") β "Order added!" tcs.list_orders β ["cinnamon roll", "iced coffee"] # all items of the current order tcs.due_amount() β 2.17 tcs.fulfill_order() β "The cinnamon roll is ready!" tcs.fulfill_order() β "The iced coffee is ready!" tcs.fulfill_order() β "All orders have been fulfilled!" # all orders have been presumably served tcs.list_orders() β [] # an empty list is returned if all orders have been exhausted tcs.due_amount() β 0.0 # no new orders taken, expect a zero payable tcs.cheapest_item() β "lemonade" tcs.drinks_only() β ["orange juice", "lemonade", "cranberry juice", "pineapple juice", "lemon iced tea", "vanilla chai latte", "hot chocolate", "iced coffee"] tcs.food_only() β ["tuna sandwich", "ham and cheese sandwich", "bacon and egg", "steak", "hamburger", "cinnamon roll"]
Notes: Round off due amount up to two decimal places.
-
-
Update previous task's solution using four pillars paradigm of OOP. (Minimum Encapsulation, Inheritance)
-
Create a Python program that simulates an electronics store. The store sells different types of electronic devices like laptops, smartphones, and televisions.
-
Create a base class
ElectronicDevice
with attributes likebrand
,price
, andwarranty_period
. It should have methods toget_details()
andpurchase()
. Thepurchase()
method should reduce the stock of the device by1
. -
Create child classes
Laptop
, Smartphone, and
Televisionthat inherit from the
ElectronicDeviceclass. Each of these classes should have additional attributes specific to them. For example,
Laptopcan have
ramand
storage,
Smartphonecan have
screen_sizeand
battery_capacity, and
Televisioncan have
screen_sizeand
resolution`. -
Use access modifiers to ensure that the price and stock attributes cannot be directly accessed or modified outside the class.
-
Create an instance of each device and call their methods to test the functionality.
-
Implement a
Discount
class that can be applied to the electronic devices to reduce their price. This class should have a methodapply_discount()
that takes anElectronicDevice
object and a discount percentage, and returns the price after discount.
-