Object Oriented Programming with Python
Python programming language supports the object-oriented programming (OOP) paradigm by introducing the 'Class' concept. If you are familiar with OOP from other programming languages like Java, C++ etc., then you already know the meaning of a class. If not, this short lesson gets you up to speed.
What is a class?
A 'class' is the fundamental building block to create objects of a specific type. A class, in its simplest form is defined with class keyword and the name for the class. Once defined a class acts as a template using which you can create object instances.
For every new type of an object you need, first you have to create a class of that type. Using that class, you can then create instances of that type. To hold the state of every instance, attributes can be added to the class definition. In that case every instance of the class carries a copy of all the attributes that can be set to any value to denote the state of the instance. You can also have methods defined in the class that can be used by the instance to modify its state.
The simplest class is defined below:
class MyClass:
pass
At the very least the class should have a name. The naming rules are same as the variable naming but from the naming convention perspective a class name should start with an upper case letter and multiple words are camel-cased.
Now let us take a look at a class with one variable and one function:
class MyAwesomeClass:
a = 10
def greet():
print("hi there");
You can access the variable 'a' and function 'greet()' inside the class object using the the 'dot' operator as shown below:
MyAwesomeClass.a
MyAwesomeClass.greet()
Points to note
- Attributes defined inside the class are local to the class
- Any attribute of the class can be accessed outside of the class, through the dot operator
- Variables and functions are also collectively called attributes
- A function definition inside a class is referred to as a method
Class Instantiation
While you can directly reference the variables inside the Class object using the dot operator, you can also create an instance object of the class using a pair of parenthesis as shown below:
a = MyAwesomeClass()
Understanding 'self' parameter
However, if you now call the a.greet() method on 'a', you will notice that it will raise an Error. Why is this?
This is because the method call a.greet() is automatically translated by Python into MyAwesomeClass.greet(a). And since such a method that takes an argument is not defined, it raises the Error. To fix that, we need to add an argument to the greet method that receives the instance object:
class MyAwesomeClass:
a = 10
def greet(self):
print("hi there");
x = MyAwesomeClass()
x.greet()
In the above code, you see that the greet method is defined with a parameter called 'self'. Although you can give any name for this parameter, it is an accepted practice to define the first parameter as self, which denotes the instance object.
Understanding the init constructor
If you need to create objects with instances having a specific initial state, you can define a special method named init Defining init() method is optional. If defined, however, then the class instantiation automatically invokes init() while creating an instance.
class Country:
def __init__(self):
self.default = 'USA'
x = Country()
print(x.default)
x.default = "India"
print(x.default)
A class can only have one init method defined. If you define multiple __init__ methods, the last definition will overwrite the previous ones.
However you can define variable arguments as the second parameter there by getting a overloaded constructor (similar to Java constructors for e.g.) type of a functionality.
Here is an example of :
class Person:
def __init__(self, first, last):
self.first_name = first
self.last_name = last
x = Person('John', "Doe")
print(x.first_name, x.last_name)
Output:
John Doe
When you instantiate this class, the init method is invoked by passing in the necessary values for the 'first' and 'last' parameters. Note that along with the custom parameters, self is also defined but is not explicitly set while instantiation.
Creating data attributes on the fly
The variables that hold data are data attributes. Just like any top level Python program, you are not required to declare any data attributes. You can simply assign and use them.
For example on the instance of the Country class shown as example above, you can add continent to the instance object as shown below:
x.continent = 'America'
Note, all that you have to do to add a new variable to the instance 'x' is just use the namespace 'x' and then add the variable with the dot operator
Method invocation
You have already invoked a few methods on the instance objects. You can also save the method definition to be used later with invocation as shown below:
class Country:
def __init__(self):
self.default = 'USA'
def print(self):
print(self.continent)
x = Country()
x.continent = 'America'
print(x.continent)
y = x.print
y()
While data attributes can be added on the fly, methods cannot be added on the fly. Note, how we first saved the method definition in 'y' and then in the next statement we invoked the method by adding the () parenthesis to 'y' This 'y' is called method object.
In the above program, if the 'continent' attribute is not set, then it will throw an error "AttributeError: 'Country' object has no attribute 'continent'" To ensure that the attribute is only called if present, you can modify the program as shown below:
class Country:
def __init__(self):
self.default = 'USA'
def print(self):
if(hasattr(self, 'continent')):
print(self.continent)
x = Country()
x.continent = "America"
y = x.print
y()
Understanding instance and class variables
Variables that are outside of all methods are class variables. Variables that are declared inside a method are local to that method. Variables that are declared by prepending 'self' are instance variables inside a class.
class Country:
continent = 'America'
states = []
def __init__(self, name):
self.name = name
def print_name(self):
print(self.name)
def print_continent(self):
print(self.continent)
def add_state(self, name):
self.states.append(name)
def print_states(self):
print(self.states)
usa = Country('USA')
usa.print_name()
india = Country("India")
india.print_name()
print('Before changing the class variable the value of continent in india: ', india.continent)
india.continent = 'Asia'
print('Continent value in usa is unchanged:')
usa.print_continent()
print('Continent value in india carries the new value:')
india.print_continent()
print('Continent in class object is still unchanged', Country.continent)
# Let us now add some states
usa.add_state('michigan')
india.print_states() # adding the state in usa is reflected in india! Both instances share the same object!
Points to note
- Variables declared outside of all methods are 'class' variables
- Mutable class attributes (like lists or dictionaries) are shared across all instances. Modifying the attribute through one instance will affect all other instances.
- When you assign a new value to a class attribute on an instance (e.g.,
india.continent = 'Asia'), you are creating an instance attribute that shadows the class attribute. The original class attribute remains unchanged, and other instances are not affected.
For more advanced class features like resource management and the with statement, see Context Managers.
There are a few more variation in the way you can declare variables. Please check the references below:
References: