0

While executing the following code:

class Test():
    def __init__(self):
        self.hi_there()
        self.a = 5
    def hi_there(self):
        print(self.a)

new_object = Test()
new_object.hi_there()

I have received an error:

Traceback (most recent call last):
  File "/root/a.py", line 241, in <module>
    new_object = Test()
  File "/root/a.py", line 233, in __init__
    self.hello()
  File "/root/a.py", line 238, in hello
    print(self.a)
AttributeError: 'Test' object has no attribute 'a'

Why do we need to specify the self inside the function while the object is not initialized yet? The possibility to call hi_there() function means that the object is already set, but how come if other variables attributed to this instances haven't been initialized yet? What is the self inside the __init__ function if it's not a "full" object yet?

Clearly this part of code works:

class Test():
    def __init__(self):
        #self.hi_there()
         self.a = 5
         self.hi_there()

    def hi_there(self):
       print(self.a)


new_object = Test()
new_object.hi_there()

I come from C++ world, there you have to declare the variables before you assign them. I fully understand your the use of self. Although I don't understand what is the use of self inside__init__() if the self object is not fully initialized.

martineau
  • 119,623
  • 25
  • 170
  • 301
raptor0102
  • 309
  • 2
  • 15
  • The instance has been created. It has *not* been initialized. These are different things. `__init__` performs initialization. – user2357112 Nov 01 '18 at 00:23
  • `__init_()` is an instance value initializer, not a so-called "constructor" like in C++. Instances are created by the class' `__new__()` method _before_ `__init__()` called. The default `__new__()` is often used instead of a custom one. That makes it kind of like the default class `new()` method in C++. – martineau Nov 01 '18 at 01:10
  • As @martineau has said, you misunderstands `__init__`. That is not a constructor but a initializer. The instance has been fully created before executing `__init__`, `__init__` just does something more about the created instance. – Sraw Nov 01 '18 at 01:14

3 Answers3

2

There is no magic. By the time __init__ is called, the object is created and its methods defined, but you have the chance to set all the instance attributes and do all other initialization. If you look at execution in __init__:

def __init__(self):
    self.hi_there()
    self.a = 5
def hi_there(self):
    print(self.a)

the first thing that happens in __init__ is that hi_there is called. The method already exists, so the function call works, and we drop into hi_there(), which does print(self.a). But this is the problem: self.a isn't set yet, since this only happens in the second line of __init__, but we called hi_there from the first line of __init__. Execution hasn't reached the line where you set self.a = 5, so there's no way that the method call self.hi_there() issued before this assignment can use self.a. This is why you get the AttributeError.

  • I fully understand your answer. Although, I don't understand what is the use of "self" inside __init__ if the "self" object is not fully initialized. – raptor0102 Nov 01 '18 at 00:34
  • 1
    @raptor0102 `self` passed as the first argument of a method will always be a reference to the object itself whose method is being executed. Since the object exists (that's why its `__init__` can be called, albeit automatically), this "works". The object has its methods and class attributes set. But it doesn't have instance attributes that should be set exactly in `__init__`. And you can only set instance attributes by explicitly assigning to `self.`. We _need_ a reference to the object in order to initialize it. – Andras Deak -- Слава Україні Nov 01 '18 at 00:38
2

Actually, the object has already been created when __init__ is called. That's why you need self as a parameter. And because of the way Python works internally, you don't have access to the objects without self (Bear in mind that it doesn't need to be called self, you can call it anything you want as long as it is a valid name. The instance is always the first parameter of a method, whatever it's name is.).

The truth is that __init__ doesn't create the object, it just initializes it. There is a class method called __new__, which is in charge of creating the instance and returning it. That's where the object is created.

Now, when does the object get it's a attribute. That's in __init__, but you do have access to it's methods inside of __init__. I'm not completely knowledable about how the creation of the objects works, but methods are already set once you get to that point. That doesn't happen with values, so they are not available until you define them yourself in __init__.

Basically Python creates the object, gives it it's methods, and then gives you the instance so you can initialize it's attributes.

EDIT

Another thing I forgot to mention. Just like you define __init__, you can define __new__ yourself. It's not very common, but you do it when you need to modify the actual object's creation. I've only seen it when defining metaclasses (What are metaclasses in Python?). Another method you can define in that case is __call__, giving you even more control.

Pablo Paglilla
  • 366
  • 2
  • 5
0

Not sure what you meant here, but I guess the first code sample should call an hello() function instead of the hi_there() function.

Someone corrects me if I'm wrong, but in Python, defining a class, or a function is dynamic. By this I mean, defining a class or a function happens at runtime: these are regular statements that are executed just like others.

This language feature allows powerful thing such as decorating the behavior of a function to enrich it with extra functionality (see decorators).

Therefore, when you create an instance of the Test class, you try to call the hello() function before you have set explicitly the value of a. Therefore, the Test class is not YET aware of its a attribute. It has to be read sequentially.