When you're describing methods (__init__
in your case), self
is refers to instance, that will be passed in future — you need this, because you don't know what instances will be created from your class after.
When you're building instance with c1 = car(...)
:
- at first, python makes "empty" object and assigns it to
c1
- then this empty object being passed to initializer, roughly as
car.__init__(c1, 'SUV', 'Red')
And your __init__
actually mutates c1
, just by referencing to that object as self
.
Here's example that may help you better understand, step by step.
Python objects is not restricted by any schema of fields (by default). This means that you can add or remove attributes in runtime – after instance been created:
class Car:
pass #does nothing
car = Car()
car.color = 'red'
car.type = 'SUV'
You have class that does not describe anything, except the name, you can add type/color/whatever you want after object creation.
The downside of this – there's a huge room to make mistake, plus you need to write a lot of code as your data model grows.
The next logical step to take - make a "mutation" function, that takes object + required attribute values, and bundle all mutation logic in one place:
class Car:
pass
def prepare_car(car_instance, color, type):
car_instance.color = color
car_instance.type = type
car = SimpleCar()
prepare_car(color='red', type='SUV')
Now all mutation logic is in one place, any user can call the same call, no need to re-write all mutation code every time. Logically, code that mutates car, is highly related to SimpleCar
class (well, because it changes car color and type). It would be good to have it bound to class somehow. Let's move it to class namespace:
class Car:
def prepare_car(car_instance, color, type):
car_instance.color = color
car_instance.type = type
we've just moved our function in scope of object. What does that mean for us? To access that function, we need to provide "namespace" first, i.e. Car.prepare_car
– reads as "take a class Car_, look for prepare_car method". Now we can call it with
car = Car()
Car.prepare_car(car, color='red', type='SUV')
Here come the magic of python OOP. Once you have instance of a class, you can call methods on instance itself, not from class scope. And what happens when you do so – python will automatically pass instance itself as first argument to function:
car = Car()
Car.prepare_car(car, color='red', type='SUV') # calling class.function
is the same as
car = Car()
car.prepare_car(color='red', type='SUV') # calling instance.method
This is the reason why first argument for methods usually called self
this is only convention, but it reads good, consider re-factored code
class Car:
def prepare(self, color, type):
self.color = color
self.type = type
car = Car()
car.prepare(color='red', type='SUV')
now method is called prepare
, not prepare_car
, because we know what we'll mutate – this is in scope of Car
class, so we expect this function to interact with Car
s. First argument now called self
, because we'll mostly call it on instances (like my_car.prepare(...)
), so instance changes itself.
And finally, the __init__
. This method (if provided) will be automatically called on instance after it's been created, and signature for instantiating class changes accordingly. Lets make prepare
method and initiation part:
class Car:
def __init__(self, color, type):
self.color = color
self.type = type
car = Car(color='red', type='SUV')