Python, Concept by Concept

unit

Objects

A map editor needs each marker to carry its x and y together and keep them after the program ends — that is what an object is for.

Across three lessons, you will make objects, give them methods, save an object, collect objects in lists, keep lists and dictionaries as attributes, and model a changing state.

You will learn

  • Define a class that makes objects.
  • Use __init__ and self to give each new object its own attributes.
  • Write methods that calculate from or change an object's attributes.
  • Save an object's data and load it into a new object.

Lesson 1: Build Objects with a Class

Make an object with one attribute

A class is a set of instructions for making objects. class Marker: starts the instructions. The __init__ method runs when a new Marker is made. self.label stores a label on that object.

class Marker:
    def __init__(self, label):
        self.label = label

m = Marker("A")
print(m.label)

Output

A

Notice

m.label is "A". self means this particular marker. Now give a point two attributes, one for each coordinate.

Make a point that carries two coordinates

A map editor needs one marker to keep two coordinates together instead of juggling loose values. Point(2, 5) will store both x and y.

class Point:
    def __init__(self, x, y):
        self.x = x
        self.y = y

home = Point(2, 5)
print(home.x)
print(home.y)

Output

2
5

Notice

Point(2, 5) makes one object. Python calls __init__ automatically. The assignments store 2 in home.x and 5 in home.y. Values such as x and y that an object carries are called attributes.

Inside a class, self means THIS particular object. When home is being built, self.x means this particular point's x attribute.

Two objects keep separate attributes

A class can make many objects. Each new object receives its own attributes. Watch what happens when only one point changes.

first = Point(1, 3)
second = Point(8, 6)

first.x = 4

print(first.x)
print(second.x)

Output

4
8

Identity beat: first.x changes from 1 to 4. second.x stays 8. The two names refer to two different objects, so changing one object's attribute does not change the other object.

A real map program can read two coordinates, construct a point, and show its stored attributes.

Try it yourself

x = int(input("X: "))
y = int(input("Y: "))
point = Point(x, y)
print(point.x, point.y)

Notice

The two entered numbers become the new object's separate coordinates.

Trap: forgetting self

Study this cell, but do not run it. The line x = x only assigns a parameter back to the same local name. It never creates an x attribute on the point.

Read the error

class Point:
    def __init__(self, x, y):
        x = x
        self.y = y

broken_point = Point(3, 7)
print(broken_point.x)

Python reports AttributeError because broken_point.x was never created. The repair is self.x = x: store the parameter in THIS particular object's attribute.

Lesson 2: Methods Compute with Attributes

A poster planner needs each rectangle to know its own size and label. A method is a function written inside a class. It receives self, uses this object's attributes, and can return a value to the caller.

Start with just one method: area uses width and height from the rectangle that calls it.

class Rectangle:
    def __init__(self, code, width, height):
        self.code = code
        self.width = width
        self.height = height

    def area(self):
        return self.width * self.height

poster = Rectangle("NW-14", 8, 5)
print(poster.area())

Output

40

Notice

poster.area() returns 40. The method reads this poster's width and height. Now add perimeter and description methods to the same class.

class Rectangle:
    def __init__(self, code, width, height):
        self.code = code
        self.width = width
        self.height = height

    def area(self):
        return self.width * self.height

    def perimeter(self):
        return 2 * self.width + 2 * self.height

    def describe(self):
        return f"{self.code}: {self.width} by {self.height}"

poster = Rectangle("NW-14", 8, 5)
print(poster.area())
print(poster.perimeter())
print(poster.describe())

Output

40
26
NW-14: 8 by 5

Notice

poster.area() returns 40. Python supplies poster as self. The added perimeter() returns 26, and describe() returns "NW-14: 8 by 5". Each method returns its answer; the caller chooses to print it. describe() uses an f-string rather than a special display method.

A method and a plain function

A plain function can also receive an object. Compare where the rectangle appears in these two calls.

def area_of(rectangle):
    return rectangle.width * rectangle.height

print(poster.area())
print(area_of(poster))

Output

40
40

Both calls return 40. In poster.area(), the method belongs to the rectangle instructions. In area_of(poster), the object is passed to a separate function.

A method that draws

A rectangle can return a picture of itself. Build one row of "#" characters per unit of height, append the rows to a list, and join them with newline characters.

class Rectangle:
    def __init__(self, code, width, height):
        self.code = code
        self.width = width
        self.height = height

    def area(self):
        return self.width * self.height

    def draw(self):
        rows = []
        for i in range(self.height):
            rows.append("#" * self.width)
        return "\n".join(rows)

poster = Rectangle("D-1", 5, 2)
print(poster.draw())

Output

#####
#####

Notice

poster.draw() returns "#####\n#####". The caller prints the returned picture. A real program can read a width and height before constructing its rectangle.

Try it yourself

width = int(input("Width: "))
height = int(input("Height: "))
poster = Rectangle("D-1", width, height)
print(poster.area())

Notice

The input cell builds a rectangle from the entered dimensions and prints its area.

Distance between two points

A method can receive another object as a parameter. The distance rule squares the horizontal and vertical changes, adds them, then uses ** 0.5 to take the square root.

Notice

** is the power operator. Here, ** 0.5 means "the square root".

class Point:
    def __init__(self, x, y):
        self.x = x
        self.y = y

    def distance(self, other):
        dx = self.x - other.x
        dy = self.y - other.y
        return (dx * dx + dy * dy) ** 0.5

start = Point(0, 0)
finish = Point(3, 4)
print(start.distance(finish))

Output

5.0

The call returns 5.0. Here self is start, while the parameter other receives finish.

Slice an attribute string

Because self.code holds a string, a method can take a real slice of that attribute string.

class Rectangle:
    def __init__(self, code, width, height):
        self.code = code
        self.width = width
        self.height = height

    def short_code(self):
        return self.code[0:2]

    def describe(self):
        return f"{self.code}: {self.width} by {self.height}"

banner = Rectangle("SE-22", 10, 3)
print(banner.short_code())
print(banner.describe())

Output

SE
SE-22: 10 by 3

Notice

self.code[0:2] slices from position 0 up to, but not including, position 2. The call returns "SE". The slice is applied to an attribute, not to a loose name.

Lesson 3: Save and Load an Object

A map editor needs a point to survive after the program ends. The object will save its attributes as text. A separate loader will read that text and return a new object.

class Point:
    def __init__(self, x, y):
        self.x = x
        self.y = y

    def save(self, path):
        with open(path, "w") as f:
            f.write(f"{self.x}\n")
            f.write(f"{self.y}\n")
        return path

def load_point(path):
    with open(path, "r") as f:
        x = int(f.readline().strip())
        y = int(f.readline().strip())
    return Point(x, y)

point_path = "lesson_point.txt"
original = Point(12, 7)
original.save(point_path)
loaded = load_point(point_path)
print(loaded.x)
print(loaded.y)

Output

12
7

Follow the trip:

  • original.save(point_path) writes this object's two attributes with f-string lines.
  • load_point is a module-level function, outside the class. It opens the same path in explicit "r" mode.
  • int(...) changes each cleaned text line back to an integer.
  • return Point(x, y) makes and returns a new object.

The cell writes the file before it reads it, so it is safe to run again.

A method can change its own object

Some methods return a calculation. Other methods update an attribute. A counter is a small example of a changing object.

class Counter:
    def __init__(self, label):
        self.label = label
        self.count = 0

    def increment(self):
        self.count = self.count + 1
        return self.count

visits = Counter("library")
print(visits.increment())
print(visits.increment())
print(visits.count)

Output

1
2
2

Notice

visits remembers its changed count. A list can also be an attribute, so one object can collect several values.

A list as an attribute

A shelf starts with an empty list of books. Its add method appends one title to that list.

class Shelf:
    def __init__(self):
        self.books = []

    def add(self, title):
        self.books.append(title)

shelf = Shelf()
shelf.add("Tide")
print(shelf.books)

Output

['Tide']

Notice

shelf.books is ["Tide"]. The list belongs to this shelf. A dictionary can be an attribute too.

A dictionary as an attribute

A scoreboard stores a separate amount under each player's name.

class Scoreboard:
    def __init__(self):
        self.points = {}

    def record(self, name, amount):
        self.points[name] = amount

board = Scoreboard()
board.record("Ivo", 15)
print(board.points)

Output

{'Ivo': 15}

Notice

board.points is {'Ivo': 15}. The dictionary is stored on the scoreboard object. A list can also hold several objects.

Objects in a list

Make three points, then visit each point's x attribute.

points = [Point(1, 2), Point(3, 4), Point(5, 6)]
for point in points:
    print(point.x)

Output

1
3
5

Notice

The loop prints 1, 3, and 5. Each item in points is a whole object, so point.x reads its attribute.

A state machine

A lamp has one current state. Pressing it switches "off" to "on" or "on" to "off".

class Lamp:
    def __init__(self):
        self.state = "off"

    def press(self):
        if self.state == "off":
            self.state = "on"
        else:
            self.state = "off"
        return self.state

lamp = Lamp()
print(lamp.press())
print(lamp.press())

Output

on
off

Notice

The calls return "on" then "off". Each press updates the same lamp. A real shelf program can read how many titles to collect.

Try it yourself

n = int(input("How many books? "))
shelf = Shelf()
for i in range(n):
    shelf.add(input("Title: "))
print(shelf.books)

Notice

The input cell collects exactly n titles in one shelf's list.

Put it together: a class describes how to build objects. __init__ gives each new object its own attributes. Methods use self to calculate with or change that object. Attributes can hold numbers, text, lists, or dictionaries. A list can also hold several objects. A state machine changes an object's state in a controlled sequence. A save method can write attributes, while a module-level load function reads them into a new object.

Recap

  • class Point: begins instructions for making points.
  • __init__ runs for each new object and stores values with self.x = x.
  • Each object has its own attributes, so changing one point need not change another.
  • A method reads or changes the object named by self; a call such as point.distance(other) passes another object.
  • An object can save its attributes as text, and a loader can make a new object from that text.

You can now build map points that calculate with their coordinates and survive in a saved file.

Go to the exercises (24)

For parents and teachers: report a problem