[Tutor] Help with understanding classes

Steven D'Aprano steve at pearwood.info
Sat May 21 13:49:27 CEST 2011


On Sat, 21 May 2011 08:24:34 pm Robert Sjöblom wrote:
> I'm trying to wrap my head around classes and their attributes, but
> am having a hard time doing so. The websites and books that I have
> consulted haven't been much help; most of them assume prior
> programming/oop experience, something I lack.

Let's start with a simple question -- what is an object? In programming, 
an object is a thing that combines data and behaviour together in one 
handy package. Back in the Dark Ages *wink*, code and behaviour were 
always separate, so you would have to design a data structure in one 
place of your program, then write functions to manipulate it somewhere 
else. If you had two different data structures which needed similar 
actions, you had to jump through hoops to keep the functions for one 
separate from the functions of the other.

But objects let you keep related pieces of code together, so they don't 
get in the way of unrelated code with the same name:

class Artist:
    def draw(self):
        print("I splashed paint on the canvas until it looked like"
              " my mother on a rocking horse.")

class GunSlinger:
    def draw(self):
        print("The stranger reached for his gun, but I was too" 
              " quick for him and plugged him full of lead.")


The function (actually "method") called draw for Artists doesn't get 
over-ridden by the method with the same name for GunSlingers:


>>> whistler = Artist()
>>> wyatt_earp = GunSlinger()
>>> whistler.draw()
I splashed paint on the canvas until it looked like my mother on a 
rocking horse.
>>> wyatt_earp.draw()
The stranger reached for his gun, but I was too quick for him and 
plugged him full of lead.


Here you see object code in action. You start with a class, which is 
something vaguely like a template. (It's actually nothing like a 
template, but that will do for now.) Before you can use the class, you 
normally have to instantiate it.

Think of it like this: in the real world, "car" is a kind of machine, or 
if you prefer, a *class* of machine. But you can't just get into the 
abstract class of "car" and drive to the shops, you need an actual, 
concrete, physical car: an *instance* of the class. In object oriented 
programming ("OOP"), this is normally the same: you create an instance 
of the class, and then work with that. The instance gets its behaviour 
(and sometimes its data) from the class.

The examples above are classes with behaviour only, no data or state. 
They never change. That's not very useful. Normally you want to store 
data in the class instance, sometimes in the class itself. Here's an 
example:

class Paper:
    colour = "white"
    size = "A4"

This defines a class with no behaviour, only state. In this case, it has 
two attributes, colour and size. The values stored in the class are 
global defaults: all Paper instances share the same value. But you can 
give individual instances their own independent value by assignment:

>>> sheet = Paper()
>>> sheet.colour
'white'
>>>
>>> another_sheet = Paper()
>>> another_sheet.size = "A5"  # cut the paper in half
>>> another_sheet.colour = "green"  # and paint it
>>> another_sheet.colour  # check that the change is stored
'green'
>>>
>>> sheet.colour  # and check that the other sheet is unchanged
'white'


Normally though, you don't need or want to set default state that 
applies to all instances. In the above Paper example, it is harmless, 
but sometimes it can lead to bugs, so be careful with class attributes.

So although attributes common to all instances of a class can sometimes 
be useful, generally people try to avoid them, and it is more common to 
put off defining the instance's attributes until the instance is 
created. For that, Python has a special method called "__init__" 
(that's two underscores at the front and back). When you create an 
instance by calling Paper(), the __init__ method (if any) is 
automatically called by Python.

class Paper:
    def __init__(self, colour="white", size="A4"):
        self.colour = colour
        self.size = size

Now the attributes no longer live inside the class, but inside the 
instance. Paper instances no longer share global state. Each instance 
gets its own independent attribute for colour and size, which you can 
specify when you create it:

>>> sheet = Paper("blue", "Foolscap")
>>> sheet.colour
'blue'


Now, I've skimmed over a couple of important things here. Firstly, 
syntax: in Python, the syntax for attribute access is with a dot:

paper.size

lets you retrieve the attribute. To set the attribute, you simply assign 
to it:

paper.size = "A3"

Notice that dot is the same syntax used for accessing methods:

whistler.draw()

That's because in Python, methods are just another attribute, only you 
can call them with () syntax. This lets you do all sorts of cool and 
advanced things, like store a method somewhere to use it later:

import random
if random.random() < 0.5:
    method = wyatt_earp.draw  # no parentheses!
else:
    method = whistler.draw

# later...
method()  # call the method

but I digress.

The other thing I glossed over was the mysterious "self" parameter in 
the method definitions. Remember, methods are defined inside the class, 
not inside the instance. When you call a method, how does the class 
know which instance it should look at? The answer is, Python does some 
sleight of hand in the background, changing what you write:

whistler.draw()

into what actually gets executed:

Artist.draw(whistler)

That's moderately advanced though, you don't need to care about it, 
*except* that you have to remember to declare a parameter in all your 
methods to hold that auto-magic instance argument. Conventionally, we 
call it "self", but you can call it anything you like. (You can, but 
you shouldn't.) Forgetting to declare "self" in your methods is one of 
the more common source of bugs.


That's pretty much all you need to know to start using objects. There's 
a lot more though: inheritance, class methods and static methods (as 
opposed to ordinary methods), properties, descriptors (advanced!), 
slots, and more. But one step at a time. 

Any questions, don't hesitate to ask!



-- 
Steven D'Aprano


More information about the Tutor mailing list