Understanding classes, init and self

Could you explain this code excerpt? I am learning and trying to get in touch with the logic of classes.

class NewWindow(tk.Toplevel): 
  def __init__(self):
    self.title("Another window")

The __init__ method initialises a new instance (object) of the class NewWindow. The actual creation of the instance is done by a separate method __new__, before __init__ is called. See the documentation for more details.

In the __init__ method the first statement, super().__init__(), is a call to the __init__ method of the superclass, tk.TopLevel - the NewWindow instance would then acquire any attributes being defined in the superclass __init__. And the last statement, self.title("Another Window"), calls the title method on the NewWindow instance with the value "Another Window".

class NewWindow(tk.Toplevel): # Override the class "tk.Toplevel" with these functions. 
  def __init__(self): # Runs every time an instance of the class is run.
    super().__init__() # This runs the parent class __init__ function in tk.Toplevel
    self.title("Another window") 
    mytitle = "Stuff" # This variable is only available in this function __init__.

I’m newer to Python so constructive criticism is welcome.

So if super().__init__() calls the initializer from the tk.Toplevel superclass, it means the Toplevel widget is initialized, right?

So NewWindow class sems to be about initializing Toplevel widget. So if we dont set tk.Toplevel as a class parameter, we would not get parameters from the method which is called from superclass.

And what if I replace super().__init__() by for example tk.Toplevel.__init__()?

So self in def __init__(self): is a a parameter variable, which calls the class itself to the __init__ method? Which mean NewWindow class initialize itself. The title method is from where? Isnt it from tk.Toplevel as we initalize Toplevel within the class before?

Did you try it? What happened?

Well, probably all variables of tk.Toplevel are available from NewWindow class then, right?

TypeError: Toplevel.__init__() missing 1 required positional argument: 'self'

Umm, it would need an argument?

What does tk.TopLevel.__init__ look like? Without that it is unclear what actual initialisation if any that the superclass is doing? The fact you call self.title(...) in NewWindow.__init__ implies title is inherited from tk.TopLevel or its superclasses, assuming you haven’t defined it anywhere else in the NewWindow class.

Also, self is a name for the NewWindow instance.

No, self.title(...) is a call to the title method of self, which represents the NewWindow instance being initialised. The parentheses after title indicate a function call, which in this case accepts a string.

I see you are right now. Is self.title() a built-in function for that class tk.Toplevel?

I haven’t seen tk.Toplevel in my tutorials. Is this a visible window widget with a titlebar?

Probably, but I don’t know tkinter - you can check the Python docs

It doesn’t override the methods of tk.Toplevel but inherits them, which then can be overridden in the class that inherited them.

Not quite, that would be the __call__ method, __init__ is only run when a new instance of a class is created.

No, self.title() is a method inherited from tk.Toplevel. Tho tkinter comes with Python and there is no clear definition of what a built-in is, so you can consider it built-in I guess :person_shrugging:.

Well, yes, it’s just a window, see the definition of a top-level window by Microsoft:

A window that has no parent, or whose parent is the desktop window, is called a top-level window .
- About Windows - Win32 apps | Microsoft Learn

Hope this helped to improve your knowledge :blush:

1 Like

Thank you, I really do appreciate being corrected. :grinning:

What exactly do you mean by that? self is the instance of a class that is normally automatically given by Python when calling a method from an instantiated object, and it doesn’t call functions or classes. Instantiated meaning so much as created. For example, if you have a blueprint for a building, you can’t just use the staircase or elevator, because well… it is just a blueprint. The same goes for classes they’re essentially just blueprints for something, and without creating (instantiating) an object from it you can’t use attributes/methods associated with it.

That could work but you would need to give tk.Toplevel.__init__() self as an argument, otherwise, it has no instance and thus can’t use functions or attributes in nothing[1].

I suggest you read this article: Inheritance and Composition: A Python OOP Guide – Real Python

  1. That would also be a case where Python doesn’t automatically sets self because this is just not the intended way of calling this method ↩︎

Assuming we are inheriting from tk.Toplevel, and we want to make a modification to the constructor init() from the inheriting super class, we can use either of the two ways:

1. super().__init__()


2. tk.Toplevel.__init__(self)

Note that for this particular example, you only require self. In other classes, depending on their particular design, they may require additional arguments besides only self. This applies to the __init__ method as well.

The following is a simple example of how the __init__ constructor from the superclass Reindeer is being modified by the subclass Rudolph:

class Reindeer:

    def __init__(self, hoof, tail, nose):

        self.hoof = hoof
        self.tail = tail
        self.nose = nose

    def print_me(self):

        print('Hoof: ', self.hoof)
        print('Tail: ', self.tail)
        print('Nose: ', self.nose)

class Rudolph(Reindeer):

    def __init__(self, hoof):      # Redefine constructor -
        Reindeer.__init__(self, hoof, 'White', 'Red')

# Create class instances (objects)
generic_deer = Reindeer('Oval', 'Brown', 'Black')
Rudy = Rudolph('Round')

# Print

Since Rudolph the red nosed reindeer has a red nose and a white tail, we don’t have to include that every time that we create an object from it. We only have to state the type of hoofs that we wish it to have. In the superclass Reindeer, all three arguments have to be provided. Thus, by using the following statements:

    def __init__(self, hoof):      # Redefine constructor - only need one argument at time of instantiation
        Reindeer.__init__(self, hoof, 'White', 'Red')  # Two arguments are pre-filled

We have modified the __init__ constructor in the subclass.

Hope this helps.

1 Like

A key point that others have referred to but I want to emphasize a bit more, as it seems to be at the root of a lot of your confusion here and is crucial for understanding what’s going on here, is understanding the difference between a class and an instance of a class. Let’s suppose we have the following classes:

class Vehicle:
    def __init__(self, speed):
        self.speed = speed

class Motorcycle(Vehicle)
    def __init__(self, speed, sidecar_attached):
        self.sidecar_attached = sidecar_attached

    def detach_sidecar(self):
        if self.sidecar_attached:
            import webbrowser; webbrowser.open("https://youtu.be/YC-MR84S1H8?t=58")
        self.sidecar_attached = False

Motorcycle is a class; it refers to a type (or class) of object. By contrast, if we then ran the following code:

my_motorcycle = Motorcycle(speed=120, sidecarattached=False)
your_motorcycle = Motorcycle(speed=50, sidecarattached=True)

we would have created two Motorcycle instances, my_motorcycle and your_motorcycle, corresponding to two individual motorcycles. The class Motorcycle has the consistent set of methods (things the object can do, such as accelerate, brake, honk, detach the sidecar, etc.), and attributes (data the object holds, such as a speed, tire pressure, sidecar status, etc.), per the class definition. However, the values of those (instance) attributes are unique to each instance of Motorcycle.

In other words, a class is like a model of vehicle, while an instance is one particular vehicle. Every vehicle of the same model has the the same basic functions and attributes, but each individual vehicle has its own speed, tire pressure, mileage, etc (attribute values), and calling a (instance) method on a specific vehicle affects the attribute values (or “state”) of that instance—for example, calling your_motorcycle.detach_sidecar() modifies that motorcycle’s value of sidecar_attached (your_motorcycle.sidecar_attached), but doesn’t affect others’.

Behind the scenes, when you call (instance) methods like your_motorcycle.detach_sidecar() on a Motorcycle instance, Python passes the your_motorcycle object as their first argument (which by convention is named self—there’s nothing actually magic about that name), before any you pass in yourself. That way, the method knows which particular motorcycle it is acting on. The same thing happens when you call super().__init__()—through a bit of magic, super() implicitly knows the current instance and passes it to any methods you call on the object it returns.

This is equivalent to calling the method on the class, and manually passing in the particular instance you want the method to act on, e.g. Motorcycle.detach_sidecar(your_motorcycle). (Of course, you’d normally never need to do this, but it hopefully serves to illustrate what’s actually happening here). In your case, since tk.Toplevel is the superclass of NewWindow, calling tk.Toplevel.__init__(self) is equivalent to calling super().__init__() inside an instance method of NewWindow, just clunkier, less idiomatic and hard-coded.

As far as understanding self is concerned, there isn’t anything actually special about the name self; it is merely the (universal) convention for the name of the first parameter to any instance method of a class, to which Python automatically passes the current instance of the class when calling a method on an instance.

Just to note, both these approaches have the effect of calling the method that is the immediate superclass constructor, not modifying it. (We might, however, call your specific example __init__ method as a whole “wrapping” the superclass one).

And for that, super().__init__() is the canonical, idiomatic and straightforward way to do that specifically, and will work regardless of superclass. There’s no need to hardcode a particular superclass, call its __init__ class method and manually pass the current instance, aside from highly specialized advanced scenarios involving multiple inheritance from multiple non-mixin classes with __init__ methods defined where you want to call a specific constructor that has lower priority in the MRO. Needless to say, this is not something a beginner is going to need to worry about any time soon, and possibly ever. Thus:

should of course be written

        super().__init__(hoof, 'White', 'Red')

And also: Tkinter 8.5 reference: a GUI for Python

So, does it mean that if I need to use certain methods from a class, I cannot just write CertainClass.certain_method()? The class should be “loaded” to memory (?) via self and then I can use its methods? The same should work for built-in methods. At least I can read it in /tkinter/init().py’s that every method of a widget (class) has the self parametr.

Thanks, I guess I am starting to understand.

Yes, you would need an instance for the method to work because the method might miss some information that is set/configured in an instance. CertainClass.__init__ might configure/set something on which CertainClass.certain_method is dependant.

Well yes, you need an instance of CertainClass to run its methods. It’s what I said with the blueprint, you can see what it is supposed to be and how it should work but you cannot interact with the attributes or methods the blueprint is describing, without creating an object from it.

Every method of a class is required to have the self[1] parameter. But you don’t need to set it when you have an instance and call a method like this: instance.do_something() because Python realizes the context and puts self in for you

:+1: Just keep learning. OOP might be a bit confusing in the beginning but once you understand the concept, you will love it. I also recommend Real Python as a learning resource, I think they are pretty good.

  1. Fun fact: You don’t have to call it self you can just use any name you want. self just is the standard name everyone uses for it, so you should stay with it too even tho you can change it ↩︎

1 Like

No. If the class is CertainClass then that class itself will be defined (and initialized as object) before any instances of the class can be initialized. And if certain_method is a static method (defined with the @staticmethod decorator so the function doesn’t require a self argument) you can call that method even if there are no instances of the class. (Class methods defined with @classmethod are similar.)

But if certain_method is not a static method, so if it takes a self argument, then it can only be called on an instantiated object of that class.

1 Like