Object-Oriented Programming
Object-Oriented Programming (OOP) in Python
- Everything in Python is an Object
- Shows data types through class type
print(type(None)) # <class 'NoneType'>
print(type(True)) # <class 'bool'>
print(type(5)) # <class 'int'>
print(type(5.5)) # <class 'float'>
print(type('hello')) # <class 'str'>
print(type([])) # <class 'list'>
print(type(())) # <class 'tuple'>
print(type({})) # <class 'dict'>
- Each built-in data type in Python is actually a class.
- When you create a value, you’re instantiating an object of that class.
- Example: 5 is an object of class int, 'hello' is an object of class str.
Creating Your Own Classes
- We're able to create our own types, our own data types with different attributes and methods.
- We can create our own classes and objects.
- OOP is a programming paradigm that uses "objects" to represent data and methods to manipulate that data.
- A class is a blueprint for creating objects. It defines a set of attributes and methods.
- An object is an instance of a class. It is created based on the blueprint provided by the class and has its own unique state and behavior.
class BigObj: # Blueprint (class name should be in PascalCase, singular)
pass
obj1 = BigObj() # () Instantiate or create an object
print(type(obj1)) # shows that BigObj is a class
- This is how you want to organize your code.
- Thinking less procedural and thinking more in terms of functionality
- Whenever we create a method the self parameter is required self refers to whatever's to the left of the dot.
class PlayerCharacter: #class object attribute, it's not dynamic, it's static
membership = True
#Constructor or # Initialization or init method(dunder method)
def __init__(self, name='anonymous', age=0):
#Can access class object attribute through self, can add safeguards and controls
if age > 18:
self.name = name
self.age = age
def run(self):
print('run')
return 'done'
def shout(self):
print(f'my name is {self.name}')
@classmethod
- It is to define a method that is bound to the class and not the object of the class.
- it's a method on an actual class
- Alternative constructor that can create objects in different ways
@classmethod
def add_things(cls, num1, num2):
return cls('Teddy', num1 + num2)
@staticmethod
- It is a method that doesn't operate on an instance nor modify class state.
- It behaves like a regular function but belongs to the class's namespace.
@staticmethod
def add_numbers(num1, num2):
return num1 + num2
init method
- This is going to call the init method, or Object constructor
player1 = PlayerCharacter('Cindy', 20)
player1.attack = 50 # can add new attributes to the object after its been created
# can create different players with different attributes
print(player1) # Output: <__main__.PlayerCharacter object at 0x7f9c8c4d1d60>
print(player1.name) # Output: Cindy(access by .name)
print(player1.age)
player1.run() # Output: run
print(help(player1)) # shows blueprint of the object
player1.shout() # Output: my name is Cindy
player2 = PlayerCharacter.add_things(12, 10)
print(player2.age)
Another Example: Cat Class
class Cat:
def __init__(self, name, color):
self.name = name
self.color = color
def meow(self):
print(f'{self.name} says meow!')
cat1 = Cat('Whiskers', 'black')
print(cat1.name) # Output: Whiskers
print(cat1.color) # Output: black
cat1.meow() # Output: Whiskers says meow!
Syntax of OOP
class NameOfClass:
def __init__(self, param1, param2):
self.param1 = param1
self.param2 = param2
def method(self):
code
pass
@classmethod
def class_method(cls, param1, param2):
code
pass
@staticmethod
def static_method(param1, param2):
code
pass
4 Pillars of OOP
1. Encapsulation :
- It is the binding of data and functions that manipulate that data and we encapsulate into one big object{OOP}
- It keeps methods, functions and all other stuff safe from outside interference and misuse.
2. Abstraction :
- It is the concept of hiding the complex reality while exposing only the necessary parts.
- The idea behind abstraction is that we hide away information and only give access to things that a user is concerned about. _name : protected attribute, underscore means that you shouldn't touch this __name : private attribute, double underscore means that you really shouldn't touch this
3. Inheritance :
- It is a way to form new classes using classes that have already been defined. In python, everything is an object, and all objects inherit from a base object class.
Example of Inheritence:
class User(object): # parent class or base class or super class
- The object is default parent class in python comes with built-in dundermethods
- Python inherits from the base object class that Python provides we don't need init method if no variables or attributes are to be assigned
def __init__(self, email):
self.email = email
def sign_in(self): # chilren classes, subclasses, or derived classes
print('logged in')
class Wizard(User): # Wizard class inherits User class by passing it as parameter
def __init__(self, name, power, email):
super().__init__(email) # calling the parent class constructor
self.name = name
self.power = power
def attack(self):
print(f'attacking with power of {self.power}')
class Archer(User):
def __init__(self, name, num_arrows):
self.name = name
self.num_arrows = num_arrows
def attack(self):
print(f'attacking with arrows: arrows left - {self.num_arrows}')
def run(self):
print('ran really fast')
wizard1 = Wizard('Merlin', 50, 'merlin@gmail.com')
archer1 = Archer('Robin', 100)
wizard1.attack() # Output: attacking with power of 50
archer1.attack() # Output: attacking with arrows: arrows left - 100
wizard1.sign_in() # Output: logged in # inherited method from User class
print(wizard1.email) # inherited attribute from User class
isinstance
Check if something is an instance or object of a class - syntax = isinstance(instance, ClassName) # returns True or False
print(isinstance(wizard1, Wizard)) # Output: True
print(isinstance(wizard1, User)) # Output: True
print(isinstance(wizard1, object)) # Output: True(all classes inherit from object class)
Multuple Inheritance
This means that a class can be derived from more than one base class.
class HybridBorg(Wizard, Archer): # can give multiple parameters
def __init__(self, name, arrows):
Archer.__init__(self, name, arrows)
hb1 = HybridBorg('borgie', 100)
print(hb1.sign_in())
print(hb1.num_arrows)
print(hb1.run())
4. Polymorphism :
- means having many forms.
- Same method name, different behavior depending on the object.
print(wizard1.attack())
print(archer1.attack())
They share the same method names but the object that's calling is different
Example of Polymorphism:
def player_attack(char):
char.attack()
player_attack(wizard1)
player_attack(archer1)
for char in [wizard1, archer1]:
char.attack()
Introspection
Introspection in computer programming means the ability to determine the type of an object at runtime.(shows what you have access to)
print(dir(wizard1)) # shows all attributes and methods of the object
Dunder Method(Double Underscore Method) / Magic Method
Dunder methods are special methods in Python that start and end with double underscores (e.g., str, len). - Used to customize object behavior.
class Toy():
def __init__(self, color, age):
self.color = color
self.age = age
self.my_dict = {'name': 'Yoyo', 'has_toys': True}
The str method is only modified when we use it on this specific object.
def __str__(self):
return f'{self.color}'
() or call method allows call functions is using this dunder call.
def __call__(self):
return 'yes?'
def __len__(self):
return 5
def __getitem__(self, i):
return self.my_dict[i]
def __del__(self):
print('deleted!')
action_figure = Toy('red', 0)
print(action_figure.__str__()) # default dunder method that returns a string representation
print(str(action_figure)) # same as above
print(len(action_figure)) # Output: 5
print(action_figure()) # Output: yes?
print(action_figure['name']) # Output: Yoyo
del action_figure # Output: deleted!
Method Resolution Order (MRO)
-
Defines the order Python searches for methods in inheritance hierarchies.
-
Rule that Python follows to determine when you run method having complicated inheritance structure.
class A:
num = 10
class B(A):
pass
class C(A):
num = 1
class D(B, C):
pass
print(D.num) # Output: 1
Python looks for num in D, then B, then C, then A. It finds num in C first.
print(D.mro()) # shows the method resolution order
print(D.__mro__) # same as above
D.__str__ # shows the dunder methods available for class D
Explore different use cases of MRO:
Use case of MRO