Understanding Self Internally in Python
Asked Answered
R

1

7

I fully understand what is being passed to self in this example. I'm very confused on how it is being passed to self internally. Could someone help me understand?

class Cars:

    def __init__(self, model, engine, doors):

        self.model = model
        self.engine = engine
        self.doors = doors

tesla = Cars('Model S', 'Electric', 'Four door')
ford = Cars('Mustang', 'v8', 'Two door')
Risley answered 23/3, 2018 at 3:44 Comment(11)
Consider reading a Python Class tutorial. SO is not a tutorial site.Ruthenian
Yes, I've read a tutorial and that other post. I just don't quite understand how it is passing the instance to self internally. Thanks anyway.Risley
@Ben. I guess you didn't read the right tutorial. Not many really good ones out there to be honest.Ministrant
@cᴏʟᴅsᴘᴇᴇᴅ. OP has a valid point here. The other question is not a very good duplicate. OP is not aware of the fact that the syntax is actually equivalent to object.__call__(Cars, ...), which calls Cars.__new__ and passes the result to __init__, and the other question is not really about that.Ministrant
@MadPhysicist Taking your word for it. I did not analyse the duplicate as closely as I probably should've, so reopened.Icebound
@cᴏʟᴅsᴘᴇᴇᴅ. I appreciate the trust.Ministrant
*type.__call__, not object._call__. my mistake.Ministrant
@BenHutton. I've added a couple of dozen links in my answer for you to follow. They are to the most reputable sources I could find (SO and Python docs :) Hopefully it makes the process easier and introduces you to the nitty gritty of Python.Ministrant
@cᴏʟᴅsᴘᴇᴇᴅ. Hopefully I've justified the reopenMinistrant
@MadPhysicist Awesome, great piece. I'll try and see if I can get this added to the list of canonicals on SOpython.Icebound
@cᴏʟᴅsᴘᴇᴇᴅ. I didn't even know that's a thing :)Ministrant
M
14

There are many steps that beginner tutorials do not cover, so I will attempt to be brief but thorough. I will try to be precise in my terminology, so you can look up all the sections you are unclear about.

In general, methods in Python are functions in the class object. All functions are descriptors. Part of what being a descriptor means is that when you access a method through the instance of a class, it creates a closure that automatically passes the instance you created it on as the self parameter. For example, if Cars had a method start(self) in addition to __init__, then tesla.start would be a "bound method", which is a closure that passes tesla as self to Cars.start. Notice that I did not put parentheses after tesla.start. Putting parentheses would actually invoke the bound method.

Second piece of information: if a class defines a __call__ special method, its instances are said to be callable. This means that you can invoke an instance as if it were a function using the () operator. You can see a case of this when you do tesla = Cars(...). Here Cars is a class object, but you are calling it as if it were a function. We are now getting close to where self actually gets passed in to __init__.

Thirdly, pretty much everything in Python is an object and obeys the general rules you know for objects, like being created from a class, etc. This includes functions and classes. A class object is created from another class, which is appropriately named a metaclass. Normally metaclasses are a can of worms you don't want to open, so we will scratch just enough of the surface here and no more. The most common metaclass is type: 99%1 of all class objects you will encounter as a beginner as instances of type. type defines a __call__ method, which is what you are invoking when you do Cars(...), since Cars is an instance of type.

type.__call__(Cars, ...) does a couple of things. First it calls Cars.__new__(Cars, ...). This returns the new instance that you will later end up assigning to tesla or ford or whatever. Then, if the thing that __new__ returned is an instance of Cars, it will call Cars.__init__(self, ...), where self is that new instance it just created.

And that's how self gets passed to __init__. Keep in mind that all the steps can be customized or overridden, so this is really just a basic overview of the simplest case.


The links in this text should get you started in more specific research. All the links are completely distinct, even when they are for the same term. All the links are to Stack Exchange sites (SO with one exception), or the official Python 3 documentation, with one exception.


1 I made up that statistic, but it's probably right anyway.

Ministrant answered 23/3, 2018 at 5:3 Comment(1)
I greatly appreciate you taking the time to discuss that. That gives me a much better understanding of how everything works together along with something to go off of to do further research. Thank youRisley

© 2022 - 2024 — McMap. All rights reserved.