Calling the method which is not created

Screen Link:
https://app.dataquest.io/c/78/m/435/object-oriented-python/11/creating-and-updating-an-attribute

My Code:

class NewList(DQ):
    """
    A Python list with some extras!
    """
    def __init__(self, initial_state):
        self.data = initial_state
        self.calc_length()
    
    def calc_length(self):
        """
        A helper function to calculate the .length
        attribute.
        """
        length = 0
        for item in self.data:
            length += 1
        self.length = length
    
    def append(self, new_item):
        """
        Append `new_item` to the NewList
        """
        self.data = self.data + [new_item]
        self.calc_length()

fibonacci = NewList([1, 1, 2, 3, 5])
print(fibonacci.calc_length)

fibonacci.append(8)
print(fibonacci.calc_length)

What I expected to happen:
I called calc_length method from the class and expected it to return the length of the list.
In the solution part, only length is called like fibonacci.lenght() am not able to understand if iwe haven’t defined any method with the name lenght how are we calling it here?

What actually happened:

<bound method NewList.calc_length of Class Name:       <class '__main__.NewList'>
Class Attributes: {'data': [1, 1, 2, 3, 5], 'length': 5}
Class Methods:    ['append', 'calc_length']
>
<bound method NewList.calc_length of Class Name:       <class '__main__.NewList'>
Class Attributes: {'data': [1, 1, 2, 3, 5, 8], 'length': 6}
Class Methods:    ['append', 'calc_length']
>

The solution is not calling a method.

The solution is accessing an attribute.

print(fibonacci.length)

length is an attribute.

I would recommend going through the 9th Mission Step again to understand attributes better.

I am still not able to understand the significance of the ‘self’ keyword. Like why do we need to use it everywhere?
I can understand that when calling a method in a class by default an argument is called and that’s why we need to pass self in those methods. Buy why ‘self’ before each attribute within in a method?

Also I have now added a retun statement to the calc_length method. Why isn’t it returning the length? Why do we need to call the attribute and calling the method is not working?

def calc_length(self):
       
        length = 0
        for item in self.data:
            length += 1
        self.length = length
        return self.length

If the content didn’t help clarify this then maybe this should help - Problem in Object Oriented Programming - #2 by the_doctor

Because you are not calling the method in your code. You run the following -

calc_length is a method ( a function) and to call a function you need to run it as -

print(fibonacci.calc_length())

Those parentheses at the end are important when calling functions/methods. That’s how the function/method gets called and returns a value, if there’s a return statement.

Use of self seems to be the cause of the confusion here, I think, mostly. The link I shared above should help clarify it, hopefully.