Classes and objectsChapter 59 of 114
Classes and Objects
Bundle data and the code that works on it into one thing.
A class is a template
A class describes a kind of thing. An object is one actual thing made from that template:
class Dog:
sound = "woof"
first = Dog()
second = Dog()
print(first.sound)
print(type(first).__name__)
print(first is second)Output
woof Dog False
Dog() builds a new object each time. Two dogs, both with the same sound, but not the same object.
Why bundle at all
Without classes, related data drifts apart and every function needs all of it passed in:
name = "Rex"
age = 3
def describe(name, age):
return f"{name} is {age}"
print(describe(name, age))Output
Rex is 3
That works for one dog. For fifty, you need parallel lists and the discipline to keep them lined up. A class keeps each dog's data together, and keeps the functions that work on it in the same place.
Attributes
Values attached to an object. You can set them from outside:
class Dog:
pass
rex = Dog()
rex.name = "Rex"
rex.age = 3
print(rex.name, rex.age)Output
Rex 3
That works and it is a bad habit — nothing says which attributes a Dog is supposed to have, and a typo silently creates a new one. The __init__ chapter fixes this properly.
Class attributes versus instance attributes
An attribute on the class is shared by every object; one set on the object belongs to it alone:
class Dog:
species = "canis familiaris"
rex = Dog()
fido = Dog()
rex.name = "Rex"
print(rex.species, fido.species)
print(rex.name)
print(hasattr(fido, "name"))Output
canis familiaris canis familiaris Rex False
Assigning through one object creates an instance attribute that shadows the class one, rather than changing it for everybody:
class Dog:
species = "canis"
rex = Dog()
fido = Dog()
rex.species = "wolf"
print(rex.species, fido.species, Dog.species)Output
wolf canis canis
class Dog:
tricks = []
rex = Dog()
fido = Dog()
rex.tricks.append("sit")
print(fido.tricks)Output
['sit']
There is one list, on the class. Per-object data belongs in __init__.
Objects are mutable
An object passed into a function is the same object, so changes show up everywhere:
class Counter:
pass
def bump(c):
c.value += 1
counter = Counter()
counter.value = 0
bump(counter)
bump(counter)
print(counter.value)Output
2
Everything is an object
Classes are not a separate world. Numbers, strings and functions are objects too, with types and attributes:
print(type(5), type("a"), type([]))
print((5).bit_length())
print("abc".upper())Output
<class 'int'> <class 'str'> <class 'list'> 3 ABC
Writing your own class is just adding a new type to the ones already there.
Test yourself
2 questionsWhat happens when two objects share a class attribute that is a list, and one appends to it?
Show the answer
Both see the change, because there is one list on the class — It is the same trap as a mutable default argument. Per-object data belongs in __init__.
What does assigning rex.species = "wolf" do to the class attribute?
Show the answer
Nothing; it creates an instance attribute that shadows it — Reading falls back to the class, but writing always lands on the object.
The __init__ Method
Set up each new object with the data it needs.