[MUSIC PLAYING] CARTER ZENKE: Well, hello one and all, and welcome to our short on instance methods. Now, we've seen in prior shorts on defining classes and instance variables ways to define some template for some object and ways to instantiate individual instances of those templates to refer to in code and access their various instance variables, or traits, of each of those individual, in this case packages. So we have here again a class called package that encapsulates some information about packages in general. We say that every package we'll create will have a number, like an ID, a sender, a recipient, and a weight to it. And we then define here the various instance variables whenever we create a new instance of this class. Down below, on lines 11 and 12, we actually create these instances. We have here on line 11 a package whose number is 1, whose sender is Alice, whose recipient is Bob, and whose weight is 10. And then, finally, down below on line 14, we have some code here that actually accesses the instance variables to print out in a more pretty format the package information itself. So let's try running this and see the output down below once more. I'll run Python of packages.py, and we'll see, thanks to the code that we all have here, we're able to print information on these packages while still encapsulating combine the information in a more organized way. Now, it turns out that I can use classes to not just encapsulate or combine pieces of information but to also encapsulate functionality that I want to be common across all individual packages. And I can do so by defining, in this case, what I might call an instance method, a method that can be run on any instance of this particular class. Now, when I say method here, I'm really referring to a particular kind of function that is part of a class. And I want to show you one that you might find handy, one called dunder S-T-R, dunder STR. So similar to dunder init, dunder STR is a special instance method that can be called if I were to print some package. Now, down below here, on line 15, this is a long line of code. But wouldn't it be nice, let's say, if I could simply print a package like this-- print package-- and get, let's say, the same format I have down below? Right now, this will not be the case. If I run python of package.py, I'll see this, which is not very pretty at all. But I could add an instance method called dunder STR to actually improve the output of, in this case, what I see when I call print on any given instance of my package class. So let's go back up here. And to define some new instance method, I can simply drop down a line and define some new function that's part of my class. And because this one is a special method defined as some special functionality by Python itself, I'll use these double underscores and then STR. In fact, the double underscores, pronounced as dunder, they tend to refer to these special methods that Python attaches some special meaning to. In this case, dunder STR is actually called every time I print-- every time I call print on an instance of my individual package. So by convention, dunder STR takes as input, in this case-- in this case, the instance of the class itself that we refer to as self. And within dunder STR, I can return some string that will be printed every time I call print on an instance of the package. So just as a test here, why don't I say-- why don't I say "This is a package." "This is a package." So every time I call print on a package, hopefully I'll see "This is a package." I'll go ahead and run-- I'll go ahead and run Python of packages.py. And for both of those within my list here, this package and this package, we'll see "This is a package," which is helpful but only a little bit. Ideally, I'd show some information on the actual instance of the package I'm talking about. So to do so, I can again access these instance variables and return, let's say, something like an f string that's in the same format we had down below. I could say package again and then self.number, colon, maybe self.sender to self.recipient, and then a comma, self.weight kg, just like this. So this is the same f string we had in that for loop. But now I've encapsulated that same f string inside of my package class within this instance method called dunder STR. Now, ideally, when I run Python of packages.py, we'll see some more information on this package. But much more simply now, we can simply call print package. I'll run this, and we'll see that same information now part of some instance method called dunder STR. So pretty handy here. Now, what else could we actually use instance methods for? Well, we want to maybe attach functionality that seems pretty core to the idea of being a package. Maybe one thing we want packages to be able to do is calculate the cost of shipping them. So I could imagine here defining another instance method, maybe one called calculate cost, that allows a package to figure out how much it costs itself to ship. So I could define here maybe a new method called calculate_cost. And these instance methods, by default, again, take as input as the first argument the instance itself that we refer to as this variable, this argument called self. Now, as another input to calculate cost, maybe the cost changes. It may be a cost per kilogram that I could specify as input to this particular method here. I'll type a colon. And within this particular function body I can then define this instance method called calculate_cost. Let's first see how to use this instance method though. Maybe instead of going down here and just printing the package, I could do something a little more fancy. I could say, why don't we print the package itself as part of this f string but then also say that it costs-- it costs some amount? Now that I have this function called calculate_cost that is yet unimplemented, I could use it as follows. For each instance in this case that we're calling package in this for loop, I could call calculate_cost by typing a dot and then the function the actual method name, so calculate_cost. And now as input to calculate_cost, I could actually type in the input I'm expecting, cost_per_kg. So I'll say cost_per_kg. And why don't we say it's about 2 US dollars per kilogram to ship some given package? So this is how I want to use calculate_cost, but now let's implement it up on line 11. Well, I want to make sure to return here the total cost of the package. I actually know the cost per kilogram. So to do that, I think I just need some multiplication. I could maybe find the weight in kilograms and multiply it by the cost per kilogram. So I could say maybe self.weight, again accessing some instance variable, and multiply that by the cost per kilogram. And we'll make the assumption here the user knows that weight should be in kilograms. So we'll go ahead and say this is calculate_cost. It takes the weight in kilograms, multiplies it by the cost per kilogram, and that is the cost of shipping this particular package. So let's try it down below. Why don't I go ahead and run Python of packages.pi? And now we'll see package 1, Alice to Bob, 10 kg costs 20, and package 2 costs 10. Could probably improve this a little bit. We could maybe add a dollar sign here to say this is in US dollars. We'll run packages.py again, and now we'll see some pretty good formatting here. So this was our brief foray into instance methods. There are some special ones like dunder STR that allows to say how a instance should be printed, as we saw down below. We can also add some functionality, encapsulate functionality, in this case like one to calculate the cost of shipping some given package. This then was our short on instance methods. And we'll see you next time.