Classes and objects: a ticket you can use

Create and close a ticket, then follow what happens when two names refer to the same object. Objects and access · Object-oriented programming.

A support desk handles dozens of tickets. Each has its own code and status, and closing R7 must not close R8. How can a program keep each ticket's information together, set every ticket up the same way, and change one ticket without touching the others?

A class is one written description of what every ticket holds and can do. An object is one actual ticket made from that description, holding its own information; programs call it an instance of the class. The class is written once and objects are created from it as often as needed. The class is not one of the tickets.

Put a ticket in the program

Take ticket R7: it starts with status open, and closing it should change its status to closed.

A Ticket class describes how to set up ticket objects and what they can do. One Ticket object represents this particular ticket. Its state is the information it currently holds: here, the code R7 and status open. These values belong to this ticket; another ticket can have its own values.

Here is a small Python class and one use of it:

class Ticket:
    def __init__(self, code):
        self.code = code
        self.status = "open"

    def close(self):
        self.status = "closed"

    def show_status(self):
        return self.status

a = Ticket("R7")
a.close()
print(a.status)  # closed

Read a = Ticket("R7") as creating and initializing a ticket for this example, then making a refer to it. The __init__ function sets the new ticket's code to the supplied R7 and its status to open. This starting setup is initialization. It does not close the ticket; the later a.close() does that. Many languages call this setup step a constructor; the optional section below shows how Java and Python differ.

Inside the class, self refers to the particular ticket being initialized or used. The dot selects an attribute or an operation on the object before it: self.status names that ticket's status, and a.status reads the status of the ticket reached by a. An attribute is a named piece of state, such as code or status.

close is a method, an operation associated with objects of the class. In a.close(), the ticket reached by a is the receiver of the call. Python supplies that ticket as self, so the assignment in close changes its status. The class describes the operation; the call changes this ticket, not every ticket described by the class. Other methods can read or return information without changing state. If another ticket remains open, closing R7 does not close it. To know what a call changes, find the object reached by the name before the dot.

For a separate read-versus-change trace, start with a fresh open ticket: d = Ticket("R8"). Calling result = d.show_status() returns "open" and leaves d.status as "open"; the method only reads the status. After d.close(), d.status is "closed", while result remains "open", the value returned before the change.

Two tickets, then a shared name

Now follow a separate trace. It starts with the Ticket class and no tickets yet, then creates ticket T1 with code R7 and status open for a. A second creation makes b reach T2, a separate ticket with the same displayed code and status. We deliberately use the same code here to separate matching data from the same object. T1 and T2 have different identities: they are two objects even though these values match. T1 and T2 are labels the animation uses to tell the two objects apart; R7 is the ticket's code.

Two tickets, one alias, two bindings

Read the steps as text

  1. Follow the Ticket identities and their fields.
  2. A Ticket object T1 is created. a now reaches T1.
  3. A Ticket object T2 is created. b now reaches T2. a and b reach different identities; their displayed fields match.
  4. c now reaches T1. a and c share an identity; their displayed fields match.
  5. c changes T1.status from open to closed. a and c reach T1, so each sees closed.
  6. c now reaches T2.
  7. The current bindings and fields remain unchanged.

Next c = a makes c refer to T1 too. c is an alias for the object reached by a: two names now reach the same ticket. When c.close() changes T1's status, reading a.status also gives closed. No second ticket has been closed; both names read the same changed object. T2 remains open.

Finally c = b changes which object c refers to. This is rebinding: c now reaches T2, while a still reaches T1, which remains closed. The assignment does not move a or copy the ticket. The distinction is useful whenever several parts of a program can refer to one mutable object.

Python's a is b asks whether the names reach the same object. For Java object references, a == b asks the same identity question. In this example both tests are false for the separate tickets; equal displayed fields alone do not answer them.

Why this matters beyond tickets: a report page and a notification module may both be handed the same ticket. If one closes it, the other sees closed straight away, which is what you want when they must agree. If each needs its own record to edit freely, the program has to create a separate ticket for each. Handing over the same one is sharing, not copying. Before any edit, ask who else can reach this object. Making a true copy is a later lesson.

How construction differs by language

A constructor is part of the creation path for setting up an object. In Java, new Ticket("R7") selects a constructor. The instance fields first receive Java's language-defined default values; the selected constructor then completes before the expression returns its reference. A Java constructor has the class's name and no result type; this refers to the ticket being initialized.

Python uses different hooks. On the ordinary path shown here, __new__ creates an instance, then __init__ initializes it. Our __init__ sets the attributes on the already created ticket; it is not the allocation step. Watch the two language paths give the ticket its initial state.

Optional reading, two construction paths and two language hooks

Read the steps as text

  1. Follow the Ticket identities and their fields.
  2. Java creates J1 and gives its fields default values (code null, status null). Python's __new__ creates P1 with no attributes yet.
  3. Java's constructor sets code R7, status open; J1 keeps its identity. Python's __init__ sets code R7, status open on the same P1.
  4. Java's J1 is ready with the same identity and fields. Python's P1 is ready with the same identity and fields.

In Java, constructors are not inherited or overridden as methods. If a class declares no constructor, Java can supply an implicit default constructor; declaring any constructor prevents that implicit declaration.

Python's usual class call uses __new__ first. If it returns an instance of the class, __init__ initializes it; if it does not return an instance of that class, that class's __init__ is skipped for the call. Python does not choose among several same-named __init__ definitions by signature; optional arguments or separately named factories are common alternatives.

These diagrams are logical snapshots, not a promise that objects occupy a particular stack or heap location or that rebinding causes immediate reclamation. Python == can invoke a class's value-equality behavior, while is tests identity. Java reference == tests identity (or whether both are null); .equals may compare values instead.

Try the reasoning with meters

The next scene starts with two Meter objects, M1 and M2, both reading baseline. left reaches M1; right and spare reach M2. First right = left makes right and left aliases for M1. A method through right changes M1's reading to shifted, so left sees that reading too. M2 stays at baseline.

Transfer the reasoning to two meters

Read the steps as text

  1. Follow the Meter identities and their fields.
  2. right now reaches M1.
  3. right changes M1.reading from baseline to shifted. left and right reach M1, so each sees shifted.
  4. right now reaches M2.
  5. The current bindings and fields remain unchanged.

Then right = spare redirects only right to M2. left still reaches M1 / shifted; right and spare reach M2 / baseline. Follow the names to their current objects before deciding which state a read will see.

Recap

  • An object holds state; a class describes initialization and operations for its objects.
  • A method acts on its receiver; this close changes it. Aliases can observe the same changed object, while rebinding moves only the assigned name.
  • Initialization gives each ticket its starting code and status before the later close operation changes it.
  • Parts of a program that hold one object see each other's changes; create a separate object when each needs its own.

Next, object identity and aliasing asks the same question when an object is reached through a list position or another object's field, not only through a variable. To practise following and changing links between nodes, try Linked List Fundamentals.