Let's consider the case of a simple Java class Square.
A Square, which is a Shape, is defined by its position & side length.
abstract class Shape {
abstract int area();
abstract int perimeter();
}
public class Square extends Shape {
private int x;
private int y,
private int side;
public Square(int x, int y, int side) {
this.x = x;
this.y = y;
this.side = side;
}
public Square(int x1, int y1,int x2,int y2) {
if(Math.abs(x2 - x1) != Math.abs(y2 - y1) ) {
throw new IllegalArgumentException("Square must have equal sides");
}
x = x1;
y = y1;
side = (x2 - x1);
}
public int area() {
return side * side;
}
public int perimeter() {
return 4 * side;
}
public String toString() {
return String.format("Square[x=%d,y=%d,side=%d", x, y, side);
}
}
Since this article is intended for Java developers, I am not going to explain it in detail, but feel free to ask doubts in the comments.
Now, we are going convert it step by step into Python.
class Square : Shape
pass #empty blocks are not allowed in Python
Constructor (not Constructors)
Since declaration in Python, is indistinguishable from initialization, we declare the fields in the only constructor.
def __init__(self, x, y, side):
self.x = x
self.y = y
self.side = side
To create a Square instance, no new keyword is used.
The 2nd constructor having 4 parameters cannot be implemented using __init__, since Python does not support overloading neither for constructors nor methods.
You'll see how to implement it using @classmethod later.
s1 = Square(1, 3, 10)
s2 = Square(x=1, y=3, side=10)
Encapsulation
Python does not enforce strict encapsulation as does Java via public,protected,private keywords. By convention, members beginning with _ (underscore) are private in Python.
The explicit (not implicit) argument
You can notice an extra 1st argument self in the __init__.
You have probably guessed that it's analogous to this in Java.
Except that self is not a keyword & that the 1st argument explicitly refers to the current instance.
The same holds true for the instance methods. The 1st argument refers to the current instance. You cannot refer to fields or other methods in the same class without it.
def area(self):
return self.side * self.side
def perimeter(self):
return 4 * self.side
String representation
Similar to toString in Java, there are the __repr__ & __str__ methods, which get called when you use the str function. Unlike toString, the str function is not implicitly called during concatenation.
def __str__(self):
return "Square [side=" + str(self.side) + "]"
def __repr__(self):
return "Square(side="+str(self.side)+")"
sq = Square(5)
print(sq) # prints Square [side=5]
print(sq + ".") # error. cannot concatenate string & Square
print(str(sq) + ".") # prints Square [side=5].
| __repr__ method | __str__ method |
|---|---|
| returns the constructor call expression | returns string representation |
has less priority during str call |
has more priority during str call |
Static & Class methods (they're different)
In Java, class method & static method refer to the same concept, whereas in Python they are quite different.
Consider a method that for a given area, calculates the length of a side. Such a method would be static as it does not depend on the current square instance.
public static double calcSideLength(area) {
return Math.sqrt(area);
}
@staticmethod
def calc_side_length(area):
return math.sqrt(area) # math is a module not a class
A static method does not have the 1st self argument, as it's independent of the instance. Here staticmethod is a decorator i.e. a function that returns another function for function transformation.
We can implement the 2nd constructor having 4 arguments, using another decorator @classmethod.
@classmethod
def make_square_from_points(cls, x1, x2, y1, y2):
return cls(x1, x2, x1 - x2) # cls is the constructor
A class method receives the class constructor itself (cls) as it's 1st argument. Hence, it's useful for creating factory methods.
Let's check if the points passed to this method, really forms a square.
@class method
def make_square_from_points(cls, x1, y1, x2, y2):
if x2 - x1 == y2 - y1:
return cls(x1, x2, x1 - x2)
else:
raise ValueError("The points do not form a square")
Exceptions
No prizes for guessing that we are "throwing an Exception", i.e. "raising an Error" here. We can call make_square_from_points as:
try:
sq = Square.make_square_from_points(4, 5, 7, 11)
except ValueError as ve:
print(ve)
Output
ValueError: (4, 5, 7, 11)
We are going to see how to create child classes in Python. Let's create our own Error MalformedSquareException by inheriting from ValueError. (I haven't forgotton the Shape class, but I'm saving it for later.)
class MalformedSquareException(ValueError):
def __init__(self, x1, y1, x2, y2):
self.x1 = x1
self.x2 = x2
self.y1 = y1
self.y2 = y2
# Note the difference wrt __str__
def __repr__(self):
return "MalformedSquareException(%d, %d, %d, %d)" % (self.x1, self.y1, self.x2, self.y2)
def __str__(self):
return "The points (%d, %d) and (%d, %d) are not the opposite corners of a square" %(self.x1, self.y1, self.x2, self.y2)
try:
sq = Square.make_square_from_points(4, 7, 5, 11)
except MalformedSquareException as ve:
print(ve)
Output
The points (4, 5) and (7, 11) are not the opposite corners of a square
Abstract classes
Coming back to our Shape abstract, it can be written in Python as follows:
import abc
# alternative:
# class Shape(abc.ABC)
class Shape(metaclass=abc.ABCMeta):
@abc.abstractmethod
def area(self):
... # Python abstract classes require a body
@abc.abstractmethod
def perimeter(self):
...
Python abstract classes are created either by
- declaring
ABCMetaas a metaclass or - subclassing
ABC(whose metaclass isABCMeta)
As in Java, Python abstract classes can have constructors & non-abstract methods. In addition,
Python abstract classes can have static & class abstract methods.
Surprised ? Consider the method nb_sides in Shape. It's abstract as it depends on the implementation. However, for a given Shape class it's constant.
class Shape(metaclass= abc.ABCMeta):
@staticmethod
@abc.abstractmethod
def nb_sides():
...
class Square(Shape):
@staticmethod
def nb_sides():
return 4
Note that @staticmethod must preceed @abc.abstractmethod.
Operator Overloading
Yes, Python supports operator overloading. We can implement methods such as __add__, __mul__, __lt__, __gt__, __not__ etc and use their respective symbols to call them. In contrast, the only case of operator overloading in Java is using + for String contactenation.
class Square(metaclass=abc.ABCMeta):
# sq2 = sq1 * 3 creates sq2 that's 5 times bigger than sq1
def __mul__(self, n):
return Square(self.x, self.y, self.side * math.sqrt(n)
def __lt__(self, s):
return self.side < s.side
def __gt__(self, s):
return self.side > s.side
s1 = Square(0,0, 10)
s2 = s1 * 3 # __mul__
print(s1 < s2) # __lt__ is called
Multiple Inheritance
Now we've come to the meat of the matter. Python supports multiple inheritance, unlike Java which supports only single inheritance.
This naturally leads to the question: When a class has multiple ancestors, which one's members have the higher priority ?
- Immediate ancestors' members have higher priority than those farther up. (As in Java)
- Within ancestors of the same hierarchy, the class declarations, parent class order determines the priority (left -> higher, right -> lower)
Eg: Consider the following scenario:
I've introduced 2 new classes Rectangle & Rhombus which both inherit the abstract class Shape. And the Square now inherits both Rectangle & Rhombus.
class Rectangle(Shape):
def __init__(self, x, y ,length, breadth):
self.x, self.y = x,y
self.length, self.breadth = length, breadth
def area(self):
return self.length * self.breadth
def perimter(self):
return 2 * (self.length + self.breadth)
@staticmethod
def nb_sides():
return 4
class Rhombus(Shape):
def __init__(self, x, y, side, angle):
self.x, self.y = x,y
self.side, self.angle = side, angle
def area(self):
return self.side * self.side * math.sin(self.angle * math.pi/180)
def perimeter(self):
return 4 * self.side
@staticmethod
@abc.abstractmethod
def nb_sides():
return 4
class Square(Rhombus, Rectangle):
def __init__(self, side):
super().__init__(self, side, math.pi/2)
def area():
return super().area()
@staticmethod
def nb_sides():
return 4
There are a number of things to note here:
- The
supercall does not call the parent constructor, though it still references the parent object. Hence the need to call__init__again. - Since the declaration of
SquarecontainsRhombusbeforeRectangle(thoughRectangleis declared beforeRhombusin the code),super()referencesRhombusnotRectangle. - Though both
Rhombus&Rectanglehave implemented the static methodnb_sides,Squarestill needs to implement it for itself. Failure to do so, will result in an error. - The explicit call of the parent constructor via
__init__neededselfas an argument, while the call of the parentarea()method did not need it.
To know the method resolution order (ascending) of a given object using the mro method. Eg:
sq = Square(0,0,5)
print(sq.mro())
#[<class '__main__.Shape'>, <class '__main__.Rectangle'>, <class '__main__.Rhombus'>]
# higher priority is towards the right
Conclusion.
There are other advanced topics like :
- how to create an interface in Python (roundabout way) &
- how to change the default inheritance order But I've skipped them for now.
Here's a summary of the differences in Object Oriented Programming between Java & Python, as seen in this article.
| Java | Python |
|---|---|
| Strict encapsulation | Lenient encapsulation |
| Overloading of constructors & methods | No overloading |
implicit this keyword argument |
method's explicit 1st argument |
| static members | static & class methods (different) |
toString |
__repr__ and __str__
|
| Almost no operator overloading | Operator overloading |
| Abstract methods cannot be static | Abstract methods can be class or static |
| Single inheritance | Multiple inheritance |
The Java icon in the cover image is from Java icons created by Freepik - Flaticon.



Top comments (0)