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.

1 Like

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.

Does it means that value: 5 is stored now in the constructor module? Or is it reset to 0 every time?

I meant, soterd here:

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

Not entirely sure what you are referring to here because values are stored inside variables, not the entire module.

But as I mentioned in the part you shared, self.curr_loc will store the value 5 instead of 0 because move_forward() method modifies that variable when it’s called.

What action does “every time” correspond to here?

1 Like

I’m referring to the idea that init can be used to store values. Quote from learn part

I meant: does this work like store values in, let’s say, list?

a = []

a.append(5)

In the code above, after appending, the value 5, it will be there until I change something. Till then it’s there. I can call it anytime I want. So, does the init work the same, as the purpose is: to “store data”. It’s an attribute - was written. So, does it work the same way? If I call it many blocks of code later, the value will be 5 still after the part:

walle.move_forward(5)

…Or does it reset to 0 value? ( So, the robot is always on 0 value, after it processes the output?

If it’s not clear, let me put it another way. What happens after we print the “start location” after " walle.move_forward(5)" ?

PS. There should be much more exercises where we can use this whole object oriented way of programming. I don’t feel that I can handle it well. The difficulty doesn’t have to be overkill, but there are just two last exercises that really touch the real idea of using it. To whom can I write, to ask to make more ? There should be 3-8 more in my opinion ( slightly different approach, or totally different approach). In many offices, they are looking for programmers that understand object programming. After those exercises I just slightly know what’s all about, but it’s too little to feel that I could creatively develop some own approaches or ideas: If I have to overcome something.

Good set of questions. Have you tried to run the code and print the output as you ask about above? Try to run it and print it out and experiment with it. Relevant reference

Have you gone through the Practice Mode section? There is a set of problems related to Object Oriented Python in there as well that you could try out.

For any feedback you can use the Contact Us button in the top-right of this page and they can look into it as needed.

I tried, but I have no idea how to check it. Things that won’t work that I’ve checked below. It’s a combo version with all ideas I had:

class Robot:

    def __init__(self, start_location):
        self.start_loc = start_location
        self.curr_loc = start_location
        # return start_loc ## this doesn't change anything 
        return print(start_location) # "curr_loc" doesn't work too. Output error is the same: name ...  is not defined

    def move_forward(self, distance):
        self.curr_loc = self.curr_loc + distance
        # return distance ## this doesn't change anything 
        return print(start_location) # "curr_loc" doesn't work too. Output error is the same: name ...  is not defined

return print(start_location) # "curr_loc" doesn't work too. Output error is the same: name ...  is not defined
        
walle = Robot(0)        
where_walle = walle.move_forward(5)
walle_where_are_you = walle.move_forward

print(walle_where_are_you)
print(Robot.start_loc) # output: AttributeError: type object 'Robot' has no attribute 'start_loc', same with "curr_loc" and "distance"


I don’t even know how to check if the self.curr_loc value changes to 5 from 0. I believe you that it is 5 now indeed. All I have Is belief. :confused:

Whenever you get an error in Python, it also points you to the possible origin of that error.

There are multiple issues with your code if you focus on the errors the above code you shared produces -

First error

return print(start_location) # "curr_loc" doesn't work too. Output error is the same: name ...  is not defined
        
walle = Robot(0) 

The return statement above is a standalone return statement. It’s not part of any function definition and so it will throw an error.

Let’s say you comment out that particular line of code, then you will get your Second error.

This error will point you to the move_forward() function’s return statement.

def move_forward(self, distance):
        self.curr_loc = self.curr_loc + distance
        # return distance ## this doesn't change anything 
        return print(start_location)

There are two things to note here -

  1. You are trying to return a print statement, which you shouldn’t have to. You either return a value or you print the value. Not both.

  2. You don’t have start_location defined inside of move_forward.

You seem to still be unsure of the purpose of self here. I did explain that in my original reply to this post with an example, but the basic idea is that if you store a value in self.variable_name, you can use self.variable_name anywhere in you class. You can update the value, you can modify it as needed.

But if you only have the value saved in variable_name, then that variable_name can only be used inside of that class method (function). That’s why you get the error. Because start_location is not defined in move_forward(). It’s only passed to __init__().

Coming back to your original question -

Your start_location value is not changing anywhere. You create the walle object where you pass in 0 as the start_location.

That 0, when you initialize your object, is saved in self.start_loc and self.curr_loc.

When you call walle.move_forward(5), you are calling the move_forward class method (function). That method only updates self.curr_loc. So, based on the code above your start_location and your self.start_loc are not changing at all and will have 0 stored in them.