PROGRAMMING

Object-Oriented Programming for Python Beginners — Part 3

Naser Tamimi
4 min readNov 19, 2019

This is Part 3 of my Object-Oriented Programming for Python Beginners. If you are interested, please read Part 1 and Part 2 first.

In the last part, you learned about @classmethod which is a very useful decorator for constructing a new instance/object from a class. There are many popular decorators that you can use in your object-oriented programming. Let’s take a look at a few of them.

Here is our famous Circle class that we started building since Part 1.

class Circle:
def __init__(self, x=0.0, y=0.0, r=1.0, label=None):
self.label = label
self.center = (x, y)
self.radius = r
self.area = np.pi * self.radius**2

I want to add two simple methods to this class. The first method double_radius() simply multiplies the current radius by 2. The other method that I am interested in is triple_radius() and as you guess, it multiples radius by 3.

class Circle:
def __init__(self, x=0.0, y=0.0, r=1.0, label=None):
self.label = label
self.center = (x, y)
self.radius = r
self.area = np.pi * self.radius**2

def double_radius(self):
self.radius *= 2

def triple_radius(self):
self.radius *= 3

Awesome, now we test our methods.

circ = Circle(0.0, 0.0, 2.0, label='my_circle')
print(circ.radius)
circ.double_radius()
print(circ.radius)
circ.triple_radius()
print(circ.radius)

If you run the test, you will get 2.0, 4.0, and 12.0. Basically, our initial radius is 2.0, after running double_radius() method, the new radius becomes 4.0 and eventually with running the triple_radius() method, the radius becomes 12.0. Everything is according to our expectations. But, let's take a look at area attribute.

circ = Circle(0.0, 0.0, 2.0, label='my_circle')
print(circ.area)
circ.double_radius()
print(circ.area)
circ.triple_radius()
print(circ.area)

What we get from this test is 12.56637…, 12.56637…, and 12.56637…. WHAT??!!! Why my area attribute does not change?! The reason is that the area attribute only get calculated when the object is being constructed at __init__. To update your area, you must add self.area = np.pi * self.radius**2 to both double_radius() and triple_radius() methods. Something like this.

class Circle:
def __init__(self, x=0.0, y=0.0, r=1.0, label=None):
self.label = label
self.center = (x, y)
self.radius = r
self.area = np.pi * self.radius**2

def double_radius(self):
self.radius *= 2
self.area = np.pi * self.radius**2

def triple_radius(self):
self.radius *= 3
self.area = np.pi * self.radius**2
circ = Circle(0.0, 0.0, 2.0, label='my_circle')
print(circ.area)
circ.double_radius()
print(circ.area)
circ.triple_radius()
print(circ.area)

Now area attributes get updated after each method. But if you are a good programmer, you should know that repeating means there is a better way.

How can I avoid repeating here? If instead of area attribute, you had area method, then you did not get area updated every time you change your radius. Therefore, it is better to define the area as a method not attribute. Let's do it.

class Circle:
def __init__(self, x=0.0, y=0.0, r=1.0, label=None):
self.label = label
self.center = (x, y)
self.radius = r

def area(self):
return np.pi * self.radius**2

def double_radius(self):
self.radius *= 2

def triple_radius(self):
self.radius *= 3
circ = Circle(0.0, 0.0, 2.0, label='my_circle')
print(circ.area())
circ.double_radius()
print(circ.area())
circ.triple_radius()
print(circ.area())

Woo-hoo!!! It works!!! But honestly, I don’t like it. The area is an attribute and property of a circle, not a method or an action. I don’t really like () that we put after the area. I wish there was a better way.

THERE IS A BETTER WAY. If you use @property decorator before your method, your method becomes like an attribute.

class Circle:
def __init__(self, x=0.0, y=0.0, r=1.0, label=None):
self.label = label
self.center = (x, y)
self.radius = r

@property
def area(self):
return np.pi * self.radius**2

def double_radius(self):
self.radius *= 2

def triple_radius(self):
self.radius *= 3
circ = Circle(0.0, 0.0, 2.0, label='my_circle')
print(circ.area)
circ.double_radius()
print(circ.area)
circ.triple_radius()
print(circ.area)

As you see, now we have a method for calculating area, but with @property it seems like an attribute.

Now, let’s assume I want to change my object area and I expect the radius will change accordingly. Let me tell you, it is not as simple as circ.area = some value. You must user setter decorator. See the example here.

class Circle:
def __init__(self, x=0.0, y=0.0, r=1.0, label=None):
self.label = label
self.center = (x, y)
self.radius = r

@property
def area(self):
return np.pi * self.radius**2

@area.setter
def area(self, value):
self.radius = np.sqrt(value/np.pi)

def double_radius(self):
self.radius *= 2

def triple_radius(self):
self.radius *= 3
circ = Circle(0.0, 0.0, 2.0, label='my_circle')
print(circ.area)
print(circ.radius)
circ.area = 10print(circ.area)
print(circ.radius)

As you see, for our area property, we are defining a setter using @area.setter. Inside this function, we simply calculate the radius that gives us the desired area. Now, every time the user set the area to a specific number both radius and area get updated.

Our Circle class is being very popular and it can do many things related to Circle objects. But this class also can be used for a few other related things. For example, one might like to know how much is 125 degrees in radians. It is good that we have a function in our Circle class that does it for them. But the problem is this function has nothing to do with our object. Honestly, it is a separate function. We call these functions, static functions. To show a function is static in our class, we must use a specific decorator called @staticmethod.

import numpy as npclass Circle:
def __init__(self, x=0.0, y=0.0, r=1.0, label=None):
self.label = label
self.center = (x, y)
self.radius = r

@property
def area(self):
return np.pi * self.radius**2

@staticmethod
def to_radians(angle):
return angle/180*np.pi
print(Circle.to_radians(55))

to_radians() is a static method. In this example, it is a part of the class Circle, but it does not do anything on the object/instance (that’s why it does not need self as an input). We used this static method, to convert 55 degrees to radians here.

Today we learned two important decorators in OOP: @property and @staticmethod. As you can see, possibilities are unlimited in OOP and decorators even make it more interesting.

--

--

Naser Tamimi
Naser Tamimi

Written by Naser Tamimi

Data Engineer @ Expedia | Ex-Meta, Ex-Shell

Responses (1)