When you instantiate c1 to be an object of class car, c1 gets those 3 things (attributes) you gave it: a model, a year, and a price. In order for c1 to know that you want its cost() method to access its price and not c2's price for example, you (correctly) defined the cost() method to associate the price printed with the price of the object that cost() is called on.
The word self is common, perhaps because given Python syntax, when you call a method on a class object with dot notation as in classObject.classMethod(), it might feel more like you are "calling classMethod() on classObject" than more typically "passing someArgument to someFunction" let's say, since you don't put classObject inside the parenthesis of classMethod. But the key piece is the association you make in the class definition (which will work with "self" or "insta" or "puppy", as long as you use the chosen word consistently). Perhaps a useful naming that would help illuminate what is going on in this particular context would be
def cost(myCar):
print ("Price of car is: ", myCar.price)
since you want the cost of myCar to be the price of myCar. This is what self does in a more general (and thus more readable and maintainable) way.
self.c1.cost()is just syntactic sugar forcar.cost(c1), soc1is just an argument and like any argument can be called anything. It is just convention that this argument is calledself.