Python Arena
Step 2 of 4

Class methods and static methods

Methods can belong to the class too. You call such a method through the class's name, without creating an object first.

A class method has the decorator @classmethod above it, and its first parameter is cls instead of self. Where self is the object, cls is the class itself, and Python fills it in for you just as it fills in self. A static method has the decorator @staticmethod and no such parameter at all: it's an ordinary function that lives in the class because it belongs with it.

class Time:
    def __init__(self, hours, minutes):
        if not Time.valid(hours, minutes):
            raise ValueError(f"Not a valid time: {hours}:{minutes}")
        self.hours = hours
        self.minutes = minutes

    @staticmethod
    def valid(hours, minutes):
        return 0 <= hours < 24 and 0 <= minutes < 60

    @classmethod
    def from_text(cls, text):
        parts = text.split(":")
        return cls(int(parts[0]), int(parts[1]))

    @classmethod
    def midnight(cls):
        return cls(0, 0)

    def __str__(self):
        return f"{self.hours:02}:{self.minutes:02}"


print(Time.valid(25, 0))
lunch = Time.from_text("12:30")
print(lunch)
print(Time.midnight())
Output
False
12:30
00:00

(In the f-string, :02 writes a number with at least two digits, padding it with a zero.)

  • Time.valid(25, 0) looks only at the two numbers it gets, so it needs no Time object to work on. Any code can ask it whether a time makes sense, and __init__ asks it first thing, before it stores anything.
  • from_text and midnight create new objects: cls(...) calls the class, exactly as Time(...) would. Class methods like these are a handy way to offer more than one way to create an object.

Which one to choose

Both kinds are called the same way, Time.valid(...) or Time.from_text(...). The difference is what they get:

  • an ordinary method gets the object (self), and is called on an object;
  • a class method gets the class (cls), so it can create objects with cls(...) and use class variables as cls.name;
  • a static method gets neither: it works with the arguments you give it. Like any other code, it can still reach the class by its name, for example to read a class variable such as Ticket.sold below, but it has no self and no cls.

A class method that works with a class variable:

class Ticket:
    sold = 0

    def __init__(self):
        Ticket.sold += 1

    @classmethod
    def sales_report(cls):
        return f"{cls.sold} tickets sold"


Ticket()
Ticket()
print(Ticket.sales_report())
Output
2 tickets sold

Calling an ordinary method through the class doesn't work, because there is no object to be its self. Time.__str__() would stop with TypeError: Time.__str__() missing 1 required positional argument: 'self'.

Your task

Challenge 1 of 3

Fill in the blanks so that Rectangle.square(side) is a class method that creates a square, and the program prints:

3 x 3
9

Challenge 2

A music player shows a song's length as minutes and seconds, such as 3:05, with the seconds always written with two digits. Write the class Song:

  • a static method format_length(seconds) that returns a length given in seconds as such a string, for example "3:05" for 185 seconds;
  • a constructor that takes a title and a length in seconds, and stores them in the attributes title and seconds;
  • __str__, which returns a string such as Slow River (5:02). Let it use the static method.

For example, this code:

print(Song.format_length(185))
print(Song.format_length(59))
print(Song("Slow River", 302))

prints:

3:05
0:59
Slow River (5:02)

Challenge 3

Write the class Order. Each new order gets the next number from the class variable next_number, which starts at 1: the first order is number 1, the next number 2, and so on. The class has:

  • a constructor that takes the customer's name, stores it in customer, and gives the order its number;
  • a class method start_numbering(cls, number) that sets the number the next order will get;
  • __str__, which returns a string such as Order 1 for Ada.

For example, this code:

print(Order("Ada"))
print(Order("Ben"))
Order.start_numbering(100)
print(Order("Cy"))
print(Order.next_number)

prints:

Order 1 for Ada
Order 2 for Ben
Order 100 for Cy
101