Kodewick
Lesson 26 of 31Contents

Classes and objects

Lesson 26 of 31 · Part 5 · Objects · about 20 minutes

So far your data has lived in strings, numbers, lists and dictionaries. A class lets you make a type of your own. Here is a Lamp type: every lamp has a street and a brightness, and every lamp can be switched on.

main.py
1class Lamp:2    def __init__(self, street, brightness):3        self.street = street4        self.brightness = brightness5        self.on = False6 7    def switch_on(self):8        self.on = True9        print(f"Lamp on {self.street} is on")10 11mill = Lamp("Mill Road", 3)12mill.switch_on()13print(mill.brightness, mill.on)
Output
Lamp on Mill Road is on
3 True

Classes and objects

The class is the blueprint; each thing made from it is an object (also called an instance). Lamp("Mill Road", 3) makes one new lamp object, and mill is a name for it. Call the class again and you get another, separate lamp:

main.py
1class Lamp:2    def __init__(self, street, brightness):3        self.street = street4        self.brightness = brightness5 6mill = Lamp("Mill Road", 3)7market = Lamp("Market", 5)8print(mill.street, mill.brightness)9print(market.street, market.brightness)10print(type(mill))
Output
Mill Road 3
Market 5
<class '__main__.Lamp'>

The values stored on an object, like mill.street, are its attributes. You read them with a dot. Class names are written in CapWords (Lamp, BankAccount), unlike variables and functions.

If you print an object itself, as in print(mill), real Python shows something like <__main__.Lamp object at 0x7f3a...>: the class name and a memory address that changes from run to run. That is not very useful; lesson 28 shows how to make objects print nicely.

__init__ sets up a new object

__init__ (two underscores on each side) is a special method that runs by itself every time an object is made. Its job is to give the new object its starting attributes. The arguments you pass to Lamp(...) go to __init__, after self.

If you leave one out, you get the same kind of error as with any function:

main.py
1class Lamp:2    def __init__(self, street, brightness):3        self.street = street4        self.brightness = brightness5 6dock = Lamp("Dock Lane")
Output
Traceback (most recent call last):
  File "main.py", line 6, in <module>
    dock = Lamp("Dock Lane")
TypeError: Lamp.__init__() missing 1 required positional argument: 'brightness'

__init__ can also set attributes that don't come from arguments, like self.on = False in the first example: every lamp starts switched off.

self is the object

Every method gets the object it was called on as its first parameter, named self by convention. When you write mill.switch_on(), Python runs switch_on with self set to mill. So self.on = True changes that lamp, and no other.

Guess first

What does the last line print?

1class Lamp:2    def __init__(self, street):3        self.street = street4        self.on = False5 6    def switch_on(self):7        self.on = True8 9mill = Lamp("Mill Road")10market = Lamp("Market")11market.switch_on()12print(mill.on, market.on)

Tip: Press Step on any example in this lesson. The variables panel shows each object with its attributes, and while a method runs, self is the very object the method was called on.

Methods

A method is a function defined inside a class. Methods can take more parameters after self, use the object's attributes, and return values, just like the functions from lesson 16:

main.py
1class Lamp:2    def __init__(self, street, brightness):3        self.street = street4        self.brightness = brightness5 6    def brighten(self, amount):7        self.brightness = min(self.brightness + amount, 10)8 9    def label(self):10        return f"{self.street}: {self.brightness}/10"11 12mill = Lamp("Mill Road", 3)13mill.brighten(4)14print(mill.label())15mill.brighten(6)16print(mill.label())
Output
Mill Road: 7/10
Mill Road: 10/10

A method can call another method on the same object through self, as in self.brighten(1). A common slip is forgetting self in the method's parameters: Python still passes the object, and there is nowhere to put it.

main.py
1class Lamp:2    def switch_on():3        print("On")4 5mill = Lamp()6mill.switch_on()
Output
Traceback (most recent call last):
  File "main.py", line 6, in <module>
    mill.switch_on()
TypeError: Lamp.switch_on() takes 0 positional arguments but 1 was given

The error says the method takes 0 arguments but got 1: that 1 is the object itself. This class has no __init__, which is fine: then the object simply starts with no attributes.

Changing attributes

Attributes can be changed from outside with a plain assignment, and you can even add new ones. Reading one that was never set is an AttributeError:

main.py
1class Lamp:2    def __init__(self, street):3        self.street = street4 5mill = Lamp("Mill Road")6mill.street = "Old Mill Road"7mill.color = "yellow"8print(mill.street, mill.color)9print(mill.brightness)
Output
Old Mill Road yellow
Traceback (most recent call last):
  File "main.py", line 9, in <module>
    print(mill.brightness)
AttributeError: 'Lamp' object has no attribute 'brightness'

Changing attributes directly is allowed, but a method is often better: it can check the new value first. That is what the next example does.

A bank account

Here is a class that keeps a balance and refuses to go below zero. The rules about money live in one place, inside the class:

main.py
1class BankAccount:2    def __init__(self, owner, balance=0):3        self.owner = owner4        self.balance = balance5 6    def deposit(self, amount):7        if amount <= 0:8            raise ValueError("deposit must be positive")9        self.balance += amount10 11    def withdraw(self, amount):12        if amount > self.balance:13            print(f"{self.owner}: not enough money")14            return False15        self.balance -= amount16        return True17 18wick = BankAccount("Wick", 20)19wick.deposit(15)20wick.withdraw(50)21wick.withdraw(30)22print(f"{wick.owner} has {wick.balance} coins")
Output
Wick: not enough money
Wick has 5 coins

balance=0 is a default value, as in lesson 17: BankAccount("Ada") starts at 0. The raise comes from lesson 21.

Class attributes

An attribute set on self belongs to one object. An attribute written directly in the class body belongs to the class and is shared by all its objects. It's good for values that are the same for everyone, or for counting objects:

main.py
1class Lamp:2    max_brightness = 103    count = 04 5    def __init__(self, street):6        self.street = street7        Lamp.count += 18 9mill = Lamp("Mill Road")10market = Lamp("Market")11print(Lamp.count)12print(mill.max_brightness, market.max_brightness)
Output
2
10 10

When you read mill.max_brightness, Python looks on the object first and, not finding it, on the class. To change a class attribute, assign through the class, as in Lamp.count += 1.

Guess first

What does the last line print?

1class Lamp:2    max_brightness = 103 4mill = Lamp()5market = Lamp()6mill.max_brightness = 57print(market.max_brightness)

Objects in a list

Objects go into lists like any other value. Then loops, comprehensions, sum and sorted with a key (lesson 19) all work on them:

main.py
1class Lamp:2    def __init__(self, street, brightness):3        self.street = street4        self.brightness = brightness5 6lamps = [Lamp("Mill Road", 3), Lamp("Market", 7), Lamp("Dock Lane", 5)]7for lamp in lamps:8    print(lamp.street, lamp.brightness)9 10print("Total:", sum([lamp.brightness for lamp in lamps]))11brightest = max(lamps, key=lambda lamp: lamp.brightness)12print("Brightest:", brightest.street)13dim = [lamp.street for lamp in lamps if lamp.brightness < 6]14print(dim)
Output
Mill Road 3
Market 7
Dock Lane 5
Total: 15
Brightest: Market
['Mill Road', 'Dock Lane']

Practice

Write the code, then press Check. A hint costs nothing; the answer is there when you need it.

Exercise 1

A house

Write a class House whose __init__ takes owner and floors and stores them. Then make a house for "Ada" with 2 floors and print its two attributes.

It should print
Ada 2
exercise1.py
1class House:2    # Your code here3    pass4 5home = House("Ada", 2)
Output
Press Run or Ctrl+Enter
Exercise 2

A counter

Add two methods to Tally: add(self, n) adds n to self.total, and report(self) returns the text "Total: " followed by the total. The program should then print the line below.

It should print
Total: 13
exercise2.py
1class Tally:2    def __init__(self):3        self.total = 04 5ores = Tally()6ores.add(4)7ores.add(9)8print(ores.report())
Output
Press Run or Ctrl+Enter
Exercise 3

Count the carts

Use a class attribute made that counts how many Cart objects have been made. After the three carts below, print Cart.made.

It should print
3
exercise3.py
1class Cart:2    def __init__(self, load):3        self.load = load4 5a = Cart(3)6b = Cart(5)7c = Cart(2)
Output
Press Run or Ctrl+Enter

What you learned

  • class Name: defines a new type; calling Name(...) makes an object of it.
  • __init__ runs when an object is made and sets its starting attributes on self.
  • self is the object a method was called on: mill.switch_on() runs switch_on with self as mill.
  • Methods are functions inside a class; they can read and change the object's attributes.
  • Attributes on self belong to one object; class attributes are shared by all of them.
  • Objects can go in lists and be looped over, summed by an attribute, or sorted with a key.