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
Catclass 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 = nameself.age = agestaticmethoddef 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) This seems to work, but we'll run into issues if we try to subclass
Cat. The.from_stringstatic method hard-codes the class:return Cat(...). That will always return instances ofCat, even when we call it on subclasses ofCat.- Note: this code example reuses elements (variables, etc.) defined in earlier examples.
>
class Tiger(Cat):can_roar = Truebig_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'
That's not what we want!
Tiger.from_stringis now an alternate constructor for theTigerclass, so it should return instances ofTiger. We define theTigersubclass so we can add methods and attributes beyond what we have inCat. But when we callTiger.from_string, we get a regularCatinstance, so none of the tiger-specific methods are available.- Note: this code example reuses elements (variables, etc.) defined in earlier examples.
>
big_keanu.can_roarResult:
AttributeError: 'Cat' object has no attribute 'can_roar'
Fortunately, Python offers us a clean way around this problem. The
@classmethoddecorator works like@staticmethod, except that we get the class itself as an argument.This is similar to how
selfworks. 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_stringalternate constructor rewritten as a class method.>
class Cat:def __init__(self, name, age):self.name = nameself.age = ageclassmethoddef from_string(cls, cat_string):name, age_string = cat_string.split(",")return cls(name, int(age_string))The
clsargument refers to the class from which the instance was created: in our case,Cat. In the method body,cls(...)calls theCatconstructor.This argument is named
clsby convention, though you may also seeklassorclass_. We can't name itclassbecause 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'
Unlike the original version, this method works correctly with subclasses. When we call
Tiger.from_string, Python passes inTigeras theclsargument.- Note: this code example reuses elements (variables, etc.) defined in earlier examples.
>
class Tiger(Cat):can_roar = Truebig_keanu = Tiger.from_string("Big Keanu,4")type(big_keanu).__name__Result:
'Tiger'
- Note: this code example reuses elements (variables, etc.) defined in earlier examples.
>
big_keanu.can_roarResult:
True
In practice, many Python programmers default to using
@classmethodinstead of@staticmethod. This adds theclsargument, which we may not always use. Still, it has a nice symmetry: instance methods getself, and class methods getcls.Here's a code problem:
The
Houseclass 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_costgets the materials, which method is it actually calling?class House:staticmethoddef materials():# This returns the materials needed, along with their relative costs.return [("wood", 30000),("concrete", 20000),("paint", 10000),]classmethoddef total_cost(cls):# Calculate the total cost from the materials list.return sum([cost for (_, cost) in cls.materials()])class HouseWithPool(House):staticmethoddef 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:
None