Skip to content
Carlos Iriarte

Learning Python | Methods vs Functions vs Classes

python, learning7 min read

Let's write a program that behaves like a cat. You like cats, right?

My cat likes to wake up at 4:30 am, and start meowing. Apparently it's important that everyone knows she's awake.

1print("Meow")

That's good, but my cat does many other things. And meows in many different ways depending on her mood:

I'm going to teach you (my) cat's language:

I'm hungry
1print("ฅ^•ﻌ•^ฅ Meow")
I'm wanna play!
1print("ฅ^•ﻌ•^ฅ Meoooow")
Open the door you tirant!
1print("ฅ^•ﻌ•^ฅ Meoooooooow")

Yup. I have a very vocal kitty. Looking at the examples above that code seems very similar in all the cases: it starts with a cute kaomoji and then the phrase she wants to say in her language.

So this is a program describing her morning:

1print("ฅ^•ﻌ•^ฅ Meoooooooow")
2print("ฅ^•ﻌ•^ฅ Meow")
3print("ฅ^•ﻌ•^ฅ Meoooow")

It does look a bit repetitive... and it's hard to tell what it's doing. Let's try to make it a bit better by grouping the common code in a method.

What's a method?

A method is way to call a piece of code used many times in different parts of the program.

Defining a method

1def method_name():
2 # method body

Let's stop for second and think about what "defining" means. If I ask you, what is "walking", how would you describe it? If someone asks me, I'd say "walking is moving a number of steps".

1def walking(number_of_steps):
2 print(f"moving {number_of_steps} steps")

We've "defined" our first method! You might be pondering, what is that number_of_steps doing there? Well, number of steps is something that can't be fixed. Imagine if walking was always "moving 10 steps". That would be annoying. Some days I walk 5K steps, other days 11K+ i.e. number_of_steps is an argument (or parameter) to our method. We also see the body of the method that prints a string with the number of steps we passed as an argument.

Now that we know how to define methods, let's refactor our cat program by defining a method "meow":

1def meow(number_of_os):
2 print("ฅ^•ﻌ•^ฅ Me" + "o" * number_of_os + "w")

Awesome. We now have a way to describe what a meow is! But imagine if I would describe to you what "eating an ice cream" is and that would be it? You would never really experience it. To experience things you have to actually DO them, I'll buy you an ice cream when Rona 🦠 is over.

Anyway, how do we make methods do something in python? We invoke the methods. In python, you invoke methods by writing the method_name followed by an open parenthesis "(" then arguments separated by commands and finally ")". If there's no arguments, you write "()".

1def meow(number_of_os):
2 print("ฅ^•ﻌ•^ฅ Me" + "o" * number_of_os + "w")
3
4meow(8) # ฅ^•ﻌ•^ฅ Meoooooooow or "Open the door you tirant!"
5meow(1) # ฅ^•ﻌ•^ฅ Meow or "I'm hungry"
6meow(4) # ฅ^•ﻌ•^ฅ Meow or "I'm wanna play!"

That's a bit better. But those numbers confuse me. Reading this code makes me wonder what that means. Not ideal. It would be nice if we could just describe my cat's morning routing with more English like terms. Why don't we define methods that describe what she's saying?

1def meow(number_of_os):
2 print("ฅ^•ﻌ•^ฅ Me" + "o" * number_of_os + "w")
3
4def open_the_door():
5 meow(8)
6
7def hungry():
8 meow(1)
9
10def i_wanna_play():
11 meow(4)
12
13open_the_door()
14hungry()
15i_wanna_play()

It's more code. But it also makes things a bit clearer. An important thing to notice is that we're using our meow definition to define other actions! How cool is that? But we do that in English all the time, when I defined "walking" in English I had to use other definitions (like "moving" or "number" or "steps") that someone else defined at some point in history. That's exactly what we do all the time when we program.

...I'm starting to feel bad about my other cat. I've spent a good chunk of this document talking about one of my cats, but not the other one... I'm a horrible cat dad.

My male cat is a bit different. But he communicates in the same cat language as my girly kitty. He also asks to open doors, to say he's hungry and also to express his desire to play. But he doesn't make the exact same sounds. He still meows don't get me wrong, but he has his own particular attributes. Wouldn't it be nice if we had a "template" for cats where we specify just what's different between them? Python to the rescue! Python and other programming languages have this concept of "Class" that behaves more or less like a template.

Defining a Class

If I ask you: Define a "tree", what would you tell me? If someone asks me, I'd say: it's a "plant". That's a bit different from when we defined "walking", because walking was an action an a tree is a thing or noun. We define things in English by stating attributes about it. In python, we do it similarly:

1class Tree(Plant): # a tree is a plant
2 pass

Oftentimes, when we describe things, we want to use particular examples. If I tell you a tree is a plant that would be technically correct but it doesn't give you a good idea of what a tree is until you see one.

I would probably say: "Do you see that big plant with green leaves?" that's a tree. But not all the trees have green leaves (like the cherry blossoms) or big (like the cute bonsais). "That big plant with green leaves" an instance of a tree. But the important thing is that we can describe a tree with those attributes! The caveat is that to make use of this Tree we need an actual example so we always have to create (or construct an instance).

This is how we define a Tree, and how to create or construct one.

1class Plant: # Let's ignore the details of plant for now
2 pass
3
4class Tree(Plant): # a tree is a plant
5 def __init__(self, size, color_of_leaves): # we define how to construct a tree by using a reference to the new tree, its size and the color of its leaves
6 self.size = size # a tree has a size
7 self.color_of_leaves = color_of_leaves # a tree's leaves are of a certain color
8
9
10t = Tree("big", "green") # When we do this, python is invoking __init__(self, size, color_of_leaves) for us and returning it, and we assign that value to "t"

Note that constructing (or initializing) (that __init__ method) has a reference to the object we're creating in the parameter self. We use this self argument to set the attributes that we will pass when we create a tree.

Now that we now how to define a Class (our cat template!), let's continue with our cats program. Let's define a Cat class:

1class Cat: # if you don't specify the base class, python assumes it comes from Object
2 def __init__(self, oos_when_hungry, oos_when_playful, oos_when_open_door): # the reference to the cat we're constructing (self), and the number of oos they use when meowing
3 self.oos_when_hungry = oos_when_hungry
4 self.oos_when_playful = oos_when_playful
5 self.oos_when_open_door = oos_when_open_door

Sweet. We have described what a cat is. But... unlike our tree, our cats are very talkative! What does that mean? It means cats have actions too. How do we describe actions in python? well, with methods of course! Let's ponder for a second, what does that mean? It means that things have attributes but also methods. And now that we're here, let's start calling things with Python lingo: Python calls these things Objects. This is what "Object Oriented Programming" is: A way to describe objects which are data (attributes) and behavior (methods).

Now, after that jargon... how do we refactor (reorganize code with a goal) the methods we already have? We have to follow a few steps:

  • The first thing to do is, in python, if you want something to be part of something else, indent (add a tab) the text to the right. In visual studio code, you can do this by copying and pasting the three definitions (but not the invocations) and pasting it below the Cat class. Then you can press the tab key to indent the text.

  • Let's take look at the methods we have: hungry, open_door, i_wanna_play call meow but need each specific cat's style of meowing (encoded as the number_of_oos in ther meows). So we can use the values we stored when we constructed each cat:

    1- def open_the_door():
    2+ def open_the_door(self):
    3- meow(8)
    4+ meow(self.oos_when_open_door)
    5
    6- def hungry():
    7+ def hungry(self):
    8- meow(1)
    9+ meow(self.oos_when_hungry)
    10
    11- def i_wanna_play():
    12+ def i_wanna_play(self):
    13- meow(4)
    14+ meow(self.oos_when_playful)

Red lines are before and green lines are the new changes. Please note that we're adding self to the arguments of each method definition. Because we will need to refer to the attributes from our Cat class.

  • What about the meow method? Well, the meow method is a bit different. Every domestic cat in the world meows. It's not like my cats are unique in that. Also, it only takes number_of_oos as an argument and that's not part of our Cat definition, we pass that value when we invoke it from the other methods. Such methods that are not touch any parts of self are called static methods. And we define them like this:

    1class ClassName:
    2 @staticmethod
    3 def method_name(arguments):
    4 pass

    in the specific case of our meow method:

    1class Cat: # if you don't specify the base class, python assumes it comes from Object
    2 def __init__(self, oos_when_hungry, oos_when_playful, oos_when_open_door): # the reference to the cat we're constructing (self), and the number of oos they use when meowing
    3 self.oos_when_hungry = oos_when_hungry
    4 self.oos_when_playful = oos_when_playful
    5 self.oos_when_open_door = oos_when_open_door
    6
    7 @staticmethod
    8 def meow(number_of_os):
    9 print("ฅ^•ﻌ•^ฅ Me" + "o" * number_of_os + "w")

    Please note that static methods don't use self because it doesn't need it! After these changes, our Cat class looks like this:

  • One final step, is to change the way we invoke meow in the other methods by adding the Class name (Cat in our case) when we invoke it:

    1def open_the_door(self):
    2- meow(self.oos_when_open_door)
    3+ Cat.meow(self.oos_when_open_door)
    4
    5 def hungry(self):
    6- meow(self.oos_when_hungry)
    7+ Cat.meow(self.oos_when_hungry)
    8
    9 def i_wanna_play(self):
    10- meow(self.oos_when_playful)
    11+ Cat.meow(self.oos_when_playful)

The reason we add Cat. is because the static method is now a child of Cat, so we have to call it by its full name (the technical term is fully qualified name). Otherwise it would be impossible to differentiate it from regular methods in this python file.

1class Cat:
2 def __init__(self, oos_when_hungry, oos_when_playful, oos_when_open_door):
3 self.oos_when_hungry = oos_when_hungry
4 self.oos_when_playful = oos_when_playful
5 self.oos_when_open_door = oos_when_open_door
6
7 @staticmethod
8 def meow(number_of_os):
9 print("ฅ^•ﻌ•^ฅ Me" + "o" * number_of_os + "w")
10
11 def open_the_door(self):
12 Cat.meow(self.oos_when_open_door)
13
14 def hungry(self):
15 Cat.meow(self.oos_when_hungry)
16
17 def i_wanna_play(self):
18 Cat.meow(self.oos_when_playful)

So we have our Cat definition. Remember, we're just defining it. But to use it, we have to create/instatiate it. We intantiate objects from a class like this:

1class ClassName:
2 def __init__(self):
3 pass
4
5# Creating objects looks almost like invoking a function.
6# In python, you can see that Classes always start with capital letters, so if you see something like below:
7my_object = ClassName() # it's creating/instantiating an object

In our feline world, our program now looks like this:

1class Cat: # if you don't specify the base class, python assumes it comes from Object
2 def __init__(self, oos_when_hungry, oos_when_playful, oos_when_open_door): # the reference to the cat we're constructing (self), and the number of oos they use when meowing
3 self.oos_when_hungry = oos_when_hungry
4 self.oos_when_playful = oos_when_playful
5 self.oos_when_open_door = oos_when_open_door
6
7 @staticmethod
8 def meow(number_of_os):
9 print("ฅ^•ﻌ•^ฅ Me" + "o" * number_of_os + "w")
10
11 def open_the_door(self):
12 Cat.meow(self.oos_when_open_door)
13
14 def hungry(self):
15 Cat.meow(self.oos_when_hungry)
16
17 def i_wanna_play(self):
18 Cat.meow(self.oos_when_playful)
19
20# Let me tell you about my girly kitty's morning
21a = Cat(1, 4, 8)
22a.open_the_door()
23a.hungry()
24a.i_wanna_play()
25
26print() # Let's add some space for readability. This line has nothing to do with the cats.
27
28# Let me tell you about my big macho kitty
29b = Cat(2, 10, 5)
30# He also does things in a different order...
31b.i_wanna_play()
32b.hungry()
33b.open_the_door()

How do I tell the two kitties apart?

You may have noticed I didn't give the cats names, like every other python tutorial in the world. The reason is because in our discussion, the name hasn't been relevant to describe my cats' routines. In fact, when we compare objects we do so based on their attributes. We say this tree is bigger than that one, this job pays me less than that other job. So... how do we encode that? In programming, there's many ways to achieve the same result, some solutions might be more appropriate than others (see? I am comparing solutions 🤔) but let's try to solve this case with our current level of knowledge of python.

Defining a function

When think about "comparing", what is that? Well, it's an action! And how do encode actions in python? With methods. But in this case, this method is giving us information back, we're not just printing stuff like in the methods we've used so far. In other words when we ask the question: "do I like vanilla ice cream more than chocolate?" the information we get back is: yes or no. Methods that return something are called functions and they look like this:

1def do_i_like_chocolate_more_than_vanilla():
2 return True

In the example I hardcoded the value for my preference (it will always answer True). But you can return anything from a function: a number, a string, an object or even another methods or functions. In summary: functions return information and all functions are methods. So everything we have learned about methods applies to functions too!

With this new insight, let's teach our cats how to differentiate among themselves! I'll create a new function name equals in our Cat class that compares each attribute of one cat with the other to see if they are the same cat:

1# I'm going to just show the code of equals here
2def equals(self, other_cat):
3 if self.oos_when_hungry == other_cat.oos_when_hungry and
4 self.oos_when_playful == other_cat.oos_when_playful and
5 self.oos_when_open_door == other_cat.oos_when_open_door:
6 return True
7 else:
8 return False

And the full Cat class:

1class Cat: # if you don't specify the base class, python assumes it comes from Object
2 def __init__(self, oos_when_hungry, oos_when_playful, oos_when_open_door): # the reference to the cat we're constructing (self), and the number of oos they use when meowing
3 self.oos_when_hungry = oos_when_hungry
4 self.oos_when_playful = oos_when_playful
5 self.oos_when_open_door = oos_when_open_door
6
7 @staticmethod
8 def meow(number_of_os):
9 print("ฅ^•ﻌ•^ฅ Me" + "o" * number_of_os + "w")
10
11 def open_the_door(self):
12 Cat.meow(self.oos_when_open_door)
13
14 def hungry(self):
15 Cat.meow(self.oos_when_hungry)
16
17 def i_wanna_play(self):
18 Cat.meow(self.oos_when_playful)
19
20 def equals(self, other_cat):
21 if self.oos_when_hungry == other_cat.oos_when_hungry and \
22 self.oos_when_playful == other_cat.oos_when_playful and \
23 self.oos_when_open_door == other_cat.oos_when_open_door:
24 return True
25 else:
26 return False
27
28# Let me tell you about my girly kitty's morning
29a = Cat(1, 4, 8)
30a.open_the_door()
31a.hungry()
32a.i_wanna_play()
33
34print() # Let's add some space for readability. This line has nothing to do with the cats.
35
36# Let me tell you about my big macho kitty
37b = Cat(2, 10, 5)
38b.open_the_door()
39b.hungry()
40b.i_wanna_play()
41
42print() # Let's add some space for readability. This line has nothing to do with the cats.
43
44if a.equals(b):
45 print("a == b Same Cat")
46else:
47 print("a == b Not the same Cat!")
48
49print() # Let's add some space for readability. This line has nothing to do with the cats.
50
51if a.equals(a):
52 print("a == a Same Cat")
53else:
54 print("a == a Not the same Cat!")
55
56print() # Let's add some space for readability. This line has nothing to do with the cats.
57
58if b.equals(b):
59 print("b == b Same Cat")
60else:
61 print("b == b Not the same Cat!")

You might be wondering, why can't I just do:

1if a == b:
2 print("Same Cat")

The fact is, you can! But not yet. Certain operations are common in programming, we've been using one already: constructing Objects. We have learned that to use objects you have to create them first, it's so common that Python has a special syntax for it as the __init__ method.

Comparing objects is also very common in programming. We have to compare things when we want to see if they are equal, bigger or smaller, or when we're sorting them. So Python also has a special syntax for this behavior in the function __equals__. Python exposes these common behaviors for objects as dunder methods/functions. Another example that you might remember is __str__ which is a function that we use every time we want to see a string representation of an object.

Let's see how could we implement __equals__ for our cats program:

1def equals(self, other_cat):
2 if self.oos_when_hungry == other_cat.oos_when_hungry and \
3 self.oos_when_playful == other_cat.oos_when_playful and \
4 self.oos_when_open_door == other_cat.oos_when_open_door:
5 return True
6 else:
7 return False
8
9def __equals__(self, other_cat):
10 return self.equals(othercat)

We're just calling the other function, let's test this in the big program:

1class Cat: # if you don't specify the base class, python assumes it comes from Object
2 def __init__(self, oos_when_hungry, oos_when_playful, oos_when_open_door): # the reference to the cat we're constructing (self), and the number of oos they use when meowing
3 self.oos_when_hungry = oos_when_hungry
4 self.oos_when_playful = oos_when_playful
5 self.oos_when_open_door = oos_when_open_door
6
7 @staticmethod
8 def meow(number_of_os):
9 print("ฅ^•ﻌ•^ฅ Me" + "o" * number_of_os + "w")
10
11 def open_the_door(self):
12 Cat.meow(self.oos_when_open_door)
13
14 def hungry(self):
15 Cat.meow(self.oos_when_hungry)
16
17 def i_wanna_play(self):
18 Cat.meow(self.oos_when_playful)
19
20 def equals(self, other_cat):
21 if self.oos_when_hungry == other_cat.oos_when_hungry and \
22 self.oos_when_playful == other_cat.oos_when_playful and \
23 self.oos_when_open_door == other_cat.oos_when_open_door:
24 return True
25 else:
26 return False
27
28 def __equals__(self, other_cat):
29 return self.equals(other_cat)
30
31# Let me tell you about my girly kitty's morning
32a = Cat(1, 4, 8)
33a.open_the_door()
34a.hungry()
35a.i_wanna_play()
36
37print() # Let's add some space for readability. This line has nothing to do with the cats.
38
39# Let me tell you about my big macho kitty
40b = Cat(2, 10, 5)
41b.open_the_door()
42b.hungry()
43b.i_wanna_play()
44
45print() # Let's add some space for readability. This line has nothing to do with the cats.
46
47if a.equals(b):
48 print("a == b Same Cat")
49else:
50 print("a == b Not the same Cat!")
51
52print() # Let's add some space for readability. This line has nothing to do with the cats.
53
54if a.equals(a):
55 print("a == a Same Cat")
56else:
57 print("a == a Not the same Cat!")
58
59print() # Let's add some space for readability. This line has nothing to do with the cats.
60
61if b.equals(b):
62 print("b == b Same Cat")
63else:
64 print("b == b Not the same Cat!")
65
66print() # Let's add some space for readability. This line has nothing to do with the cats.
67
68if a == b:
69 print("a == b It's the same cat!")
70else:
71 print("a == b It's not the same cat!")
72
73print() # Let's add some space for readability. This line has nothing to do with the cats.
74
75if a == a:
76 print("a == a It's the same cat!")
77else:
78 print("a == a It's not the same cat!")