# OOPS 2 - Dunders, Inheritance Continued
---
title: Agenda
description:
duration: 300
card_type: cue_card
---
### Agenda
* Dunder / Magic methods
* Inheritance
* Private Properties
* Multiple Inheritance
* Method Resolution Order
---
title: Quiz-1
description: Quiz-1
duration: 60
card_type: quiz_card
---
# Question
How many objects would be created by runing the following code?
Code
```python=
class A:
print("Am I a class?")
A()
obj1 = A()
if A():
print(A())
```
# Choices
- [x] 4
- [ ] 2
- [ ] 3
- [ ] 0
---
title: Dunder / Magic methods
description:
duration: 1200
card_type: cue_card
---
## Dunder / Magic methods
* These are special methods that have double underscores at the beginning and end of their names (e.g., __init__).
* These methods provide a way for a class to define its behavior.
* By modifying these methods, you can customize the default behavior of your objects.
Code
```python=
class Car:
def __init__(self, name, mileage):
self.name = name
self.mileage = mileage
```
Code
```python=
c1 = Car("Nexon", 12)
c2 = Car("Altroz", 15)
```
Code
```python=
print(c1)
# Name -> Mileage
```
> Output
<__main__.Car object at 0x7bd1cb5cc880>
Whenever call the `print()` function over some object then there should always be a pre-defined message linked to that object that gets printed.
Hence, printing is a behaviour in Python.
With dunder/magic methods, we can modify this behaviour.
Code
```python=
class Car:
def __init__(self, name, mileage):
self.name = name
self.mileage = mileage
# Whenever an object is printed, __str__() function is called.
def __str__(self):
return f"{self.name} -> {self.mileage}"
def __add__(self, other):
return self.mileage + other.mileage
def __lt__(self, other):
return self.mileage < other.mileage
def __call__(self):
print("I WAS CALLED")
```
Code
```python=
c1 = Car("Nexon", 12)
c2 = Car("Altroz", 15)
```
Code
```python=
print(c1)
```
> Output
Nexon -> 12
Code
```python=
c1 + c2
# Car.__add__(c1, c2)
```
> Output
27
---
title: Quiz-2
description: Quiz-2
duration: 60
card_type: quiz_card
---
# Question
What will be the output of the following code?
Code
```python=
class A:
def __init__(self, a):
self.a = a
def __add__(self, b):
if isinstance(b, int):
return A(self.a + b)
return A(self.a + b.a)
def __str__(self):
return f"{self.a}"
one = A(1)
two = A(2)
print(one + two + 100)
```
# Choices
- [x] 103
- [ ] 3
- [ ] Error
- [ ] None of the above
---
title: Inheritance
description:
duration: 1200
card_type: cue_card
---
## Inheritance
<img src="https://d2beiqkhq929f0.cloudfront.net/public_assets/assets/000/015/907/original/Screenshot_2022-10-07_at_10.04.40_AM.png?1665117262">
\
Code
```python=
class Parent:
def __init__(self):
print("parent class")
class Child(Parent): # inheriting parent class
def __init__(self):
print("child class")
```
Code
```python=
class SchoolMember:
def __init__(self, name):
self.name = name
class Student(SchoolMember):
def __init__(self, name, grade):
self.grade = grade
class Staff(SchoolMember):
def __init__(self, name, salary):
self.salary = salary
class Teacher(Staff):
def __init__(self, name, salary, subject):
self.subject = subject
```
<img src="https://d2beiqkhq929f0.cloudfront.net/public_assets/assets/000/015/908/original/Screenshot_2022-10-07_at_10.05.56_AM.png?1665117338">
\
Code
```python=
s1 = Student("Krishna", "A")
type(s1)
```
> Output
__main__.Student
Code
```python=
s1.grade
```
> Output
'A'
Code
```python=
s1.name # class inherited but still can't use the properties
```
> Output
---------------------------------------------------------------------------
AttributeError Traceback (most recent call last)
<ipython-input-17-58cbae705711> in <cell line: 1>()
----> 1 s1.name # class inherited still cannot use the properties
AttributeError: 'Student' object has no attribute 'name'
#### The problem is that we never called the `__init__()` function of the parent class inside the child class.
#### We can use the `super()` method to directly call the methods of parent class.
Code
```python=
class SchoolMember:
def __init__(self, name):
self.name = name
class Student(SchoolMember):
def __init__(self, name, grade):
super().__init__(name)
# super() automatically corresponds to the direct parent class
self.grade = grade
class Staff(SchoolMember):
def __init__(self, name, salary):
super().__init__(name)
self.salary = salary
class Teacher(Staff):
def __init__(self, name, salary, subject):
super().__init__(name, salary)
self.subject = subject
```
Code
```python=
s1 = Student("Sharath", "A")
print(s1.name)
```
> Output
Sharath
Code
```python=
s1.name = "Random"
print(s1.name)
```
> Output
Random
Code
```python=
t1 = Teacher("Bipin", 10, "Python")
t1.name, t1.salary, t1.subject
```
> Output
('Bipin', 10, 'Python')
---
title: Quiz-3
description: Quiz-3
duration: 60
card_type: quiz_card
---
# Question
What would be the output of the following?
Code
```python=
class A:
def do_something(self):
print("I do something!", end=" ")
class B(A):
def do_something(self):
super().do_something()
print("I also do something!", end=" ")
b = B()
b.do_something()
```
# Choices
- [ ] I also do something!
- [ ] I also do something! I do something!
- [x] I do something! I also do something!
---
title: Need of private properties
description:
duration: 600
card_type: cue_card
---
## Need of private properties -
Code
```python=
class BankAccount:
def __init__(self, balance):
self.balance = balance
def withdrawl(self, amount):
self.balance -= amount
def deposit(self, amount):
self.balance += amount
```
Code
```python=
b1 = BankAccount(10000)
```
Code
```python=
b1.deposit(5000)
b1.balance
```
> Output
15000
Code
```python=
b1.withdrawl(15000)
b1.balance
```
> Output
0
Code
```python=
b1.balance = 23456789854323456789987654345678909876543
b1.balance
```
> Output
23456789854323456789987654345678909876543
Just like that, any property within the instance can be modified to anything.
---
title: Break & Doubt Resolution
description:
duration: 600
card_type: cue_card
---
### Break & Doubt Resolution
`Instructor Note:`
* Kindly take this time (up to 5-10 mins) to give a short break to the learners.
* Meanwhile, you can ask the them to share their doubts (if any) regarding the topics covered so far.
---
title: Private Properties
description:
duration: 1200
card_type: cue_card
---
## Private Properties
- will be accessible within the class.
- won't be accessible outside the class definition.
Code
```python=
class BankAccount:
def __init__(self, balance):
# initializing private property
self.__balance = balance
# Setter
def withdraw(self, amount):
self.__balance -= amount
def deposit(self, amount):
self.__balance += amount
# Getter
def check_balance(self):
return self.__balance
```
Code
```python=
b1 = BankAccount(10000)
# cannot be accessed like this
b1.__balance
```
> Output
---------------------------------------------------------------------------
AttributeError Traceback (most recent call last)
<ipython-input-34-88d3b8138d4e> in <cell line: 2>()
1 # cannot be accessed like this
----> 2 b1.__balance
AttributeError: 'BankAccount' object has no attribute '__balance'
Code
```python=
b1.deposit(10000)
b1.withdraw(5000)
b1.check_balance()
```
> Output
25000
#### Nothing can be completely *private* in Python.
When we create variables like `__private_var`, Python internally creates it with name of `_ClassName__private_var`.
Hence, we can still access it with the above name.
<img src="https://d2beiqkhq929f0.cloudfront.net/public_assets/assets/000/015/909/original/Screenshot_2022-10-07_at_10.18.54_AM.png?1665118116">
\
Code
```python=
b1._BankAccount__balance = 159854568765456789876545678765678
# Name Mangling
b1.check_balance()
```
> Output
159854568765456789876545678765678
Code
```python=
# Checking the properties and methods within any object -
dir(b1)
```
> Output
['_BankAccount__balance',
'__class__',
'__delattr__',
'__dict__',
'__dir__',
'__doc__',
'__eq__',
'__format__',
'__ge__',
'__getattribute__',
'__gt__',
'__hash__',
'__init__',
'__init_subclass__',
'__le__',
'__lt__',
'__module__',
'__ne__',
'__new__',
'__reduce__',
'__reduce_ex__',
'__repr__',
'__setattr__',
'__sizeof__',
'__str__',
'__subclasshook__',
'__weakref__',
'check_balance',
'deposit',
'withdraw']
Code
```python=
b1.__balance = "random balance"
dir(b1)
```
> Output
['_BankAccount__balance',
'__balance',
'__class__',
'__delattr__',
'__dict__',
'__dir__',
'__doc__',
'__eq__',
'__format__',
'__ge__',
'__getattribute__',
'__gt__',
'__hash__',
'__init__',
'__init_subclass__',
'__le__',
'__lt__',
'__module__',
'__ne__',
'__new__',
'__reduce__',
'__reduce_ex__',
'__repr__',
'__setattr__',
'__sizeof__',
'__str__',
'__subclasshook__',
'__weakref__',
'check_balance',
'deposit',
'withdraw']
---
title: Multiple Inheritance
description:
duration: 900
card_type: cue_card
---
## Multiple Inheritance
- Inheriting more than one class.
Code
```python=
class A:
def __init__(self, a):
self.a = a
class B:
def __init__(self, b):
self.b = b
# C inherits from both A and B
class C(A, B):
def __init__(self, a, b, c):
# Assumption 1
# super().__init__(a, b)
# Assumption 2
# super().__init__(a)
# super().__init__(b)
# Assumption 3
A.__init__(self, a)
B.__init__(self, b)
self.c = c
```
Code
```python=
c1 = C(1,2,3)
c1.c
```
> Output
3
---
title: Method Resolution Order
description:
duration: 1200
card_type: cue_card
---
## Method Resolution Order
<img src="https://d2beiqkhq929f0.cloudfront.net/public_assets/assets/000/015/910/original/Screenshot_2022-10-07_at_10.31.02_AM.png?1665118892">
\
Code
```python=
class A:
x = 10
class B(A):
pass
class C(B):
pass
class D(A):
x = 5
class E(C, D):
pass
# E -> C -> B -> D -> A
```
Code
```python=
e1 = E()
e1.x
```
> Output
5
Code
```python=
# It'll give the method resolution order
E.__mro__
```
> Output
(__main__.E, __main__.C, __main__.B, __main__.D, __main__.A, object)
---
title: Quiz-4
description: Quiz-4
duration: 60
card_type: quiz_card
---
# Question
Select the correct Method Resolution Order for class `E`.
Code
```python=
class A:
pass
class B:
pass
class C(A, B):
pass
class D:
pass
class E(C, D):
pass
```
# Choices
- [x] E -> C -> A -> B -> D
- [ ] E -> D -> C -> B -> A
- [ ] E -> A -> B -> D -> C
- [ ] None of the above
---
title: Practice Coding Question(s)
description:
duration: 600
card_type: cue_card
---
### Practice Coding Question(s)
You can pick the following question and solve it during the lecture itself.
This will help the learners to get familiar with the problem solving process and motivate them to solve the assignments.
<span style="background-color: pink;">Make sure to start the doubt session before you start solving the question.</span>
Q. https://www.scaler.com/hire/test/problem/96310/