OOP is an approach for modeling concrete, real-world things as well as relations between them. It allows programmers to create their own real-world data types and it is based on classes, objects, attributes, and methods. In object-oriented programming, we write classes that represent real-world things and situations, and we create objects based on these classes.
A class is a blueprint or a factory for creating objects and in python, everything is based on an object. Classes provide a means of bundling data and functionality together. An object is a unique instance of a data structure defined by its class. An Instance is an individual object of a certain class. The creation of an instance of the class is called instantiation.
The data values which we store inside an object are called attributes and are of two types:
- Class attributes and
- Instance attributes.
Class attributes are a variable that is defined outside the methods at the class level which are shared by all instances. The data value will be the same for all the objects but an instance attribute can overwrite it. To access the class attribute, we access it by ClassName.attributeName or objectName.attributeName
Instance attributes is a unique data or variables that are declared inside the method. It is created in the constructor (__init__( ) method) or in other instance methods. We access instance attributes using objectName.attributeName
The functions which are associated with the object are called methods or a function that you define inside the class is called a method.
How to create a class
Class definitions, like function definitions (def statements), must be executed before they have any effect. To create a class, use the keyword class.
Syntax to create a class:
class ClassName: # Define class ClassName <statement-1> . . . <statement-N>
The statements inside a class definition will usually be function definitions, but other statements are also allowed.
Use the class name to create an object.
class ClassName: # Define a class x = 10 # Declare a class variable obj = ClassName() # Creating an object print(obj.x) # Object references class attribute # Output: 10
Class objects support two kinds of operations:
- attribute references and
Attribute references use the standard syntax used for all attribute references in Python: obj. name. Valid attribute names are all the names that were in the class’s namespace when the class object was created.
class ClassName: # Define a class x = 10 obj = ClassName() # Create an object print(obj.x) # Attribute reference with object name # Output: 10
The instantiation operation (“calling” a class object) creates an empty object. Many classes like to create objects with instances customized to a specific initial state. Therefore a class may define a special method named __init__().
The __init__ method is often referred to as the “constructor”, since it is responsible for constructing new instances. All classes have a function called __init__(), which is always executed when the class is being initiated. It is used to assign values to object properties or other operations that are necessary to do when the object is being created.
class Person: def __init__(self, name, age): self.name = name self.age = age print("Name of the person:",self.name) print("Age of the person:",self.age) Person("Dawa", 25)
Name of the person: Dawa Age of the person: 25
The only operations understood by instance objects are attribute references. There are two kinds of valid attribute names; data attributes, and methods.
Data attributes correspond to “instance variables”. Data attributes need not be declared; like local variables, they spring into existence when they are first assigned.
The other kind of instance attribute reference is a method. A method is a function that “belongs to” an object.
class Person: def __init__(self, name, age): self.name = name self.age = age print("Name: ", self.name, "\nAge:", self.age) def address(self, village, gewog, dzongkhag): self.village = village self.gewog = gewog self.dzongkhag = dzongkhag print("Village: ", self.village, "\nGewog: ", self.gewog) p = Person( 'Sonam', 25) p.address('kikher','nangkhor', 'Zhemgang') # Method attribute reference print("Dzongkhag:", p.dzongkhag) # Data attribute reference
Name: Sonam Age: 25 Village: kikher Gewog: nangkhor Dzongkhag: Zhemgang
Note: The first argument of a method is called self. This is nothing more than a convention: the name self has absolutely no special meaning to Python. However, by not following the convention your code may be less readable to other Python programmers, and it is also conceivable that a class browser program might be written that relies upon such a convention.
Class variable and Instance variable
Instance variables are for unique data to each instance and class variables are for attributes and methods shared by all class instances.
class Person: location = "East" # Class variable shared by all instances def __init__(self, name): self.name = name # Instance variable unique to each instance p = Person('Dawa') q = Person('Sonam') print("Shared by all instances:", p.location) print("Shared by all instances:", q.location) print("Unique to each instances:", p.name) print("Unique to each instances:", q.name)
Shared by all instances: East Shared by all instances: East Unique to each instances: Dawa Unique to each instances: Sonam
If the same attribute name occurs in both an instance and a class, then attribute lookup prioritizes the instance.
class Person: location = "East" # Class variable shared by all instances def __init__(self,location): self.location = location # Instance variable unique to each instance p = Person("West") print(p.location)
The function definition doesn’t need to be textually enclosed in the class definition: assigning a function object to a local variable in the class will also work.
def f1(self): return 'Hello world' class Person: f = f1 # Assigning a function object to a local variable in the class location = "East" def __init__(self,location): self.location = location p = Person("West") print(p.location) print(p.f()) # Accessing a local variable of the class
West Hello world
The class inheritance mechanism allows multiple base classes. A derived (child) class can override any methods of its base (Parent) class or classes, and a method can call the method of a base class with the same name.
Execution of a derived class definition proceeds the same as for a base class. When the derived class object is constructed, the base class is remembered. This is used for resolving attribute references: if a requested attribute is not found in the derived class, the search proceeds to look in the base class. This rule is applied recursively if the base class itself is derived from some other class.
class Animal: # Base class def __init__(self, name, color): self.name = name self.color = color def eat(self): print(self.name, "is eating.") class Dog(Animal): # Derived class inherited Base class def bite(self): print(self.name,"will bite.") dog = Dog("Katu", "black") # Derived class object created dog.eat() # Base class method is called dog.bite() # Derived class method is called
Katu is eating. Katu will bite.
Python supports a form of multiple inheritances as well. A class definition with multiple base classes looks like this:
class DerivedClassName(Base1, Base2, Base3): <statement-1> . . . <statement-N>
If an attribute is not found in DerivedClassName, it is searched for in Base1, then (recursively) in the base classes of Base1, and if it was not found there, it was searched for in Base2, and so on.
Overriding methods: Polymorphism
When a method name in the child class is the same as the method name in the parent class, it will execute the child method instead of the parent method. This is known as the “overriding parent method” also known as polymorphism.
class BaseClass: def f1(self): print("Hello world from Base Class") class DerivedClass(BaseClass): def f1(self): print("Hello World from Derived Class") d = DerivedClass() d.f1()
Hello World from Derived Class
We can see that the method name f1() is the same in the derived class as well as in the base class. However, the program has executed the method in the derived class instead of the base class.
class Shape: def __init__(self, width, height): self.width = width self.height = height def area(self): print("Area of shape:", self.width*self.height) class Rectangle(Shape): def area(self): print("Area of Rectangle:", self.width*self.height) class Triangle(Shape): def area(self): print("Area of Rectangle:", (self.width*self.height)/2) r = Rectangle(10, 5) t = Triangle(5, 15) r.area() t.area()
Area of Rectangle: 50 Area of Rectangle: 37.5
Iterators and Generators
An iterator is an object which allows a programmer to loop through all the elements of the collection like a list, tuple, dictionary, etc….
It implements the iterators protocol and consists of two methods i.e. __iter__() and __next__() which also has a built-in function iter() and next() respectively. The use of iterators pervades and unifies Python. Behind the scenes, the for statement calls iter() on the container object. The function returns an iterator object that defines the method __next__() which accesses elements in the container one at a time. When there are no more elements, __next__() raises a StopIteration exception which tells the for loop to terminate.
animal = ['cat', 'dog'] n = iter(animal) print(n.__next__()) print(n.__next__()) print(n.__next__())
cat dog Traceback (most recent call last): File "E:/New folder/Bhutan Python coders/code for blog/class and instanes.py", line 6, in <module> print(n.__next__()) StopIteration
It is easy to add iterator behavior to your classes. Define an __iter__() method which returns an object with a __next__() method. If the class defines __next__(), then __iter__() can just return self.
class IterClass: def __init__(self): pass def __iter__(self): self.count = 0 return self def __next__(self): count = self.count self.count+=1 return count itr = IterClass() i = iter(itr) print(i.__next__()) print(i.__next__()) print(i.__next__())
0 1 2
Generators are a simple and powerful tool for creating iterators. They are written like regular functions but use the yield statement whenever they want to return data.
def gen(): name = 'Dawa' age = '25' yield name yield age g = gen() print(g.__next__()) print(g.__next__())