250 likes | 421 Views
Learning to Program With Python. Advanced Class 6. Topics. Inheritance Class methods / static methods Class data vs. instance data. Inheritance. Inheritance is one of the essential features of object-oriented programming.
E N D
Learning to ProgramWith Python Advanced Class 6
Topics • Inheritance • Class methods / static methods • Class data vs. instance data
Inheritance • Inheritance is one of the essential features of object-oriented programming. • After you’ve defined one class, you can define other classes to inherit the methods and properties of that parent class.
Inheritance • This way, if you need multiple classes that largely overlap in functionality and differ in relatively small ways, you only have to write the central part once. • The other classes can inherit the central functionality, and then add on whatever else they need.
Inheritance syntax #the class we’re inheriting from ↓ class ChildClassName(ParentClassName): def __init__(self): #and so on…nothing else changes here. • A synonymous term for “child class” is “subclass”, and a synonymous term for “parent class” is “superclass.”
Inheritance examples • Imagine you were writing a video game with different kinds of monsters for the player to fight. • You could create a base Monster class, and have each specific monster inherit from it and differ in whatever ways make that monster unique.
Inheritance examples #some arbitrary default attributes for a Monster class Monster: def __init__(self): self.health = 100 self.attack = 20 self.defense = 20 self.speed = 10 self.accuracy = .50 self.magic_resist = .30
Inheritance examples #Our first specific Monster. #At first we’re not going to change anything. class Banshee(Monster): pass • Recall that the keyword ‘pass’ indicates ‘nothing happens here’ . . . it is functionally equivalent to blank space, but there has to be something in the class definition, so we put ‘pass’ there to meet that requirement.
Inheritance examples • Having thus defined the Banshee class, we can now use it. >>> my_first_banshee = Banshee() >>> my_first_banshee.attack 20 >>> • Why does it have an attack attribute?? We didn’t define that in the Banshee class…
Inherited methods • Although we didn’t define an __init__ method for the Banshee class, it already had one– the one it inherited from the Monster class. • We could override the inherited method by redefining it in the subclass Banshee, and then it would use that __init__ method instead of the inherited one. • But as we wrote it, it simply defaults to Monster’s __init__.
Inheritance examples • Now let’s actually make the Banshee unique. class Banshee(Monster): def__init__(self): super().__init__() self.attack = 25 self.defense = 10 self.accuracy = 1
About Banshee’s __init__ • Now that we’ve defined an __init__ method in Banshee, the inherited one will be ignored. • Within the Banshee’s __init__ method, however, we wrote super().__init__() • That line calls the __init__ method of the superclass, Monster, thereby setting up all the attributes that we had previously defined.
About Banshee’s __init__ • So after calling super().__init__() inside of Banshee’s __init__, the attributes health, attack, defense, speed, accuracy, and magic_resist are all created on our new Banshee instance. That’s all done by Monster’s __init__ method. • After that, Banshee’s __init__ method goes on to modify those attributes according to however we want the Banshee to be different.
About Banshee’s __init__ • So in this case, we went ahead and increased Banshee’s attack, reduced its defense, and gave it perfect accuracy, since it’s more or less impossible to dodge a scream. • We wanted to leave the other attributes as they were, so we did nothing else. • We only had to specify what made Banshee different from the default Monster. The superclass’s __init__ did the rest.
Inheritance examples #Another specific monster for our game. class Dragon(Monster): def __init__(self): super().__init__() self.attack = 200 self.defense = 200 self.health = 1000
About Dragon’s __init__ • Just like the Banshee class, Dragon inherits from Monster. Which means when we create an instance of the Dragon class, all those default attributes are created. • We then customized the Dragon __init__ method to give the Dragon quite a lot of attack, defense, and health. • Again, we only had to specify how it was different from our base Monster.
The power of inheritance • This is a very simple example, where inheritance only saved us from having to specify a handful of additional attributes for each new Monster. • But it demonstrates the basic principle:inheritance saves us from having to redefine methods and attributes that we want to have in multiple classes.
The power of inheritance • We may or may not ever actually use the Monster class by itself. We could of course write: >>> jimbob = Monster() • And now we have an instance of the base Monster class, with all the attributes defined accordingly.
The power of inheritance • We may or may not ever need to use it that way. • Possibly we created the Monster class simply to serve as a mold on which to base all the other classes we write . . . and as we saw, it serves its purpose well. • All redundancy was eliminated from the code.
Accessing inherited methods • From the outside, nothing is different. There’s no way to tell if a method is inherited or not. • Either way, you write the same thing. • Let’s imagine we had defined an attack method in the Monster class, which takes one argument, the monster we’re attacking. Dragon and Banshee would have inherited this method.
Accessing inherited methods • I’ve made a Monster and a Dragon. The attack method is only defined for Monster, but Dragon inherits it, so both classes have access to it. >>> my_monster= Monster() >>> my_dragon= Dragon() >>> my_monster.attack(my_dragon) >>> my_dragon.attack(my_monster)
Accessing inherited methodsfrom inside the subclass • In a previous example, we used super().__init__() to invoke the functionality of the superclass’s __init__ method. • But this is only necessary if you want to specify that you’re calling the superclass’s implementation of the method as opposed to the subclass’s once the method has been redefined, as in the case of __init__.
Accessing inherited methods from inside the subclass • Just as you can simply call the inherited method on the class instance from the outside without doing anything special, you can simply call the inherited method from the inside without doing anything special. • Just treat it like any other method and write self.methodName(arguments).
Inherited methods • So if the Dragon class had a method attackEveryone(), it might call self.attack() on every monster in the vicinity, which is okay because it inherited the attack() method from its parent class Monster. class Dragon: #def __init__ etc defattackEveryone(nearbyMonsters): for m in nearbyMonsters: self.attack(m)
Overriding methods • We’ve already done this– we did it with the __init__ method. But you can do it with any inherited method. • If you want a subclass to have a different implementation of an inherited method, you simply redefine it inside the subclass however you want it to be, and it overrides the method. • When you call the method on the subclass, it will call the subclass’s implementation of the method, not the inherited version.