Classes and objects
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.
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)
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:
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))
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:
1class Lamp:2 def __init__(self, street, brightness):3 self.street = street4 self.brightness = brightness5 6dock = Lamp("Dock Lane")
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.
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)
market.switch_on() runs the method with self set to market, so only market.on becomes True. mill keeps its own on, still False.
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:
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())
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.
1class Lamp:2 def switch_on():3 print("On")4 5mill = Lamp()6mill.switch_on()
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 givenThe 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:
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)
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:
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")
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:
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)
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.
What does the last line print?
1class Lamp:2 max_brightness = 103 4mill = Lamp()5market = Lamp()6mill.max_brightness = 57print(market.max_brightness)
mill.max_brightness = 5 creates a new attribute on mill only, which hides the class one for mill. market has none of its own, so it still finds the class attribute, 10.
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:
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)
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.
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.
Ada 2
1class House:2 # Your code here3 pass4 5home = House("Ada", 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.
Total: 13
1class Tally:2 def __init__(self):3 self.total = 04 5ores = Tally()6ores.add(4)7ores.add(9)8print(ores.report())
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.
3
1class Cart:2 def __init__(self, load):3 self.load = load4 5a = Cart(3)6b = Cart(5)7c = Cart(2)
What you learned
class Name:defines a new type; callingName(...)makes an object of it.__init__runs when an object is made and sets its starting attributes onself.selfis the object a method was called on:mill.switch_on()runsswitch_onwithselfasmill.- Methods are functions inside a class; they can read and change the object's attributes.
- Attributes on
selfbelong 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.