Locked learning resources

You must own this product to watch this lesson.

Locked learning resources

You must own this product to watch this lesson.

OOP Method Types Comparison

00:00 Hey there! It’s Dan. Today I want to talk about the difference between class methods, static methods, and plain, or regular, instance methods in Python. Kind of demystify some of differences between them, when you would use each, what are some of the common scenarios, and how you can use them to actually make your code cleaner and more maintainable and easier to work with in the future.

00:24 Okay, so let’s jump right in. I want to make this really hands-on and work from a simple example that I can use to show you the difference. So, I created this class, I call it MyClass. It’s just a really simple example, and it’s got a couple of different method types on it.

00:42 So, the first one is just called method(), or the method is called method(), and it’s just a plain instance method. And you can see here, it takes one argument, the self argument, which points to the object instance.

00:57 That means within .method(), we can actually modify or read attributes on the object instance. And when you compare that to a class method—so, I just called this one classmethod() and then use the @classmethod decorator to actually mark it as a class method—you can see here that the .classmethod() only has access to this cls (class) parameter,

01:25 or cls argument. It doesn’t have have a self argument. So, this means a class method can only access the class itself, or the object representing the class, because well, everything is an object in Python. But the key difference is that when I create an instance of MyClass and call .classmethod() on it, it won’t be able to actually access the self object, right? So, it can only access attributes that actually exists on the class itself, and not on the instance. Now, with a static method, again, the approach is really similar.

02:03 You just define a regular method and then mark it with the @staticmethod decorator. What you can see here is that it doesn’t take any arguments at all, so it has no access to the class or the object instance at all.

02:19 It’s completely separate from that, and it’s really just a way to namespace your methods. So now, you know, I know this is very theoretical at this point, and it’s going to become much more clear when we actually try and do some experimentation with this stuff—some hands-on work.

02:39 So, the first thing I want to show you here is that when I create an object instance based on this class, I can actually call any of these method types on that object.

02:53 So, I can call a plain method—and I kind of structured these methods in a way that they return a string that kind of explains what’s going on. All right, so when I call obj.method(), we can see here that, well, we called the instance method and we had this self object pointing to an instance of MyClass.

03:16 Now, I can do the same thing with a class method, and now when I call this, you can see here that, again, we’re calling this .classmethod() method, and this time we also have access to this class object.

03:33 Now, the difference is that with the instance method, we actually had access to the object instance, right? So, this was an instance of a MyClass object.

03:44 Whereas with the class method, we have access to the class itself, but not to any of the objects because the class method doesn’t really care about an object existing. However, you can both call .classmethod() and .staticmethod() on an object instance.

04:06 This is going to work—it doesn’t really make a difference. So again, when you call .staticmethod() here, it’s going to work and it’s going to know which method you want to call, but really, the key difference now is going to be that we can also say, okay, MyClass—and I’m not creating an instance of this class—and I can go in and say .classmethod(), for example.

04:29 This is going to work fine. And I can also go ahead and call the .staticmethod(). This is also going to work fine. But of course, when I try and call .method(), that’s going to fail because we didn’t actually call it with a class instance.

04:48 So, I hope this makes this distinction between regular methods and static and class methods a little bit more clear. Now, of course, the big question is, “Okay, why…why do we need that? Why… why is that a thing?” And I want to go over some examples here of what you can use these methods for,

05:09 because I think they’re actually a really powerful concept, or a really powerful tool, for you to structure your code in a way that makes the most sense.

05:19 Okay. So, this is what I came up with: the classical pizza example for teaching object-oriented programming. So what I’ve done here is I defined this really simple Pizza class.

05:30 It’s got a constructor that takes some arbitrary ingredients object—we’re just going to assume it’s some kind of list or container with these ingredients—and then also put a .__repr__() on it so we can nicely format it as a string. And in here, if you’re wondering what that is—so, that is the new format strings in Python 3.6, which are really awesome, so I highly encourage you to try that out.

05:52 You could also just use regular format strings, of course. So, okay. Basically, what I did here, is I created this Pizza class and now we can use it to create Pizza objects, right? And so, if I’m not mistaken, that’s a Margherita?

06:07 Um…my wife’s Italian—you would probably kick my ass if I got that wrong—but I think that’s a Margherita. Well, what you’ve seen here is that we can create these Pizza objects, but as we create more and more complicated pizzas—ham, like a prosciutto or something, I don’t know. Maybe we need some mushrooms on that, as well.

06:33 And you can already tell I’m struggling with the naming here, right? I can create all of these wonderful pizzas here, but I need to remember all of these ingredients.

06:43 So now, it wouldn’t be too much of a stretch to actually solve this problem with a static method. Okay. So, I wanted to make it a little bit easier for us to create new Pizza objects without having to remember all of these ingredients.

06:56 A really good way to structure this, in my opinion, is to actually use class methods to have different factory functions for the different types of pizza you can create. I’m going to show you how this works in a minute now.

07:10 So, what I’m going to do here is I’m going to define a margherita() class method here,

07:17 and then that’s just going to create a new instance of the class…

07:23 and let me just type that out here. What I’m doing here is whenever this .margherita() method is called—and we can call it on just a Pizza class, we don’t actually need a real Pizza instance—I’m just going to create an instance of a Pizza or, you know, whatever the class is named. Like, the nice thing here is that I don’t have to refer to the name up here, so I can keep that name just in one place and whenever I update it I don’t have to worry about changing the rest of the code, but it’s just going to use the class object’s—it’s just going to call this .__init__() method, here, and it’s going to create a new Pizza with these ingredients.

08:07 And so, this is a really maintainable way to do these factory functions. I could also have a .prosciutto()I didn’t actually look the spelling up for this, all right?

08:20 So, if any Italians are watching this…

08:25 then let me know if I screwed this up!

08:29 So here, we actually want a cheese—I guess it would be 'mozzarella', right, and not 'cheese', but whatever—it’s a kind of cheese. Anyway, this isn’t about cheese—this is about Python. So, okay.

08:45 I’m creating a different kind of pizza here, and now when I finish defining this class, I can actually say, “Hey,

08:57 I want a Margherita,”

09:00 and that returns a new Pizza object, right? I could have also called this make_margherita() or new_margherita(), or something like that, right?

09:08 Just to kind of have a better naming scheme. But the same thing is going to work with the .prosciutto(). And I feel like this is a really good use for these class methods.

09:22 If you have classes with complicated constructors that take a lot of arguments and you want to provide a simplified interface for your users, then I think using a class method in this fashion can be really beneficial, and it’s just going to make the API a little bit easier for people to work with.

09:40 So, this is one example of where I would use a static method. I mean, of course, you could always argue that maybe this should be a separate function, yada yada yada, but I think in some cases this could really work well if you structure your classes that way. Up next, when to use static methods. So it’s a little bit hard to come up with a really simple example here, but you know, I’m going to keep stretching the pizza thing here. So, okay. This is what I came up with.

10:10 Basically, a static method doesn’t have access to the class or the object instance at all, right? And now, that’s a pretty big limitation, but it’s also a really good signal to show that a particular method is really independent from everything else around it, right? So for example, if I flag this as a static method, it’s pretty clear that this method, it’s probably not going to change the object or the class in any way, because it’s not really meant to have access to it. I mean, sure, you know, you could probably work around that and it could kind of work with a global variable, but in general, it’s a pretty good hint that this is a self-contained method, which has all kinds of benefits for later testing this thing and just kind of understanding the limitations of this method.

11:07 And so in some cases, it can really be helpful to make your code easier to maintain in the future, because you’re really communicating this intent where someone else reading this code can pretty clearly understand that in this case, our little ._circle_area() function here—it’s not going to modify the state of this object itself. So, let me walk you through this example real quick.

11:29 So basically, what I’ve added here—well, I kind of changed the constructor around a bit, so now we’ve got a radius argument here for the pizza as well.

11:38 I forgot to update the .__repr__(), but you know, it doesn’t really matter for now. And then I added this .area() function. And, well, I could have just calculated the area of the pizza directly in here, but I wanted to have a static method in there.

11:51 And so basically, what I did there instead of calculating the area directly with an expression here, I’m just offloading that to the ._circle_area() function, and then the ._circle_area() function just takes an r, which is the radius, and uses the classic r ^ 2 * pi formula to calculate the circle area.

12:13 So, you know, this is honestly kind of a simplistic example, and you usually wouldn’t implement it like that, but it goes to show the concept. And now what happens here is I can instantiate a Pizza.

12:27 So for example, this is a really sad pizza with like 4.5well, we don’t have a unit on that—let’s say meters. A giant pizza, cheese only. And we’re going to create this object, this Pizza object, and then we’re going to call the .area() function on it, and it’s going to give us this result.

12:48 So, the way this is calculated is that .area() actually forks off all that work to the ._circle_area() function, and now the ._circle_area() function is actually completely independent, and I also used a single underscore (_) to mark it as not part of the public API of this class, but kind of an internal implementation detail. But nevertheless, now the cool thing is that we have this ._circle_area() function that is completely independent from the rest of the object, and it can’t actually modify the object’s state.

13:26 This is a pretty good hint for someone else reading the code and it also simplifies testing because when I write my tests for this, then I don’t have to worry about instantiating a Pizza object, making sure it doesn’t get modified, because the ._circle_area() helper—it can’t do that. Right? And so I occasionally use static methods to communicate that intent and to keep my helper functions nice and clean and make sure that they’re not modifying the object’s state all over the place. Again, just a quick recap. So we talked about plain methods, class methods, and static methods, and when you can call them. Pretty much all you need to remember is that a regular method needs an object instance to be called on; and in a class method, it needs a class and it has access to the class; and a static method doesn’t really have any access to the object, or an object instance, or the class at all,

14:24 and it’s just kind of a way to namespace your functions. All right, so have fun exploring the pizza analogy here and building the perfect pizza API in an object-oriented fashion. Talk to you soon, and happy Pythoning!

Avatar image for pfinardi

pfinardi on Aug. 16, 2020

thanks for this video! Help me to demystify the difference between classmethod and staticmethod.

Avatar image for Dan B

Dan B on Nov. 10, 2020

My first thought was that I could build various constructor, e.g. from_file, from_db, from_api, etc. using @classmethod, but I think that’s wrong…

At least I see no advantage to using it that way.

How would you design a class with various constructors that way?

How about this?

class X():
    def __init__(self, file_name = None, db_name = None):

        if file_name is not None:
            self.from_file(file_name)

        if db_name is not None:
            self.from_db(file_name)

       ...

That way you can call X(file_name = 'blah') or x = X() and then x.from_file(file_name).

Not sure if that is pythonic?

Avatar image for Bartosz Zaczyński

Bartosz Zaczyński RP Team on Nov. 12, 2020

@Dan B Your thinking was right, and I don’t see why you wouldn’t take advantage of the @classmethod decorator to create so-called named constructors. Take a look at the following example:

from collections import namedtuple

class Color(namedtuple('Color', 'r g b')):

    @classmethod
    def exotic_red(cls):
        return cls(1, 0, 0)

    @classmethod
    def monaco_blue(cls, metallic=False):
        if metallic:
            raise Exception('Not available')
        return cls(0.2, 0.5, 0.75)

These methods don’t force you to hardcode the class name because of the cls parameter representing the Color class.

The .__init__() method should only accept some initial state to store in the object, whereas in your example, it contains business logic that forks into a few paths depending on how it was called. Moreover, it lets you create an object with a broken state, which doesn’t have any data loaded from a file nor a database.

Avatar image for Dan B

Dan B on Nov. 12, 2020

Sorry Bartosz, I’m confused…

Where to learn about classes and what should / shouldn’t be in an __init__ method?

My thinking was that it’s just an ‘empty’ individual if neither a file nor a database is given. Is that bad practice?

I’m quite confused by how you’re building the class with a namedtuple (I’ve been using namedtupels for a while, so I think I know how they work, it’s classes I’m not good at).

Many thanks, Dan.

P.S. Also… not sure how to @ you either! Thanks.

Avatar image for Dan B

Dan B on Nov. 12, 2020

Ahh… soo… namedtuple returns a class… so your Color class is just the subclass of the named tuple class that gets ‘called’ when the constructor is wah…

Avatar image for Dan B

Dan B on Nov. 12, 2020

Sorry, I got confused about this: florimond.dev/blog/articles/2018/08/python-mutable-defaults-are-the-source-of-all-evil/

Because tuples are immutable, I guess it’s OK to use it in the class definition here.

I still don’t see how I’d use @classmethod to build a class from a file that had any advantage over just a Class with a ‘from_file’ method.

Avatar image for Bartosz Zaczyński

Bartosz Zaczyński RP Team on Nov. 12, 2020

@Dan B

Sorry Bartosz, I’m confused…

Maybe that wasn’t the most straightforward example. You can forget about the namedtuple and replace it with a little bit of code written by hand:

class Color:

    @classmethod
    def exotic_red(cls):
        return cls(1, 0, 0)

    @classmethod
    def monaco_blue(cls, metallic=False):
        if metallic:
            raise Exception('Not available')
        return cls(0.2, 0.5, 0.75)

    def __init__(self, r, g, b):
        self.r, self.g, self.b = r, g, b

    def __repr__(self):
        return f"Color(r={self.r}, g={self.g}, b={self.b})"

Where to learn about classes and what should / shouldn’t be in an __init__ method?

It’s a matter of practice and learning about good programming principles in general through code reviews, for example.

P.S. Also… not sure how to @ you either! Thanks

There’s no way to @-mention someone at the moment. I simply write someone’s name and bold it so that we have an idea of how to navigate the conversation in a flat list of comments.

I still don’t see how I’d use @classmethod to build a class from a file that had any advantage over just a class with a from_file method.

That’s how you could use the @classmethod decorator as a named constructor in your example:

class X():

    @classmethod
    def from_file(cls, filename):
        with open(filename) as fp:
            return cls(fp.read())

    @classmethod
    def from_db(cls, database):
        with connect(database, ...) as db:
            with db.cursor() as cursor:
                return cls(cursor.execute("SELECT * FROM ..."))

    def __init__(self, data):
        self.data = data

But you could also use the @staticmethod decorator or move those methods out from the class entirely and use regular functions. It’s a question where this code belongs to logically.

The advantage to using named constructors over building the object yourself in steps is that you can’t have a partially initialized object. It’s either initialized fully or not at all, which follows the fail-fast principle.

Avatar image for Pavneet Ghai

Pavneet Ghai on Feb. 13, 2022

Which Editor is this ?

Avatar image for Dan Bader

Dan Bader RP Team on Feb. 13, 2022

I’m using an alternative Python REPL called bpython in my videos. You can learn more about it here: bpython-interpreter.org. If bpython is difficult to install, I can also recommend ptpython.

I’m running the REPL inside iTerm 2 on macOS.

You must own this product to join the conversation.