0

when we create a class which is having instance attribute,why don't we call the instance attribute using self keyword.Please

class car:
    def __init__(self, type, color):
        self.type = type
        self.color = color


c1 = car('SUV','Red')
print(c1.type)

Why not print(c1.self.type), because self.type is the actual attribute. Got the following error:

AttributeError: 'car' object has no attribute 'self'
Deepak Mishra
  • 91
  • 1
  • 7
  • Because self, as first parameter, means "the object it self". When you write `self.type` it means "the atribute type of the object". I will vote as dup: https://stackoverflow.com/questions/2709821/what-is-the-purpose-of-self – dani herrera Feb 04 '19 at 16:01
  • check here https://stackoverflow.com/questions/2709821/what-is-the-purpose-of-self#2709832 – alec_djinn Feb 04 '19 at 16:01

1 Answers1

2

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 Cars. 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')
Slam
  • 8,112
  • 1
  • 36
  • 44
  • ,thanks for the instant reply,i have some problem with understanding of "And your __init__ actually mutates c1, just by referencing to that object as self." I understand that,After an object is created, all variables that belong to the object, which are prefixed with 'self.' replaced with the object's name when it is created from the class.but attribute is self.type not just type.Could you please explain more. – Deepak Mishra Feb 04 '19 at 16:19
  • Added examples, please take a look – Slam Feb 04 '19 at 19:50