Skip to content
The AI Agent Factory

14.3 Classes and objects

Approved by
Panaversity ·
13 min read

In everyday life. A builder works from a blueprint for a house. The blueprint is not a house. Each house built from it is one real thing, with its own address and paint color. All have the same rooms.

What you will learn. How to read code built from blueprints, by its data and the job of each function.

Why it matters. Much of the code an agent writes, and most code a company publishes for its service, is built this way.

What is a class, and what is an object?

A blueprint, and one thing built from it.

See a small one work. This class is the blueprint for one invoice. Two objects are built from it, as two houses are built from one blueprint, and each holds its own number and amount. The comments name the parts. Predict both lines.

class Invoice {
  // its two fields
  number: string;
  cents: number;

  // the constructor: runs when an object is built, and fills in its fields
  constructor(number: string, cents: number) {
    this.number = number;
    this.cents = cents;
  }

  // a method: "this" is the object it was called on
  needsSecondApprover(): boolean {
    return this.cents > 1000000; // is this one's amount over $10,000.00?
  }
}

const first = new Invoice("INV-8301", 482500); // build one object
const second = new Invoice("INV-8303", 1290000); // and another, with its own data
console.log(first.number, first.needsSecondApprover());
console.log(second.number, second.needsSecondApprover());
INV-8301 false
INV-8303 true

Each object holds its own cents, so the same method gives two different answers. These words name the parts.

PartWhat it isIn the code above
ClassA blueprint for a kind of thingInvoice
Object, or instanceOne thing built from itfirst and second
FieldsThe data each object holdsnumber, cents
ConstructorThe method that runs when an object is built and fills in its fieldsTakes a number and an amount
MethodsFunctions that work on that object's dataneedsSecondApprover()

Inside a method, the object is this in TypeScript and self in Python.

How do I read a class I did not write?

By its fields, and by the one-sentence job of each method.

At work. The agent reorganized Brightline's scripts into two small classes. VendorDirectory looks up names by id. InvoiceBook holds the invoices and a directory, and answers questions about them with four methods: open(), openTotalCents(), dueDateOf(inv) and dueBy(cutoff). Ana says what each method does in one sentence. In dueBy, she checks that it calls open(), so paid invoices stay out, and dueDateOf, so empty due dates are computed from the terms.

Read a short version of VendorDirectory the same way: one field, and one method. The method's last line uses the or default from 13.4, and puts a value inside text, as 13.7 did. Predict both lines.

class VendorDirectory {
  // its one field: the names, kept by id
  #byId: Record<string, string>;

  constructor(byId: Record<string, string>) {
    this.#byId = byId;
  }

  // its one method: the name under this id, or this text if there is none
  name(id: string): string {
    return this.#byId[id] || `Unknown vendor ${id}`;
  }
}

const directory = new VendorDirectory({ V005: "Hocking Valley Pallets" });
console.log(directory.name("V005"));
console.log(directory.name("V099"));

Both print:

Hocking Valley Pallets
Unknown vendor V099

Private fields. Only the class's own methods can see a private field. TypeScript marks one with #, as in #byId, and enforces it. Python marks one with a leading underscore, as in _by_id, which is only a naming habit.

Hold another object, or extend it?

Usually hold it. With inheritance, a class extends another and becomes a kind of it, taking all its methods. With composition, a class holds another object as a field and uses it. An InvoiceBook is not a kind of vendor directory. It has one. Composition keeps each class small and lets you test each one alone. Agent code sometimes reaches for inheritance first. Ask whether "is a kind of" is really true.

Why are two objects with the same data not equal?

Because the language asks whether they are the same object, unless a method compares their fields. Two houses with the same paint are still two houses, at two addresses.

LanguageWhat an equal test asks
TypeScript=== on two objects always asks whether they are the same object, so code compares a field, such as the vendor id. TypeScript has no dunder methods.
Python== uses the class's __eq__ method, if it has one. Python names some methods with two underscores on each side. These are dunder methods, for "double underscore". Python calls __init__ when an object is built, __repr__ when it is printed, and __eq__ when it is compared with ==. On a class that only holds data, @dataclass writes all three for you.

When an agent's test compares two records with == or ===, check which kind of equal it means.

Figure 14.3. Three panels, each with two separate objects a and b holding id V005 and the name Hocking Valley Pallets. In TypeScript, a === b is false, because it compares identity. A Python plain class gives a == b False, because it has no eq of its own. A Python @dataclass gives a == b True, because its generated eq compares the fields. To ask whether it is the same vendor, compare the ids.

Figure 14.3. Same data, three answers. Only an eq method, which @dataclass writes, makes two objects with the same data equal.

Try it

The file in 14-3 is the small Invoice class. Run it and investigate, as in 13.1.

Modify. Send: "Please change the second invoice's amount to exactly 1000000 cents. Don't run it yet." Say what each line will print. Then ask the agent to run it.

Make. Send: "Please add a status field to the class and a method that answers whether the invoice is open. Print the answer for one open invoice and one paid invoice. Don't run it yet." Find the new field and the new method. Say what it will print, and then ask the agent to run it.

What you should see. After your change, both invoices answer false. Exactly $10,000.00 is not over $10,000.00.

The operating rule: read a class by its fields and the one-sentence job of each method. Prefer an object that holds another to one that extends it.

Figure 14.4. An InvoiceBook holding its invoices and a VendorDirectory. Its methods are open, openTotalCents, dueDateOf and dueBy, and dueBy calls the directory's name(id) method to add vendor names. A side panel contrasts has a, which is composition, with is a kind of, which is inheritance: an invoice book is not a vendor directory.

Figure 14.4. Composition. The book has a directory and asks it for names. It is not a kind of directory.

Next. 14.4 decides what your program lets in.

Check yourself

Question 1 / 6

0 answered

An agent's test builds two invoices from the same row and compares them.class Invoice: def __init__(self, number: str, amount_cents: int) -> None: self.number = number self.amount_cents = amount_cents a: Invoice = Invoice("INV-5318", 64000) b: Invoice = Invoice("INV-5318", 64000) print(a == b, a.number == b.number)What does it print?

Owner
Panaversity