Problem in Object Oriented Programming

I started the Object-Oriented Python of Python for data science:fundamentals course. I’m having a hard time understanding the concepts.I’m stuck on some things:
i)init:Why use this method?Is it compulsory?What happens if I don’t use it?
ii)self:Why is self used for defining some attributes whereas there are attributes which don’t need self in the same method?Example code in point(iii) where there is self.length and also length.
I know that I’ll get and error for not using self.But,I didn’t totally understand the phantom argument issue. Like why is that a problem?
iii)In this screen:
https://app.dataquest.io/m/352/object-oriented-python/10/creating-and-updating-an-attribute
For the code segment:
‘’’
class MyBankBalance():
“”"
An object that tracks a bank
account balance
“”"

def __init__(self, initial_balance):
    self.balance = initial_balance
    self.calc_string()

def calc_string(self):
    """
    A helper method to update self.string
    """
    string_balance = "${:,.2f}".format(self.balance)
    self.string = string_balance

def add_value(self, value):
    """
    Add value to the bank balance
    """
    self.balance += value
    self.calc_string()

mbb = MyBankBalance(3.50)
print(mbb.string)
‘’’
How is the calc_string method using the self.balance which is the attribute of init method?

I would recommend going through Step 8 of the Mission again to understand __init__ better.

But to add a bit more detail, it’s useful to define an initial state for your Class object. For example, consider a Robot Class. You create an object walle for that Class.

Let’s say you want to move walle to a certain position. But to be able to do that, you need to know where it is currently placed. That starting_location can be the initial state of your robot.

The constructor basically sets up such an initial state for the object you created.

class Robot:

    def __init__(self, start_location):
        self.start_loc = start_location

    def move_forward(self, distance):
        self.new_loc = self.start_loc + distance

And you create your object as -

walle = Robot(0)

So, walle is currently at the position marked by the number 0.

Now, if you wish to move walle by a distance of 5. You can run

walle.move_forward(5)

Look at the function move_forward() in the Class definition. It uses the starting location to calculate the new location.

This is an example where __init__ can be helpful. You can define or set certain properties/attributes etc. as the initial state for whatever object you are creating. This is a relatively simple example. How you implement it depends on what you are trying to achieve.

You can ignore __init__ and pass your starting location through some other normal Class method/function. But that, I believe, would not be considered as good programming practice.

As you saw in the Steps before Step 8, there was no __init__. So, it’s not compulsory. But it is common practice to include it, and as I showed above, it is useful as well.

self is useful for when you want your attributes to persist throughout your Class. For our Robot Class, if we had the following instead -

class Robot:

    def __init__(self, start_location):
        start_loc = start_location

    def move_forward(self, distance):
        self.new_loc = start_loc + distance

Notice there’s no self for start_loc this time.

If we ran

walle.move_forward(5)

That would throw an error -

NameError: name ‘start_loc’ is not defined

You might remember the concept of scopes from the Missions on functions. The scope of self.start_loc is through the entire Class.

You can access that attribute anywhere in the Class. You can manipulate that attribute anywhere and it stores that manipulated value. The scope of start_loc is limited to that function itself.

So, self, is essentially the object that you created. In our example above, it’s walle.

Once your object is instantiated, self.start_loc for walle means it’s walle.start_loc. It’s an attribute of our object.

We can even print it out -

print(walle.start_loc)

That will print out 0. If it was not defined as self.start_loc. Then trying to print the above will also result in an error -

AttributeError: ‘Robot’ object has no attribute ‘start_loc’

Another example -

Consider the Robot Class again. But this time, I add a new attribute to it.

class Robot:

    def __init__(self, start_location):
        self.start_loc = start_location
        self.curr_loc = start_location

    def move_forward(self, distance):
        self.curr_loc = self.curr_loc + distance

Notice self.curr_loc for both the constructor and the method move_forward. When you create walle, the robot’s current location is the same as its starting location. Which makes sense.

Now, we move it by 5.

walle.move_forward(5)

After the above, the self.curr_loc value changes to 5 from 0. It was useful to have the value persist through the Class, so we defined it as a Class attribute using self.

There is more to the above, but it’s not that important now. As you get more and more comfortable with working with Classes, you will learn those things.

I believe my explanation related to the scope answers this question and should clarify the use.

There could be much better explanations of init and self than what was provided in the mission. What is laid out on in the writing of step eight is mediocre at best.

Why does every class need an initial state? Or, maybe it is the object that needs an initial state?

For content feedback, you can use the Contact Us button in the top-right corner of this page.

I have mentioned in my answer above that is not necessary to have a constructor for every class. And yes, it’s dependent on whether your object needs an initial state or not.

If you have any further questions, please create a new, separate question.