#A parent/super classclass Animal:#__init__ = 'Dunder method', it must always have the argument 'self'def __init__(self,legs,prey):#Attributes#Protected attributeself._legs = legs#Private attributeself.__prey = prey#Methods.Difference between methods and functions are..#That a function can be called independently#But a method cannot be called if a class has not been made#They are also called using dot notationdef sound(self):print('Generic sound')#INHERITANCE#Below is a subclass(or child class)class Dog(Animal):#A subclass 'inherits' the attributes/methods of its parent class, and can add its own unique attributes/methodsdef __init__(self,legs,prey,breed):#Use the 'super().__init__' method to inherit the same attributes as in the parent classsuper().__init__(legs,prey)#Unique attributeself.breed = breed#Class method, A type of method where the class itself is called, so you dont need to create an object to call it@classmethod#As you can see its a decorator, so its called the same way decorators are#Below is an example of 'Method overriding'#The same method from the supercalss is used but altered and therefore completely overriden#Method overriding is an example of Polymorphismdef sound(cls):print('Woof')#Another type of method is staticmethod, its also a decorator so its called using @staticmethod before the method itself#One difference between class method and a static methoed is that static method doesnt need the additional arguments like a class method('cls')#Another difference is that a staticmethod doesnt modify or access the attributes of a class#And unlike a regular method it can be called even though no object has been created#This is an objectwild_dog = Dog(4,'rabbit','Pug')#You can access the objects attributes as suchprint(wild_dog._legs)#Since 'prey' is a private attribute, it cannot be accessed outside a class unless you use 'name mangling'print(wild_dog._Animal__prey)#Notice how you dont need to call the objects name when using a class methodDog.sound()