Problem overview
A ticket machine gives every new ticket the next number: 1, 2, 3 and so on. The counter belongs to the class, not to any one ticket: there is one copy, shared by all tickets. Each ticket also keeps its own number, copied from the counter when the ticket is created.
The class Ticket has a constructor Ticket(name) that stores name and number, a static method issued() that returns how many tickets were created since the last reset, and a static method reset_counter() that sets the counter back to 0. After a reset, creating tickets for Ann, Bob and Cy gives them the numbers 1, 2 and 3, and issued() returns 3. Two tickets for the same name still get different numbers, because the number comes from the counter, not the name.
Approach: one shared counter, one copy per ticket
Two kinds of data live here. The counter is shared, so it is a class attribute: a variable written directly in the class body, outside any method, with no self.. The name and the number differ from ticket to ticket, so they are instance attributes, set through self in __init__.
When a ticket is created, it adds 1 to the shared counter and then copies the counter's value into its own number. The copy matters. The counter keeps moving as more tickets are made, but a number that was copied stays fixed. If a ticket read the counter every time it was asked for its number, all the tickets would show the latest value.
Step by step
- Create the class attribute
count = 0at the top of the class, at the same indent asdefand withoutself.. Every ticket sees the sameTicket.count. - Store the name in
self.nameinside__init__. This belongs to the one ticket. - Add 1 to
Ticket.count. Write the class name, notself. - Set
self.numbertoTicket.count: this ticket's own copy of the counter's current value. issuedreturnsTicket.count. The@staticmethoddecorator marks a method that needs no ticket: it has noselfand is called on the class,Ticket.issued().reset_counterassigns 0 toTicket.count, so the next ticket gets number 1 again.
The solution in Python
class Ticket:
count = 0
def __init__(self, name):
self.name = name
Ticket.count += 1
self.number = Ticket.count
@staticmethod
def issued():
return Ticket.count
@staticmethod
def reset_counter():
Ticket.count = 0
Using it:
Ticket.reset_counter()
tickets = [Ticket(n) for n in ("Ann", "Bob", "Cy")]
for t in tickets:
print(t.name, "#" + str(t.number))
print(Ticket.issued())
Ticket.reset_counter()
print(Ticket.issued())
Ann #1
Bob #2
Cy #3
3
0
Why Ticket.count and not self.count
This is the classic trap. In Python, self.count += 1 reads the class attribute, adds 1, and then stores the result as a new attribute on that one object. The shared counter is not touched. Compare:
class Bad:
count = 0
def __init__(self):
self.count += 1
a = Bad()
b = Bad()
print(Bad.count, a.count, b.count)
0 1 1
The class counter is still 0, and each object has its own private count of 1. With Ticket.count += 1, the count really is shared.
Edge cases and common mistakes
- No tickets. After
reset_counter(),issued()returns 0. - Not copying the number. If a ticket returned
Ticket.countinstead of storingself.number, printing the tickets after creating all of them would show the last number on every ticket. - Forgetting the reset. Without it, a second round of tickets would continue from where the first one stopped. After
reset_counter(), two tickets for Mia get 1 and 2. - Same names. Names don't have to be unique: the number is what tells tickets apart.
- Complexity. Creating a ticket and both static methods are O(1).
