[Tutor] Understanding Classes

Steven D'Aprano steve at pearwood.info
Tue Jan 21 13:18:15 CET 2014


On Sun, Jan 19, 2014 at 04:59:58PM -0500, Christian Alexander wrote:
> Hello Tutorians,
> 
> Looked all over the net for class tutorials
> Unable to understand the "self" argument
> Attempting to visual classes

Let me explain classes by analogy. Classes are best as representing 
things, that is, nouns, whether abstract nouns like numbers, or concrete 
nouns like cars. So let's work through an example.

Suppose I have a class Car. You might think of the class as a factory 
which creates cars. We call that factory a class, and the individual 
cars built by the factory "instances". (This analogy doesn't go far 
enough: there is a lot more to the relationship between instances and 
their class, but for now we'll start with this slightly inaccurate 
picture and then improve it later on.)

So the first thing that we do is we need to decide what sort of car the 
factory will produce. I don't mean the brand name and model, I mean, 
what does it do? What parts does it need? Two doors or four? So let's 
make a basic car that does just two things: it starts the engine, and 
stops the engine:

class Car:
    def start_engine(self):
        if self.gear not in ('Park', 'Neutral'):
            raise RuntimeError('cannot start engine')
        self.engine.start()

    def stop_engine(self):
        self.engine.stop()


Not a very interesting car, but all things must start somewhere. Now, 
lets look at those methods, `start_engine` and `stop_engine`. They take 
a single argument, `self`. What does self represent? It represents the 
individual car you wish to operate. You might end up with fifty cars:

car1 = Car()  # create a new car instance
car2 = Car()
car3 = Car()
...

Every one of those cars shares access to the same `start_engine` method. 
So when you call the method:

car23.start_engine()

how does the method know to start car23's engine, instead of car42 or 
car18? It knows because Python takes the bit before the dot (`car23`), 
and automatically passes it to the method as the `self` argument.

Confused? Let's make a brief side-track, and talk about functions. 
Here's a function:

def greet(name, greeting):
    print greeting, name

If I call that function:

greet("Fred", "Greetings and salutations")
=> prints "Greetings and salutations Fred"

Python takes the first value, "Fred", and assigns it to the argument 
`name`, the second value "Greetings and salutations" and assigns it to 
the argument `greeting`. Then the function can access them as local 
variables inside the function.

The same applies to methods, with just one additional bit of magic: when 
you call a method like this:

     instance.method(a, b, c)  # say

Python turns it into a function call:

     method(instance, a, b, c)

[For advanced readers: to be precise, Python uses what is technically 
known as an *unbound method*, which it gets by looking inside the class 
of `instance`. To be even more precise, in Python 3, it no longer uses 
unbound methods but just regular functions. But the basic concept is 
the same.]

So, back to our car: we've got a car that has two methods, which 
controls the *behaviour* of the car, but they won't do anything except 
raise an exception, because the car doesn't have any *state*. It has no 
engine to turn on or off, and it has no memory of what gear it is in. So 
let's fix that.

Every individual car needs its own engine, and each car needs to 
remember it's own gear. The obvious time to add the engine is when we 
create the car. Python creates the car when you call the class name as 
if it were a function: 

my_car = Car()  # Python creates the car instance

Now of course, Python doesn't know what cars are supposed to include, so 
it just does the bare minimum, which basically is just to allocate a 
small amount of memory for the instance and a few other bits of 
behind-the-scenes book-keeping. The rest is up to you, and that's what 
the __init__ method is for. So we give the car an engine when we create 
the car, and set the gear at the same time:


class Car:
    def __init__(self):
        self.engine = Engine()
        self.gear = "Neutral"

    def start_engine(self):
        if self.gear not in ('Park', 'Neutral'):
            raise RuntimeError('cannot start engine')
        self.engine.start()

    def stop_engine(self):
        self.engine.stop()



Of course, somebody has to write the Engine class too, but I can't be 
expected to do everything. Let's just pretend it already exists. Now you 
have a class which defines cars. When you call:

my_car = Car()

Python automatically calls the __init__ method with the newly created 
car instance as `self`. The __init__ method then runs, giving the car an 
engine, and setting the gear to neutral. Now, you should be able to 
start and stop the car:

my_car.start()
my_car.stop()


and provided the engine class knows how to start and stop, so will the 
car. So, in conclusion:

- the point of the `__init__` method is that Python automatically calls 
  that method when you create an instance, so that you can initialise 
  the instance with whatever state it needs to work;

- the point of the `self` argument is so that the method knows which 
  instance it ought to operate on.


Obviously what I have described here only scratches the surface of what 
classes do. I haven't described inheritance, and my Car class is so 
lacking in functionality that it is a joke. But I hope that it helps you 
understand __init__ and self.



-- 
Steven


More information about the Tutor mailing list