Learning Python | Methods vs Functions vs Classes

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

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

I'm wanna play!

Open the door you tirant!

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:

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

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".

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":

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 "()".

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?

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:

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.

Note that constructing (or initializing) (that __init__ method) has a reference to the object we're creating in the parameter self. We use this selfargument 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:

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:

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:

in the specific case of our meow method:

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:

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.

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:

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

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:

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:

And the full Cat class:

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

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:

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