Class attributes and class methods
Some values belong to the class, not to each object: a VAT rate, a set of allowed statuses, the next invoice number. A class method works with the class itself, for example to build objects from a row of data.
- Define class attributes and read them through the class or through an object
- Explain why
self.counter += 1creates an instance attribute instead of changing the class's - Write a
@classmethodsuch asfrom_rowthat builds an object withcls(...)
A name assigned directly in the class body, outside any method, is a class attribute: one value shared by every object of the class. Constants such as a VAT rate fit here, and so does a counter that numbers invoices across all of them.
Reading self.VAT_RATE finds it: Python looks on the object first and, not finding it there, on the class. Writing is different: self.x = … always creates or changes an attribute on that one object. To change the shared value, assign through the class: Invoice.next_number += 1.
Run each example to see what it prints. Running examples doesn't count toward progress.
class Invoice: VAT_RATE = 0.2 next_number = 1 def __init__(self, customer_id, net): self.number = f"INV-{Invoice.next_number:04d}" Invoice.next_number += 1 self.customer_id = customer_id self.net = net def gross(self): return round(self.net * (1 + self.VAT_RATE), 2) first = Invoice("C00077", 100.0) second = Invoice("C01338", 19.99) print(first.number, first.gross()) print(second.number, second.gross()) print(Invoice.VAT_RATE, first.VAT_RATE, Invoice.next_number)
class Ticket: next_number = 1 def __init__(self, subject): self.number = self.next_number self.next_number += 1 self.subject = subject first = Ticket("Parcel damaged") second = Ticket("Wrong size") print(first.number, second.number) assert second.number == 2, f"both tickets got number {second.number}"
A class method receives the class instead of an object. Mark it with @classmethod on the line above def; its first parameter is called cls. Its usual job is an alternative constructor: a second way to build an object, from a CSV row or a line of text, that does the parsing and then calls cls(...) like the ordinary constructor. Callers write Product.from_row(row).
class Product: def __init__(self, sku, name, price): self.sku = sku self.name = name self.price = price @classmethod def from_row(cls, row): return cls(row["sku"].strip().upper(), row["name"].strip(), float(row["price"])) @classmethod def from_line(cls, line): sku, name, price = line.split(";") return cls(sku.strip().upper(), name.strip(), float(price)) a = Product.from_row({"sku": " spo-11681", "name": "Yoga mat", "price": "31.00"}) b = Product.from_line("hom-22847; Desk lamp ;48") print(a.sku, a.name, a.price) print(b.sku, b.name, b.price) print(type(b).__name__)
When to use it: class attributes for constants and values shared by every object; class methods for constructors that start from another format.
Mistakes to avoid: changing a shared counter through self (above); a list or dictionary as a class attribute that objects then append to (every object shares it; make per-object lists in __init__); and writing Product(...) inside a class method instead of cls(...).
- Class attributes are shared by every object; read them through self or the class.
- Assigning through
selfcreates an instance attribute; change shared values through the class. @classmethodmethods receivecls; alternative constructors returncls(...).- Next module: classes that build on other classes, and objects that contain objects.