Execute Program

Python in Detail: Class Methods

Welcome to the Class Methods lesson!

This lesson is shown as static text below. However, it's designed to be used interactively. Click the button below to start!

  • We've seen that static methods let us create methods associated with a class, rather than instances. We can also use them to define alternate constructors.

  • The Cat class below gives us two ways to create cats. We can instantiate a cat by passing in a name and age, as usual. Or we can pass in a list of comma-separated values, which might be helpful if we're reading data from a file.

  • >
    class Cat:
    def __init__(self, name, age):
    self.name = name
    self.age = age

    @staticmethod
    def from_string(cat_string):
    name, age_string = cat_string.split(",")
    return Cat(name, int(age_string))
  • Note: this code example reuses elements (variables, etc.) defined in earlier examples.
    >
    keanu = Cat.from_string("Keanu,2")
    (keanu.name, keanu.age)
    Result:
    ('Keanu', 2)Pass Icon
  • This seems to work, but we'll run into issues if we try to subclass Cat. The .from_string static method hard-codes the class: return Cat(...). That will always return instances of Cat, even when we call it on subclasses of Cat.

  • Note: this code example reuses elements (variables, etc.) defined in earlier examples.
    >
    class Tiger(Cat):
    can_roar = True

    big_keanu = Tiger.from_string("Big Keanu, 4")
  • Note: this code example reuses elements (variables, etc.) defined in earlier examples.
    >
    type(big_keanu).__name__
    Result:
    'Cat'Pass Icon
  • That's not what we want! Tiger.from_string is now an alternate constructor for the Tiger class, so it should return instances of Tiger. We define the Tiger subclass so we can add methods and attributes beyond what we have in Cat. But when we call Tiger.from_string, we get a regular Cat instance, so none of the tiger-specific methods are available.

  • Note: this code example reuses elements (variables, etc.) defined in earlier examples.
    >
    big_keanu.can_roar
    Result:
    AttributeError: 'Cat' object has no attribute 'can_roar'Pass Icon
  • Fortunately, Python offers us a clean way around this problem. The @classmethod decorator works like @staticmethod, except that we get the class itself as an argument.

  • This is similar to how self works. Instance methods automatically get the instance as their first argument. Class methods automatically get the class itself as their first argument.

  • Here's our .from_string alternate constructor rewritten as a class method.

  • >
    class Cat:
    def __init__(self, name, age):
    self.name = name
    self.age = age

    @classmethod
    def from_string(cls, cat_string):
    name, age_string = cat_string.split(",")
    return cls(name, int(age_string))
  • The cls argument refers to the class from which the instance was created: in our case, Cat. In the method body, cls(...) calls the Cat constructor.

  • This argument is named cls by convention, though you may also see klass or class_. We can't name it class because that's a keyword in Python.

  • Note: this code example reuses elements (variables, etc.) defined in earlier examples.
    >
    keanu = Cat.from_string("Keanu,2")
    type(keanu).__name__
    Result:
    'Cat'Pass Icon
  • Unlike the original version, this method works correctly with subclasses. When we call Tiger.from_string, Python passes in Tiger as the cls argument.

  • Note: this code example reuses elements (variables, etc.) defined in earlier examples.
    >
    class Tiger(Cat):
    can_roar = True

    big_keanu = Tiger.from_string("Big Keanu,4")
    type(big_keanu).__name__
    Result:
    'Tiger'Pass Icon
  • Note: this code example reuses elements (variables, etc.) defined in earlier examples.
    >
    big_keanu.can_roar
    Result:
    TruePass Icon
  • In practice, many Python programmers default to using @classmethod instead of @staticmethod. This adds the cls argument, which we may not always use. Still, it has a nice symmetry: instance methods get self, and class methods get cls.

  • Here's a code problem:

    The House class holds information needed to build a house. There are already some static methods for the required materials and the total cost.

    After subclassing this class to design a house with a pool, we have problems when calculating the cost. Find and fix the problem. Pay close attention to method resolution: when .total_cost gets the materials, which method is it actually calling?

    class House:
    @staticmethod
    def materials():
    # This returns the materials needed, along with their relative costs.
    return [
    ("wood", 30000),
    ("concrete", 20000),
    ("paint", 10000),
    ]

    @classmethod
    def total_cost(cls):
    # Calculate the total cost from the materials list.
    return sum([cost for (_, cost) in cls.materials()])
    class HouseWithPool(House):
    @staticmethod
    def materials():
    return House.materials() + [
    ("concrete", 20000),
    ("diving board", 1000),
    ]

    assert HouseWithPool.materials() == [
    ("wood", 30000), ("concrete", 20000), ("paint", 10000), ("concrete", 20000),
    ("diving board", 1000)
    ]

    # The pool should add an extra $21k in costs.
    assert HouseWithPool.total_cost() == 81000
    Goal:
    None
    Yours:
    NonePass Icon