# 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/