-
Notifications
You must be signed in to change notification settings - Fork 81
3 Object Oriented Programming
Object-oriented programming is a programming paradigm that organizes code & data using entities called objects.
Object-Oriented Programming (OOP) in Python 3 - Real Python
Object-oriented programming - Object-Oriented Programming in Python 1 documentation
Primitive data types such as int
, bool
and dict
in Python are simple ways in which data are represented. As the code becomes more complex, these simple data types are not sufficient to express the information at hand. For example, in order to describe a Person
as a data type, multiple data of simpler types need to be bundled together. These data can be strings such as the FirstName
and LastName
of the Person
or integers such as the Age
.
To create an object, object oriented programming languages define “blueprints” that are used as a guidelines to create these new user-defined data structures. This “blueprint” is called a class and bundles together the data as properties. The functionalities that act on these data, are called methods.
🚨 ***Classes Vs Instances***Imagine classes as “blueprints” of a building floor. Its properties might include the number of doors, or the dimensions of each room. Its functions can include actions operated, such as open door/window or tidy up room etc.
https://home3ds.com/advantages-converting-2d-floor-plan-3d-floor-plan/
The class or “ floor blueprints” in our case, provide information to the contractor on how the floor should look like after construction ends. The constructed floor illustrated is the actual object or instance.
Classes can be used to create objects, the same way that a blueprint can be used to create floors that look alike. One class can be used to create multiple instances of the same user-defined data type.
All classes are defined by the keyword class
followed by the class name and a colon :
. The code indented below after the definition, belongs to the class body. In the case shown below, the pass
keyword is used. This allows to create an empty class without any implementation.
class Person:
pass
Naming conventions are not just an another directive. They are not mandatory rules, but a consensus between developers of the same language, in order to point out the different uses of parts of the code. ESPECIALLY when sharing code, these conventions allow the community to understand it faster. But even when writing code for internal use, these conventions enhance readability and make code clear and comprehensible.
More on the PEP-8 conventions can be found here:
PEP 8 - Style Guide for Python Code
Python Class Constructors: Control Your Object Instantiation - Real Python
The creation of a new object in Python is split into two discrete steps. First, the construction phase of the object, followed by the initialization phase.
-
Constructor (
__new__
)def __new__(cls, *args, **kwargs): return super().__new__(cls)
This first step is part of the construction process in Python. The special method
__new__
is responsible for creating and returning a new empty object, along with the class initializer arguments.⚠️ In most cases the developer does not have interaction with the `__new__` method and Python takes care of its execution. -
Initializer (
__init__
)def __init__(self): pass
The
💣 The initializer has no return. If the developer returns a value, then a `TypeError` will be thrown.__init__
special method is the one used for initializing a new object. As the name implies, the purpose of this method is to define the initial state of the object. Note that the first parameter,self
is the instance of the object already created and inside the initializer new attributes can be defined.
Python supports two types of attributes that can belong to a class, Instance and Class attributes.
Instance attributes are object properties that share the same name, differ in contents between objects. An example on how to define an instance attribute is displayed below:
class Person:
def __init__(self, first_name, last_name, age):
# Instance Attributes
self.first_name=first_name
self.last_name=last_name
self.age=age
In contrast to instance variables, the values of class attributes is shared by all instances of the same class. They can be used to define a constant or a share property. In the case of the Person
class, the class attribute can be for example the species all persons belong to. The definition of a class attribute is right before the __init__
method as shown below:
class Person:
#Class Attribute
species = "Homo Sapiens"
def __init__(self, first_name, last_name, age):
# Instance Attributes
self.first_name=first_name
self.last_name=last_name
self.age=age
Naming conventions are not just an another directive. They are not mandatory rules, but a consensus between developers of the same language, in order to point out the different uses of parts of the code. ESPECIALLY when sharing code, these conventions allow the community to understand it faster. But even when writing code for internal use, these conventions enhance readability and make code clear and comprehensible.
More on the PEP-8 conventions can be found here:
PEP 8 - Style Guide for Python Code
Python's property(): Add Managed Attributes to Your Classes - Real Python
Properties are an extension of attributes and are often called managed attributes. It is a python functionality that allows developers to avoid getter and setter methods for class attributes.
class Person:
def __init__(self, first_name, last_name, age):
# Instance Attributes
self.first_name=first_name
self.last_name=last_name
self.__age=age
@property
def age(self):
"Get the age property"
return self.__age
@age.setter
def age(self, value):
self.__age=value
Instance methods, are functionalities defined inside a class. Since this functionalities usually depends on the instance attributes of a class, it can only be called using an object. The first parameter of these methods is always self
. An example of such an instance method is displayed below.
class Person:
#Class Attribute
species = "Homo Sapiens"
def __init__(self, first_name, last_name, age):
# Instance Attributes
self.first_name=first_name
self.last_name=last_name
self.age=age
def introduce_yourself(self):
print(f"My name is {self.first_name} {self.last_name}!")
Static methods are a special type of methods. Contextually, they are related with the object but do not use the object data at all. They are usually auxiliary methods and require no object instance to execute their functionality.
class Person:
#Class Attribute
species = "Homo Sapiens"
def __init__(self, first_name, last_name, age):
# Instance Attributes
self.first_name=first_name
self.last_name=last_name
self.age=age
def introduce_yourself(self):
print(f"My name is {self.first_name} {self.last_name}!")
@staticmethod
def clean_room(room_name):
print(f"I am cleaning the {room_name}")
Types of inheritance Python - GeeksforGeeks
Inheritance is the capability of OOP languages to inherit attributes and method of other classes. This allows developers to maintain the same interface of a class, while allowing to change the internal implementation. The existing classes are called base
, super
or parent
. The deriving classes on the other hand are called, sub-
or child
classes.
The simplest form of inheritance is single inheritance. In this case, a subclass inherits all attributes and methods of the base class. This enables code reusability, while allowing the addition of new features.
Below, an example of extending the Person
class is provided. Existing attributes are initialized using the super
class __init__
method. While existing new ones are saved within the child
class __init__
method.
As far as methods are concerned, the methods of the parent
class can be used as is. If modifications to the functionality are needed, the methods can be overriden, to completely change or partially add functionality to the method of the parent class.
class Employee(Person):
def __init__(self, first_name, last_name, age, job_title):
super().__init__(first_name, last_name, age)
self.job_title=job_title
# def introduce_yourself(self):
# print(f"My name is {self.first_name} {self.last_name} and I work as a {self.job_title}!")
# def introduce_yourself(self):
# super().introduce_yourself()
# print(f"I work as a {job_title}")
A second more advanced type of inheritance is Multilevel Inheritance. Here there is a sequence of subclasses that serve as parents for new deriving classes. The final subclass
, inherits all attributes and functionality of all, previous parent
classes. An example of this inheritance type is shown below:
The implementation of the later inheritance in Python
code is the following:
class PostDoc(Employee):
def do_research():
are_results_novel = False
while not are_results_novel:
eat()
sleep()
research()
def write_paper():
print("writing.....")
print("writing.....")
print("writing.....")
print("writing.....")
print("writing.....")
def code():
print("Debugging UQpy v1038274")
Polymorphism in Python - GeeksforGeeks
Polymorphism in Python | Object Oriented Programming (OOPs) | Edureka
The concept of Polymorphism is object-oriented programming, is the supply of a single signature that can treat multiple types of data.
The first and simplest method of polymorphism is ad-hoc polymorphism. In dynamically typed languages like Python, this type of polymorphism is achieved by inspecting the argument type and performing different operations accordingly. One example if the in-build len()
function shown below:
students = ['Student1', 'Student2', 'Student3']
name='Dimitris'
len(students)
len(name)
In Python, inheritance allows to define child
classes that share the same methods and interface as the parent
class. Inherited method can be overriden, while maintaining the same interface. As a result, we have access to methods of same signature but different implementations.
-
Method overriding
class Employee(Person): def __init__(self, first_name, last_name, age, position): super().__init__(first_name, last_name, age) self.position=position # def introduce_yourself(self): # print(f"My name is {self.first_name} {self.last_name} and I work as a {self.job_title}!")
-
Method overloading
💣 Method overloading NOT supported, how to do it insteadclass Rectangle: # function with two default parameters def area(self, a): print('Area of Square is:', a ** 2) def area(self, a, b): print('Area of Rectangle is:', a * b)
class Shape: # function with two default parameters def area(self, a, b=0): if b > 0: print('Area of Rectangle is:', a * b) else: print('Area of Square is:', a ** 2)
Abstract Base Class (abc) in Python - GeeksforGeeks
Abstraction in Python is the process of hiding internal functionality from the user. The goal of this process is to reduce the complexity and allow the generalization of concepts and code architecture. As a result, the user now is familiar with the type of work performed by a function, but not its specific implementation. Abstraction in Python is achieved with the aid of abstract classes ABC
and abstractmethod
.
Abstract base classes are a way enforce a common interface between objects. In way they are a “blueprint” for creating new classes. They define the signature of methods that deriving classes must implement and enforce it. Since ABC
cannot be instantiated, child
classes must be defined. At the same time their child
classes are guaranteed to adhere to the predefined abstract class specifications.
from abc import ABC
class Shape(ABC):
pass
Even though abstract classes can have concrete implementations of some of its methods, their main goal is to define the methods that need child
class specific implementations. This is achieved by using the abstractmethod
decorator. This prevents the instantiation of any subclasses that do not override the specific method with a custom implementation. Otherwise a TypeError
is raised.
from abc import ABC,abstractmethod
class Shape(ABC):
@abstractmethod
def calculate_area():
raise NotImplementedError("Subclasses should implement this!")
Encapsulation is one of the fundamental concepts of OOP. It describes the idea of bundling data and functionalities in units such as Python classes. This idea is usually combined with information hiding, which is the concept of keeping the internal data of a class hidden from its the outside world. This prevents accidental access and modification of private class data. In most programming languages, access modifiers are used for data hiding while getter and setter methods retrieve and modify the value in a controlled way respectively.
Python does NOT share the same access modifiers like public
, private
and protected
available elsewhere. Instead a similar behavior is achieved by using variable naming conventions and using single (_
) and double (__
) leading underscore for class attributes.
- Public access convention
self.name #Public attribute
- Protected access convention
The goal of attribute names prepended with a single underscore is to define protected members. These members of a class are called protected and are visible to child
classes. However they are publicly accessible in Python. This naming convention existing to deter programmer from using these attributes outside the class.
self._name #Protected attribute
- Private access convention
In case of instance attributes with names prepends with a double underscore is to provide private variables and thus restrict access to them. If code external to the class tries to access the attribute then an AttributeError
is raised. During runtime python performs an operation named name mangling with prepends an underscore and the class name to the variable thus making it not directly accessible.
self.__name #Private attribute
Descriptor HowTo Guide - Python 3.10.8 documentation
One common way of handling access and modification of data is with the use of getter and setter methods. This allow the class to hide its data from the public, while allowing setting their new values under controlled conditions. On example of getter and setter methods is displayed below.
class Employee(Person):
def __init__(self, first_name, last_name, age, position, salary):
super().__init__(first_name, last_name, age)
self.__position=position
self.__salary=salary
# I can tell you my current position, but you cannot change it
def get_position(self):
return self.__position
# I will not share my salary info, but you can give me a pay raise
def set_salary(self, salary):
if salary >= self.__salary:
self.__salary=salary
else:
raise ValueError("You can only give me a pay raise!")
These methods can also be replaced with the simpler properties available in Python.
class Employee(Person):
def __init__(self, first_name, last_name, age, position, salary):
super().__init__(first_name, last_name, age)
self.__position=position
self.__salary=salary
@property
def position(self):
return self.__position
def salary(self, new_salary):
if new_salary >= self.__salary:
self.__salary=new_salary
else:
raise ValueError("You can only give me a pay raise!")
salary = property(None, salary)