Learn
06.1 | CLASSES, OBJECTS AND CONSTRUCTORS
01 | YOU ALREADY USE OBJECTS
A string has characters and methods such as upper(). A list has items and methods such as append(). These are objects: values with a type, state and available behaviour. Object-oriented programming lets you define types that fit your own problem.
Imagine two club robots. Each has a name and battery percentage. A class called Robot can define what information and operations a robot object has. Each created robot is an instance of that class.
A class is often described as a blueprint, but it is also a real Python object created when the class statement executes. Creating the class does not automatically create every robot you need. You instantiate it separately.
02 | DEFINE A CLASS AND CREATE AN INSTANCE
class Robot: starts a class definition. By convention, class names use capitals at word starts, such as Robot or LibraryMember. The indented body defines attributes and methods belonging to that class.
The call Robot("Scout", 80) creates an instance and initialises it with supplied arguments. self.name and self.battery are instance attributes: named pieces of state associated with that particular robot.
Use a dot to access an attribute: scout.name. A variable such as scout holds a reference to the object; it is not the object’s permanent identity or automatically the robot’s displayed name.
class Robot:
def __init__(self, name, battery):
self.name = name
self.battery = battery
scout = Robot("Scout", 80)
print(scout.name)
print(scout.battery)EXPECTED OUTPUT
Scout 80
03 | WHAT SELF MEANS
self is the conventional first parameter of an instance method. Python supplies the instance for a normal call such as scout.describe(). It tells the method which robot’s state to access. You do not normally pass scout again inside those parentheses.
Inside a method, self.name means the name attached to the current instance. A bare name is a local or otherwise resolved variable; it is not automatically the instance attribute with the same spelling.
self is a convention rather than a reserved keyword, but use it consistently. Robot.describe(scout) shows the underlying explicit-instance form for this ordinary method, while scout.describe() is the normal readable call.
class Robot:
def __init__(self, name, battery):
self.name = name
self.battery = battery
def describe(self):
return f"{self.name}: {self.battery}%"
scout = Robot("Scout", 80)
print(scout.describe())
print(Robot.describe(scout))EXPECTED OUTPUT
Scout: 80% Scout: 80%
04 | CONSTRUCTION AND INITIALISATION
Calling a class is commonly described as constructing an object. More precisely, Python normally creates the instance before __init__ initialises it. __init__ is therefore an initializer, not the operation that allocates and returns the instance. You do not need to implement __new__ for these beginner classes.
__init__ usually assigns the required starting attributes and returns None implicitly. It must not return a different object or a non-None result. The class call returns the new instance to the caller.
Arguments after self supply the starting information. If your initializer requires name and battery, Robot() without those arguments raises TypeError. Give defaults only when they represent useful starting behaviour.
05 | MAKE STARTING STATE VALID
An object should begin in a state its methods can rely on. If battery must be a whole percentage from zero to one hundred, check that requirement before storing it. A valid type hint later will not perform that runtime check automatically.
type(battery) is int deliberately rejects True and False, because bool is otherwise a subclass of int. A name must be non-blank text. Trimming outer spaces is an explicit normalisation choice here.
Raising ValueError stops this construction when the proposed state is unsuitable. The caller can catch it and ask for a corrected input. Avoid creating an object that only sometimes has the attributes its methods require.
class Robot:
def __init__(self, name, battery=100):
if type(name) is not str or not name.strip():
raise ValueError("Use a non-blank name")
if type(battery) is not int or not 0 <= battery <= 100:
raise ValueError("Use a whole battery percentage from 0 to 100")
self.name = name.strip()
self.battery = battery
scout = Robot(" Scout ", 80)
print(scout.name, scout.battery)EXPECTED OUTPUT
Scout 80
06 | TWO INSTANCES, TWO STATES
Two calls normally create two separate instances. scout = Robot("Scout", 80) and rover = Robot("Rover", 60) give each object its own attributes. Changing scout.battery does not also change rover.battery.
Assignment is different: alias = scout binds another name to the same object. Changing alias.battery changes the object that scout also refers to. It does not create an independent copy.
is tests object identity. The example’s distinct instances are not the same object. Equality with == depends on the class’s equality behaviour; do not confuse the two tests. Dataclasses will later provide field-based equality for records.
class Robot:
def __init__(self, name, battery):
self.name = name
self.battery = battery
scout = Robot("Scout", 80)
rover = Robot("Rover", 60)
alias = scout
alias.battery = 50
print(scout.battery, rover.battery)
print(alias is scout)
print(scout is rover)EXPECTED OUTPUT
50 60 True False
07 | CLASS DATA CAN BE SHARED
An attribute assigned in the class body can supply a class-level value. A shared immutable label such as species = "robot" may be appropriate. A mutable list defined there can be a trap: instances may all access the same list object.
For per-instance history, assign self.history = [] inside __init__. That expression creates a new list each time the initializer runs. Moving only the attribute spelling is not enough; understand when the mutable object is created.
An instance assignment such as scout.label = "custom" can shadow a class attribute for that instance. Mutating a shared class list through an instance does not create a new independent list.
class Robot:
category = "club robot"
def __init__(self, name):
self.name = name
self.history = []
a = Robot("Scout")
b = Robot("Rover")
a.history.append("started")
print(a.history)
print(b.history)
print(a.category)EXPECTED OUTPUT
['started'] [] club robot
08 | WATCH REFERENCES AND INSTANCES
The fixed model begins with Scout at eighty percent and Rover at sixty. Choose which reference receives a battery update. The alias refers to Scout, so it shares Scout’s state. Each model run resets the starting values.
09 | CHOOSE A CLASS FOR A COHERENT JOB
A class helps when related state and operations belong together. A library service owns catalogues and active loans; its methods can enforce lending rules consistently. A one-off arithmetic calculation might remain clearer as a simple function.
Object-oriented design is a way to organise responsibilities, not a requirement to wrap every value in a custom class. Compare the input, output and state of the job before choosing the structure.
For this lesson, write a small class, create two instances and test their independence. Then create an alias and explain the difference. If a change appears in both places, inspect references and shared mutable attributes before blaming the method.
PUT YOUR SKILLS TO WORK
Use the Section 6 challenges to practise these ideas independently. Work in your Python editor, predict results and keep a short test record.
Section 6 challenges Python course navigation