Methods on classes have to include the self
parameter to work properly, however, there are two notable exceptions to this rule that are worth learning about. They are classmethod
s and staticmethod
s.
staticmethod
A method is said to be "static" when it doesn't use the class properties at all. In other words, "self
" is unused within the method body. However, you can't then remove the parameter self
from the definition because it has to be there for methods to work. Unless, that is, you use the decorator staticmethod
on the method. If that is applied you can remove the self
parameter and still have other non-self
parameters. Static methods like this can still be called using self
within other methods, but they can also be called via the class and instances of the class. Why would you ever want to do this though? It crops up surprisingly often, but generally you may want to group utility functions together to make them easier to use.
For example, consider the Coordinate
class from Part 4. A static method is generally a method or function that is strongly associated with the class, even if it doesn't use the class properties.
from dataclasses import dataclass
@dataclass(frozen=True)
class Coordinate:
x: int
y: int
z: int
For Coordinate
we might want to create a utility function to get possible values of x
, y
and z
. Let's fix the minimum and maximum values of the three dimensions as constants at the beginning of the file. Now we can call them in the class. Our staticmethod
would look something like this.
from dataclasses import dataclass
MIN_X, MAX_X = (0, 10)
MIN_Y, MAX_Y = (0, 10)
MIN_Z, MAX_Z = (0, 10)
@dataclass(frozen=True)
class Coordinate:
x: int
y: int
z: int
@staticmethod
def get_possible_values(axis: str):
axis = axis.lower()
if axis == 'x':
min_, max_ = MIN_X, MAX_X
elif axis == 'y':
min_, max_ = MIN_Y, MAX_Y
elif axis == 'z':
min_, max_ = MIN_Z, MAX_Z
else:
raise ValueError(f'axis {axis} is invalid. Choose "x", "y", or "z".')
return [i for i in range(min_, max_+1)]
# The staticmethod can be called from both the class and instances of the class!
print(Coordinate.get_possible_values('x'))
print(Coordinate(0, 0, 0).get_possible_values('x'))
Thus we can write a class method that does not use self
. If you still aren't convinced by this tool's efficacy don't worry. Once you've created your own classes and developed your own software using them, you will come across situations like this and this is a tool for those times.
classmethod
Class methods are just as easy to create and apply as static methods, but they have a very different purpose. Class methods are instance creation methods. This is easier to explain with an example. To do so, let's consider the ThreeDimensionalSpace
class from Part 4.
@dataclass
class ThreeDimensionalSpace:
coordinates: set[Coordinate]
I have omitted the other methods as we don't need them for the example.
When working in 3D cartesian space (i.e. in some sort of 'box') we need to create instances of Coordinate
for every point, but all we need to know to do this is really the minimum and maximum values for x, y, and z. Since x, y, and z must be integers we can work out every possible coordinate from just 6 numbers: min and max of x, y, and z.
However, the class ThreeDimensionalSpace
needs us to create the set of all these coordinates every time before we can instantiate it. What if we built it in?
@dataclass
class ThreeDimensionalSpace:
coordinates: set[Coordinate]
@classmethod
def from_min_max(cls, xlims: tuple[int, int], ylims: tuple[int, int], zlims: tuple[int, int]):
coordinates = set()
for x in range(xlims[0], xlims[1]+1):
for y in range(ylims[0], ylims[1]+1):
for z in range(zlims[0], zlims[1]+1):
coordinates.add(Coordinate(x, y, z))
return cls(coordinates)
Enter the classmethod
. This is a method that can be called from the class itself, or an instance of a class like staticmethod
, and its selling point is that it creates a new instance of the class. The first parameter for a method decorated with classmethod
must always be cls
, which is the constructor of the class. In practice this lets you add class-creation shortcuts to your work. It can even be used as the only way to instantiate the class, which can be useful when you don't want users to construct the class themselves in the "normal" way or perhaps there is a private way of instantiating the class that you don't want users to see.
In [12]: new = ThreeDimensionalSpace.from_min_max((0, 2), (0, 2), (0, 2))
In [13]: for c in new.coordinates:
...: print(c)
...:
Coordinate(x=2, y=0, z=2)
Coordinate(x=0, y=1, z=0)
Coordinate(x=2, y=2, z=2)
Coordinate(x=2, y=1, z=0)
Coordinate(x=1, y=2, z=2)
Coordinate(x=0, y=0, z=1)
Coordinate(x=0, y=2, z=1)
Coordinate(x=1, y=0, z=1)
Coordinate(x=1, y=1, z=0)
Coordinate(x=2, y=0, z=1)
Coordinate(x=2, y=1, z=2)
Coordinate(x=2, y=2, z=1)
Coordinate(x=0, y=1, z=2)
Coordinate(x=1, y=2, z=1)
Coordinate(x=0, y=2, z=0)
Coordinate(x=0, y=0, z=0)
Coordinate(x=1, y=1, z=2)
Coordinate(x=1, y=0, z=0)
Coordinate(x=2, y=0, z=0)
Coordinate(x=2, y=2, z=0)
Coordinate(x=0, y=1, z=1)
Coordinate(x=2, y=1, z=1)
Coordinate(x=1, y=2, z=0)
Coordinate(x=0, y=0, z=2)
Coordinate(x=0, y=2, z=2)
Coordinate(x=1, y=0, z=2)
Coordinate(x=1, y=1, z=1)
Hopefully the utility of class methods should be self-evident. However both of these decorators are simply useful tools that you should know about. In this part I have covered static and class methods, and I briefly mentioned the concept of "Private" methods and classes, which will be the subject of the next installment of this series. Stay tuned for Part 6: Public, Private, and Super Private Naming.
For a complete look at the articles in this series, have a look at this overview page: Getting started with classes in Python