[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