Class Methods
00:00 In the previous lesson, you learned more about the cousin of attributes, properties. In this lesson, I’ll dive deeper on methods. There are three types of methods in Python: instance methods, those which you’ve seen so far; class methods, which are like class attributes, but more method-y; and static methods, which don’t require a reference to either a class or an object.
00:27 I told you I’d wait to repeat myself. Instance methods are those you’ve been using, and as their name implies, they operate on the instance object, which means there has to be an instance to be able to call them.
00:41
They’re typically used to do stuff to the data associated with the object, and while I’m repeating myself, each instance method must take at least one argument, a reference to the object, which by convention is named self
. You know all this though, but I figured it’d be good to put it all in one place so you can compare it to what comes next.
01:03
A class method is associated with the class instead of the instance. To indicate that it’s a class method, you wrap it with the @classmethod
decorator.
01:13
This kind of method also requires at least one argument, but instead of it being the object, it’s a reference to the class. Like self
, there’s a convention for this one as well.
01:25
It should be named class, spelled cls
, and like self
, this is only enforced through the fear of your fellow coders—or to maintain their respect and love.
01:36 That sounds a little more positive than a threat. A couple common uses for class methods are manipulating class attributes and factories. A factory is a method that returns an instance of a new object.
01:50 It’s an alternative to a constructor and usually is done if some sort of side effect needs to be achieved when creating the object. For example, say your class has a couple of configurations of arguments where if you specified one, you had to use its companion, but if you specified another, it had to be paired with something else.
02:09
You could enforce this inside the .__init__()
, or you could provide two factory methods with the required arguments. Granted, both these cases are probably a sign that your code is too complicated.
02:21 Part three of this course talks about the why of object-oriented design. For now, I’ll stick with the how. Finally (all right, I didn’t mean that as programmer pun, but I’m going to leave it right there), you have static methods. Instance methods need an object.
02:38 Class methods need a reference to the class. Static methods are distinguished by requiring neither of those things. Like with class methods, you create this using a decorator.
02:50 It can take arguments, but none are required. They’re typically used to group data-less functions together. For example, kilometers-to-miles and liters-to-gallons conversions, both inside of a converter class. Of course, this kind of grouping can be done equally well with a module.
03:09
I’m honestly not sure why static methods are included in Python. I suspect it’s because they’re there in other object-oriented languages. Any use case I can think of can be done equally well with a module or a class method. For example, if I recall correctly, all the math functions are grouped together as static methods in JavaScript on the Math
class. In Python, they’re in the math
module instead.
03:34
This isn’t one I tend to use myself, but let’s go look at some examples. Here, I have a Vehicle
class. First, let’s look at the .water_vehicle()
factory, which is a class method.
03:49
I indicate that with the @classmethod
decorator. Notice the use of cls
, pronounced class, as the first argument, the class method’s equivalent of self
.
04:01
As this is intended as a factory, its job is to construct a Vehicle
object. So that’s the first thing it does. And then I set some attributes, and then because it is a factory, which is like a constructor, I return it.
04:16
And why might I do this instead of say just using a .__init__()
? Well, this ensures that the name and dimensions are required when constructing a water vehicle. By contrast, the name, dimensions, and number of wheels is required when creating a road vehicle. This one’s also a factory, but with different arguments. Like the water vehicle, it constructs a new Vehicle
object and sets its attributes
04:45
and then returns it. Nothing new here. This is a regular old instance method, taking self
as an argument. And here is a static method. Because it is a static method, it doesn’t have self
or cls
as an argument.
05:05
This .all_float()
method is a utility that checks if all the vehicles passed into it float. If you haven’t seen the star notation here before, it means the method can take any number of arguments. Inside the method, the vehicle
argument is used as an iterable to iterate over all the arguments passed in, and that’s actually what this method does.
05:28
It iterates over all the vehicles passed in and returns True
if all of them float. Let’s create some vehicles.
05:38 First I import the class,
05:44
and here I’ve used the .water_vehicle()
factory, which is a class method, to create a new vehicle. The .water_vehicle()
factory requires a name for the vehicle and some dimensions.
05:55 Instead of light-years, let’s say these are microns. It’s a very tiny boat.
06:02
The factory called the constructor, instantiated the object, and returned it, so I can now use the boat
object and see its attributes like any other.
06:13
The factory set other values as well, so not just those passed in. Boats don’t have wheels. Steering wheels don’t count. So rather than a default argument value and the chance that someone will set it in .__init__()
, the factory pattern makes sure here that it is zero.
06:34 The volume is an instance method which bases its calculation on the dimensions tuple. Twelve thousand cubic microns it is. Let’s create another vehicle.
06:47 This time, I’ve used the other factory, also a class method. This factory takes three arguments: the name, the dimension tuple, and the number of wheels. The method itself, of course, takes four, the first argument being the class, but when you’re invoking it here like I did, you don’t talk about that. There’s the name …
07:08 and the number of wheels and its volume. Now let’s use the static method.
07:20 You call static methods like you call class methods, on the class itself. Static methods don’t have a class or object passed to them though, so it can only deal with the arguments passed into the method.
07:35
Passing in this boat returns True
, as boats can float.
07:42 I can pass the boat in twice. Remember, the algorithm doesn’t care. It doesn’t consider that it’s the same thing twice. It just iterates through all the arguments and checks if each can float.
07:59 Adding the car to the call tells me something in the argument list can’t float. It’s not entirely fair. Cars can float, just not for very long.
08:11 You’ve seen a lot of little bits and pieces of sample code. It’s time to put it all together into a working example.
Christopher Trudeau RP Team on Sept. 23, 2023
Hi cordovez,
Factories get used when you have variations on a thing that you need to construct. IIRC, the example in the course is to have two classmethods which have different sets of arguments that get used together, and instead of constructing the class by hand and using the right set of arguments, you call the factory instead.
It isn’t quite as common in Python, but Java uses it heavily. You’ll frequently even see classes that are factories of other classes.
As an example in practice, I used to maintain a library that generated the style files needed to change the look-and-feel of Bootstrap (I haven’t touched it in forever and it is several versions out of date, so it might as well be dead). It had a class representing all the kinds of items in Bootstrap: buttons, bullets, etc. You could create this class either from a saved file, or by constructing it using the kind of style file that Bootstrap is built on: a SaaS file. So my BStrapVars class had a factory_from_Saas()
and a factory_from_file()
method.
To your question about subclasses, yes, you can approach that way instead. I could have had a BStrapVarsSaaS class and a BStrapVarsFile class, that did nothing more than declare different __init__
methods, and otherwise just use the base class for functionality. The only downside of this approach is that you really should make the parent class purely abstract (covered in part 2 of the course) so somebody doesn’t construct the parent directly.
I’ve also used factory methods as shortcuts. I have a library that does code presentation. Inside of it there is a lexer that parses the code for syntax highlighting. The lexer can be constructed directly, but it has a large number of parameters. The factory method wraps the constructor and takes a single shortcut term. Call the factory with “python” and it sets the lexer for parsing a python file, call it with “REPL” and it sets the lexer for parsing python in a REPL, which is similar but subtly different. In this case I definitely prefer the factory over subclasses, because the factories are mostly one-liners, and having a class for every one of them would be overkill in my opinion.
Hope this gives you a bit more insight.
Become a Member to join the conversation.
cordovez on Sept. 22, 2023
The factory pattern seems very elegant and useful, yet I’m barely wrapping my head around it. Any other examples of how it is used? Or perhaps another tutorial?
I am wondering if breaking this out into two subclasses that inherit from Vehicle is the same thing? Perhaps this will be discussed later in inheritance.