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.
Andras on Dec. 31, 2024
On one hand I find it very elegant that Vehicle
doesn’t need to override the default __init__()
(from object
) and we can still create different vehicles. I know this is allowed in Python as instance attributes can be set anywhere the instance is available. On the other hand, if we don’t have __init__
, where should we look for the full set of attributes of a vehicle? The set of attributes that really define a Vehicle
object. The attributes that I may want to use in client code expecting Vehicle
instances. What if we had 20 class methods, all returning a different vehicle? In theory they all could just add the relevant subset of attributes. So I feel like I would have to scroll though all of them and find the union of attributes they define to really have an understanding of what a vehicle can be. My naive approach in this example would have been to add an __init__
with the four attributes name
, dimensions
, floats
, num_wheels
, and call it from the class methods, potentially hard coding a few parameters. I understand it is ugly, but it would also protect against creating empty vehicles with Vehicle()
, which you can do now. Btw, is there any way to make this default construction Vehicle()
non-public, like in C++ or Java?
Andras on Dec. 31, 2024
One more thing: was there a reason for writing Vehicle()
in the class methods rather than cls()
? I know there is no functional difference for this little piece of code, but for larger class hierarchies with inheritance I think the latter is the recommended approach.
Christopher Trudeau RP Team on Jan. 1, 2025
Hi Andras,
Don’t read too much into the style of the code in this case, as its primary purpose was to illustrate the @classmethod
and @staticmethod
decorators and how they get used. The example is a little contrived, and I agree at least the common attributes could have been put in the initializer.
I might even take that one step further and say that this code implies that you probably shouldn’t have a water and road vehicle in the same class. If you’re finding that there are cases where certain attributes aren’t meaningful for a subset of your use, it might be a sign that it shouldn’t be a single class. The other two parts of this course delve into this a bit deeper.
Python doesn’t strictly have a private/protected concept. There are some conventions like using a _
prefix to indicate that folks outside the class shouldn’t touch something, but it isn’t enforced by the compiler. There is also the __
prefix that gets used for system-like methods (like __init__
), and that hides things away a little, but it can still be accessed.
Many years ago I was a Java programmer and I was taught to think about protecting others from the code. “This is my internal stuff, nobody should touch it”. As I moved to Python I eventually ended up with a much more lax attitude. If they’ve got access to the code, why do I care so much about what is an isn’t private or protected? You want to signal to other developers that something might change, but why worry so much about it? By the time they’re elbow deep in the code, they’re already mucking around. So my attitude has changed from “protect all the things” to “meh, whatever”. If I switched back to Java I think I’d use private/protected a lot less.
The one place I find this is still important and a downside of this lax-ness, is when you want to use a factory method to construct a class. Consumers of your class are supposed to call the factory to make an object not the __init__
directly. As there is no way to stop them, and almost all classes have a __init__
, it is a bit error prone that you can’t protect it better.
As to your last question about using cls
, yes, that is the recommended mechanism because if you want to rename your class, you don’t have to find all usages of the class name. As to why I did it this way when I wrote this example, I’m not sure. It might have been a conscious decision as folks new to the topic sometimes get tripped up on the difference between cls
and self
… or it could have just been where my head was at that day :)
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.