WEBVTT 1 01:00:00.000 --> 01:00:03.458 [CLASSICAL MUSIC] 2 01:00:03.458 --> 01:00:23.875 3 01:00:23.875 --> 01:00:26.583 DAVID J. MALAN: All right, this is CS50's Introduction 4 01:00:26.583 --> 01:00:27.916 to Programming with Python. 5 01:00:27.916 --> 01:00:31.541 My name is David Malan, and this is our week on object-oriented programming, 6 01:00:31.541 --> 01:00:32.791 or OOP. 7 01:00:32.791 --> 01:00:34.750 It turns out that, in the world of programming, 8 01:00:34.750 --> 01:00:37.583 there's different paradigms of programming languages. 9 01:00:37.583 --> 01:00:40.208 There's different ways of solving problems with code, 10 01:00:40.208 --> 01:00:42.000 and it's a little hard to see this at first 11 01:00:42.000 --> 01:00:43.916 if you've only learned one language. 12 01:00:43.916 --> 01:00:47.416 But over time, if and when you learn other languages besides Python, 13 01:00:47.416 --> 01:00:50.083 you'll start to notice certain patterns and certain capabilities 14 01:00:50.083 --> 01:00:52.000 of some languages but not another. 15 01:00:52.000 --> 01:00:54.791 Thus far, within the world of Python, you and I 16 01:00:54.791 --> 01:00:58.541 have largely been writing code that's procedural in nature, whereby we're 17 01:00:58.541 --> 01:01:00.708 writing procedures; we're writing functions; 18 01:01:00.708 --> 01:01:03.166 and we're doing things top to bottom. 19 01:01:03.166 --> 01:01:05.458 Everything is step by step by step, as you would 20 01:01:05.458 --> 01:01:07.208 expect in general from an algorithm. 21 01:01:07.208 --> 01:01:09.250 But along the way, we've actually dabbled 22 01:01:09.250 --> 01:01:14.083 in another paradigm known as functional programming with Python whereby we've 23 01:01:14.083 --> 01:01:15.875 been able to pass functions around. 24 01:01:15.875 --> 01:01:18.666 We even had an anonymous function some weeks ago. 25 01:01:18.666 --> 01:01:22.500 And that's evidence of features of a functional programming language, 26 01:01:22.500 --> 01:01:24.708 even though we've just scratched the surface thereof. 27 01:01:24.708 --> 01:01:28.083 Today we focus on another paradigm, and this one in more detail-- 28 01:01:28.083 --> 01:01:30.166 namely object-oriented programming. 29 01:01:30.166 --> 01:01:32.958 And now, while some of you might have prior programming experience 30 01:01:32.958 --> 01:01:36.500 and have learned languages like Java, which are, by design, fundamentally 31 01:01:36.500 --> 01:01:40.291 object-oriented, Python indeed allows you a bit of flexibility 32 01:01:40.291 --> 01:01:43.250 when it comes to how you solve problems with code. 33 01:01:43.250 --> 01:01:46.458 But it turns out object-oriented programming 34 01:01:46.458 --> 01:01:49.000 is a pretty compelling solution to problems 35 01:01:49.000 --> 01:01:52.666 that you invariably encounter as your programs get longer, larger, 36 01:01:52.666 --> 01:01:54.375 and more complicated. 37 01:01:54.375 --> 01:01:56.333 So indeed, OOP, for our purposes, is going 38 01:01:56.333 --> 01:02:00.791 to be a solution to a problem that builds on so many of the lessons past. 39 01:02:00.791 --> 01:02:02.458 So let's go ahead and do this. 40 01:02:02.458 --> 01:02:05.833 Let's start by writing a program very procedurally 41 01:02:05.833 --> 01:02:07.625 by opening up VS Code here. 42 01:02:07.625 --> 01:02:10.875 I'm going to go ahead and create a program called student.py. 43 01:02:10.875 --> 01:02:14.083 And in this program, I want to do something relatively simple 44 01:02:14.083 --> 01:02:18.375 initially, as we might have done some weeks ago now, where I just 45 01:02:18.375 --> 01:02:22.166 ask a user for their name and, maybe in the context of the Harry Potter 46 01:02:22.166 --> 01:02:26.291 universe, their house, and just print out where that student is from. 47 01:02:26.291 --> 01:02:30.166 And let's gradually enhance this program by adding more and more features to it 48 01:02:30.166 --> 01:02:33.875 and see if we don't stumble upon problems that, up until now, 49 01:02:33.875 --> 01:02:36.958 we might not have had very elegant, well-designed solutions to. 50 01:02:36.958 --> 01:02:40.250 But if we introduce, explicitly, object-oriented programming 51 01:02:40.250 --> 01:02:43.250 as a programming technique, I bet we can clean up our code 52 01:02:43.250 --> 01:02:47.208 and set the stage for writing even more sophisticated programs, longer programs 53 01:02:47.208 --> 01:02:48.083 down the line. 54 01:02:48.083 --> 01:02:51.791 So in student.py, let me go ahead and do a name variable, 55 01:02:51.791 --> 01:02:54.250 setting it equal to the return value of input, 56 01:02:54.250 --> 01:02:57.041 and just prompt the user for their name like this. 57 01:02:57.041 --> 01:02:59.791 And then let me go ahead and do the same for a house variable 58 01:02:59.791 --> 01:03:03.041 and prompt the user for their house, using input like this. 59 01:03:03.041 --> 01:03:05.208 And let's do something super simple now. 60 01:03:05.208 --> 01:03:07.875 Let's just go ahead and print out an f string 61 01:03:07.875 --> 01:03:11.291 that says something like name from house, 62 01:03:11.291 --> 01:03:13.958 just so that I can confirm that the contents of these variables 63 01:03:13.958 --> 01:03:15.000 are indeed as I expect. 64 01:03:15.000 --> 01:03:17.083 I'm not going to do any error checking or trimming 65 01:03:17.083 --> 01:03:18.333 or anything like that for now. 66 01:03:18.333 --> 01:03:21.416 I'm really just going to spit back out whatever the user just typed in. 67 01:03:21.416 --> 01:03:24.375 All right, let me go ahead and run Python of student.py. 68 01:03:24.375 --> 01:03:29.083 Let's use our go-to, like Harry, as in Harry Potter, from Gryffindor. 69 01:03:29.083 --> 01:03:32.958 And when I hit Enter, now let's see if I see that Harry from Gryffindor 70 01:03:32.958 --> 01:03:34.041 is indeed the case. 71 01:03:34.041 --> 01:03:37.375 All right, so I think we have a working program at this point, but let's 72 01:03:37.375 --> 01:03:40.541 now introduce some of those lessons learned way back from week zero 73 01:03:40.541 --> 01:03:42.583 where we started writing our own functions, 74 01:03:42.583 --> 01:03:46.250 not necessarily because it solves the problem more correctly-- 75 01:03:46.250 --> 01:03:47.916 I daresay this is correct as is. 76 01:03:47.916 --> 01:03:52.041 But it begins to give us building blocks that we can extend so 77 01:03:52.041 --> 01:03:54.125 as to solve more complicated programs. 78 01:03:54.125 --> 01:03:58.375 So let me go back up to student.py, and let's go ahead now and do this. 79 01:03:58.375 --> 01:04:01.875 Let's put the entire logic I just wrote inside of our typical method 80 01:04:01.875 --> 01:04:05.458 called main, and let me indent those three lines so that at least they're 81 01:04:05.458 --> 01:04:07.333 now combined into one main method. 82 01:04:07.333 --> 01:04:12.208 But instead of using input on line 2 an input on line 3, 83 01:04:12.208 --> 01:04:14.083 don't we go ahead and assume, for the moment, 84 01:04:14.083 --> 01:04:17.000 that we've got some function called get_name in the world, 85 01:04:17.000 --> 01:04:19.958 and let's go ahead and assume we've got another function like get_house 86 01:04:19.958 --> 01:04:21.708 in the world that don't take parameters. 87 01:04:21.708 --> 01:04:24.166 But their purpose in life is, by their name, 88 01:04:24.166 --> 01:04:28.458 going to be to get the user's name and to get their users house, respectively. 89 01:04:28.458 --> 01:04:32.083 And then I'm going to print out the exact same f string as before. 90 01:04:32.083 --> 01:04:34.333 I, of course, need to implement these functions now. 91 01:04:34.333 --> 01:04:38.291 So let me go lower in my file and define a function called get_name. 92 01:04:38.291 --> 01:04:41.375 Nothing in these parentheses because it's not going to take a parameter. 93 01:04:41.375 --> 01:04:44.083 And I'm going to go ahead and do something like name equals input 94 01:04:44.083 --> 01:04:48.750 ("Name"), just like before, and then I'm going to go ahead and return name. 95 01:04:48.750 --> 01:04:52.000 So it's a super simple function, but it's an abstraction. 96 01:04:52.000 --> 01:04:55.458 I now have a function called get_name whose implementation details 97 01:04:55.458 --> 01:04:56.916 I don't have to care about anymore. 98 01:04:56.916 --> 01:04:58.458 I just know that the function exists. 99 01:04:58.458 --> 01:05:00.250 And I can tighten this up, in fact, . 100 01:05:00.250 --> 01:05:02.875 Don't really need a name variable on line 8 101 01:05:02.875 --> 01:05:05.875 if I'm immediately going to return that same name variable on line 9. 102 01:05:05.875 --> 01:05:07.833 So let me just tighten this up a little bit 103 01:05:07.833 --> 01:05:09.791 even though it doesn't change the functionality 104 01:05:09.791 --> 01:05:15.750 and just immediately return the return value of the inputs function call here. 105 01:05:15.750 --> 01:05:18.916 Let's do something very similar now for get_house, 106 01:05:18.916 --> 01:05:20.750 which will similarly take no arguments. 107 01:05:20.750 --> 01:05:22.666 I'm going to go ahead and return the return 108 01:05:22.666 --> 01:05:25.541 value of input, this time prompting the user for their house. 109 01:05:25.541 --> 01:05:27.250 And I need one final detail. 110 01:05:27.250 --> 01:05:32.208 At the very bottom, let's continue our habit of doing if the name of this file 111 01:05:32.208 --> 01:05:35.750 equals, equals, quote, unquote, main, then let's go ahead 112 01:05:35.750 --> 01:05:38.875 and actually call main and recall that we have that in place 113 01:05:38.875 --> 01:05:42.458 so that, if this eventually becomes part of a module, a library of sorts, 114 01:05:42.458 --> 01:05:44.791 I don't accidentally call main blindly. 115 01:05:44.791 --> 01:05:50.250 I only do it if I mean to run main from the command line on this file. 116 01:05:50.250 --> 01:05:53.458 All right, so if I didn't make any mistakes here, let me go ahead 117 01:05:53.458 --> 01:05:57.416 and, in my terminal window, again, run Python of student.py, Enter. 118 01:05:57.416 --> 01:05:59.250 Let's type in Harry, Enter. 119 01:05:59.250 --> 01:06:01.541 Let's type in Gryffindor, Enter. 120 01:06:01.541 --> 01:06:02.875 And we're set. 121 01:06:02.875 --> 01:06:05.583 Harry from Gryffindor seems to still be working. 122 01:06:05.583 --> 01:06:09.416 So we haven't really solved the problem any more correctly, 123 01:06:09.416 --> 01:06:11.875 but I've laid the foundation to maybe now do 124 01:06:11.875 --> 01:06:13.583 some more interesting things because I've 125 01:06:13.583 --> 01:06:16.875 had these building blocks in place. 126 01:06:16.875 --> 01:06:21.083 But let me propose that we could be doing this a little bit differently. 127 01:06:21.083 --> 01:06:23.000 get_name, get_house is fine. 128 01:06:23.000 --> 01:06:26.416 But at the end of the day, I'm really trying to get a student from the user. 129 01:06:26.416 --> 01:06:30.083 I want their name and their house, not just one or the other. 130 01:06:30.083 --> 01:06:32.833 So maybe it would be a little cleaner still 131 01:06:32.833 --> 01:06:36.083 to define a function called get_student and let 132 01:06:36.083 --> 01:06:38.291 get_student do all of this work for us. 133 01:06:38.291 --> 01:06:43.000 Now, theoretically, get_student could call get_name and could call get_house. 134 01:06:43.000 --> 01:06:44.750 But because these functions are so short, 135 01:06:44.750 --> 01:06:48.875 I think I'm OK with just defining one function, called get_student, 136 01:06:48.875 --> 01:06:50.666 that similarly won't take any arguments. 137 01:06:50.666 --> 01:06:52.250 But it's going to do two things. 138 01:06:52.250 --> 01:06:55.875 It's going to get the students name, by prompting them with input as before. 139 01:06:55.875 --> 01:06:57.625 And it's going to get the student's house, 140 01:06:57.625 --> 01:07:01.125 by also prompting them as before. 141 01:07:01.125 --> 01:07:02.416 Now, hmm. 142 01:07:02.416 --> 01:07:04.833 I want to return the student, but I think 143 01:07:04.833 --> 01:07:06.875 I might have painted myself into a corner 144 01:07:06.875 --> 01:07:10.125 here because I now have two variables-- name and house. 145 01:07:10.125 --> 01:07:13.250 And yet, up until now, we've pretty much returned one or the other. 146 01:07:13.250 --> 01:07:15.000 We've returned one value. 147 01:07:15.000 --> 01:07:17.500 So any suggestions for how we can perhaps 148 01:07:17.500 --> 01:07:22.375 solve this problem that I just created for myself, whereby I want to return, 149 01:07:22.375 --> 01:07:28.083 really, a student, but I currently have a name variable and a house variable. 150 01:07:28.083 --> 01:07:30.541 I'd minimally like to return both of those. 151 01:07:30.541 --> 01:07:33.208 AUDIENCE: I believe that, we can return a dictionary, 152 01:07:33.208 --> 01:07:35.000 includes the name and the house. 153 01:07:35.000 --> 01:07:36.833 DAVID J. MALAN: Yeah, so we absolutely could 154 01:07:36.833 --> 01:07:41.000 return a dictionary-- a dict object in Python, whereby maybe one key is name; 155 01:07:41.000 --> 01:07:43.666 one key is house; and the values thereof are exactly 156 01:07:43.666 --> 01:07:44.916 the values of these variables. 157 01:07:44.916 --> 01:07:46.791 So we could totally do that. 158 01:07:46.791 --> 01:07:49.375 I worry that that might be getting a little complicated. 159 01:07:49.375 --> 01:07:51.916 I wonder if there's a simpler way instead. 160 01:07:51.916 --> 01:07:55.000 Any other instincts-- even if you're not sure it would work? 161 01:07:55.000 --> 01:07:56.541 AUDIENCE: Return both name and house? 162 01:07:56.541 --> 01:07:58.333 DAVID J. MALAN: Return both name and house. 163 01:07:58.333 --> 01:07:59.416 I like the sound of that. 164 01:07:59.416 --> 01:08:00.250 It sounds simple. 165 01:08:00.250 --> 01:08:03.041 I don't have to figure out what a dictionary is going to look like. 166 01:08:03.041 --> 01:08:05.750 And in fact, this, too, would be a valid approach, 167 01:08:05.750 --> 01:08:07.250 even if you've not seen this before. 168 01:08:07.250 --> 01:08:11.625 It turns out, in Python, that you can return multiple values, 169 01:08:11.625 --> 01:08:12.958 but that's a bit of a white lie. 170 01:08:12.958 --> 01:08:16.333 Or we could take Muhammad's approach of actually returning a dictionary 171 01:08:16.333 --> 01:08:17.833 and putting multiple keys therein. 172 01:08:17.833 --> 01:08:19.833 So here, again, we have yet another example 173 01:08:19.833 --> 01:08:22.375 of how you can solve the same problem in at least two ways, 174 01:08:22.375 --> 01:08:24.708 and I daresay we're about to see even more. 175 01:08:24.708 --> 01:08:27.916 So one way you could solve this problem, whereby 176 01:08:27.916 --> 01:08:32.916 you want to return multiple values, would be to do something like this. 177 01:08:32.916 --> 01:08:37.666 I could go ahead and literally return not just name, but I could put a comma 178 01:08:37.666 --> 01:08:39.250 and also return house. 179 01:08:39.250 --> 01:08:41.958 This is not necessarily something you can do in other languages 180 01:08:41.958 --> 01:08:44.166 if you have programmed in other languages before. 181 01:08:44.166 --> 01:08:45.416 It depends on the language. 182 01:08:45.416 --> 01:08:49.291 But it looks like, thanks to this comma, maybe I can, in fact, 183 01:08:49.291 --> 01:08:51.916 return two values as [INAUDIBLE] proposed. 184 01:08:51.916 --> 01:08:55.375 Well, if I'm returning to values in this way on line 10, 185 01:08:55.375 --> 01:08:57.916 how do I get both values at the same time? 186 01:08:57.916 --> 01:08:59.208 Well, there's a couple of ways. 187 01:08:59.208 --> 01:09:01.833 Let me go up to my main function. 188 01:09:01.833 --> 01:09:04.416 I know, minimally, I'm going to have to change the get_name 189 01:09:04.416 --> 01:09:05.958 and get_house to get_student. 190 01:09:05.958 --> 01:09:09.041 But what am I going to store the return value in? 191 01:09:09.041 --> 01:09:11.500 I think I could actually do this. 192 01:09:11.500 --> 01:09:14.916 And we have seen this technique before, where you can unpack, 193 01:09:14.916 --> 01:09:17.541 so to speak, sequences of values that are coming back. 194 01:09:17.541 --> 01:09:21.083 And indeed, consider this to be exactly that. name, 195 01:09:21.083 --> 01:09:25.000 house is some kind of sequence that I'm returning of values-- name, house. 196 01:09:25.000 --> 01:09:27.291 So if I want to unpack those and store the return 197 01:09:27.291 --> 01:09:30.000 values in two separate variables, I can, in fact, 198 01:09:30.000 --> 01:09:34.208 use the commas on the left-hand side of my assignment operator, the equal sign, 199 01:09:34.208 --> 01:09:35.416 to do just that. 200 01:09:35.416 --> 01:09:40.000 Now, to be clear, I don't need to call these variables name and house here. 201 01:09:40.000 --> 01:09:43.500 I could simplify this and use just n here and h here, 202 01:09:43.500 --> 01:09:46.583 and then I could return just n and h. 203 01:09:46.583 --> 01:09:51.208 But I would argue that's not very clear to the reader as to what's going on. 204 01:09:51.208 --> 01:09:52.958 So I think, in this case, even though it's 205 01:09:52.958 --> 01:09:56.000 a coincidence that I've used the same variable names in get_student 206 01:09:56.000 --> 01:09:59.875 and get_name, and in main, it's a little more readable to someone like me. 207 01:09:59.875 --> 01:10:01.541 So I'm going to leave it as is. 208 01:10:01.541 --> 01:10:04.041 Well, let's go ahead and see, now, if this works. 209 01:10:04.041 --> 01:10:07.166 Let me clear my screen down here and run Python of student.py, Enter. 210 01:10:07.166 --> 01:10:08.375 Let's again type in Harry. 211 01:10:08.375 --> 01:10:11.041 Let's again type in Gryffindor, Enter. 212 01:10:11.041 --> 01:10:14.791 And voila, we still see that Harry is from Gryffindor. 213 01:10:14.791 --> 01:10:16.958 But what are we actually doing here? 214 01:10:16.958 --> 01:10:19.666 What are we actually doing by returning this value? 215 01:10:19.666 --> 01:10:24.333 Well, it turns out that what we've just done is use a tuple. 216 01:10:24.333 --> 01:10:29.791 A tuple is another type of data in Python that's a collection of values-- 217 01:10:29.791 --> 01:10:32.291 x, y or x, y, z. 218 01:10:32.291 --> 01:10:36.750 It's similar in spirit to a list, in that sense, but it's immutable. 219 01:10:36.750 --> 01:10:37.666 It's not mutable. 220 01:10:37.666 --> 01:10:38.708 Now, what does that mean? 221 01:10:38.708 --> 01:10:41.708 A list, as we've seen it before, is a data structure in Python 222 01:10:41.708 --> 01:10:43.458 that you can change the values of. 223 01:10:43.458 --> 01:10:47.250 You can go into bracket 0 for the first location and change the value there. 224 01:10:47.250 --> 01:10:50.208 You can go to bracket 1, bracket 2, bracket 3 and actually change 225 01:10:50.208 --> 01:10:51.458 the values in lists. 226 01:10:51.458 --> 01:10:54.791 But if you have no intention of changing the values of variables 227 01:10:54.791 --> 01:10:57.750 and you want to return, effectively, multiple values, 228 01:10:57.750 --> 01:10:59.541 you don't have to even return it as a list. 229 01:10:59.541 --> 01:11:03.416 You can return it as a tuple instead, just by using a comma. 230 01:11:03.416 --> 01:11:07.000 And it turns out we can make explicit that-- here's the white lie. 231 01:11:07.000 --> 01:11:10.500 I'm not actually returning to values per se. 232 01:11:10.500 --> 01:11:13.416 Whenever you use a comma in this way on line 9, 233 01:11:13.416 --> 01:11:17.500 you're actually returning one value, which is a tuple. 234 01:11:17.500 --> 01:11:20.291 Inside of that tuple now are two values. 235 01:11:20.291 --> 01:11:23.791 So it's similar in spirit to returning one list with two thing Here 236 01:11:23.791 --> 01:11:26.291 I'm returning one tuple with two things. 237 01:11:26.291 --> 01:11:28.958 And the mere fact that I've used a comma and nothing else 238 01:11:28.958 --> 01:11:31.375 tells Python that I indeed want to return a tuple. 239 01:11:31.375 --> 01:11:34.083 But there's more explicit syntax that we can use instead. 240 01:11:34.083 --> 01:11:39.291 I can actually-- more verbosely-- put explicit parentheses around the values 241 01:11:39.291 --> 01:11:42.458 of this tuple just to make more clear to me, to the reader 242 01:11:42.458 --> 01:11:44.083 that this isn't two values per se. 243 01:11:44.083 --> 01:11:46.500 This is one value with two things inside of it. 244 01:11:46.500 --> 01:11:48.458 And what I can actually do then, too, is-- 245 01:11:48.458 --> 01:11:51.000 I don't have to unpack this up here, so to speak. 246 01:11:51.000 --> 01:11:54.833 I can actually go up here and maybe give a more apt name, like student, 247 01:11:54.833 --> 01:11:58.375 and I can name the value, or rather name the variable 248 01:11:58.375 --> 01:12:01.333 in which I'm storing the return value of get_student as, 249 01:12:01.333 --> 01:12:02.791 quote, unquote, "student." 250 01:12:02.791 --> 01:12:05.166 So maybe this is a little better design now 251 01:12:05.166 --> 01:12:08.625 because I'm sort of abstracting away what a student is. 252 01:12:08.625 --> 01:12:11.666 It's implemented at the moment as a tuple with two values. 253 01:12:11.666 --> 01:12:15.875 But at least, now I have a variable called what I mean, a student. 254 01:12:15.875 --> 01:12:17.375 But there's going to be a catch. 255 01:12:17.375 --> 01:12:21.583 On line 3, I still want to print out that student's name and their house. 256 01:12:21.583 --> 01:12:24.541 But I don't have a name variable anymore, and I don't have a house. 257 01:12:24.541 --> 01:12:28.625 And I also don't have a dictionary, as was proposed earlier, so I can't even 258 01:12:28.625 --> 01:12:31.833 go at those keys by name. 259 01:12:31.833 --> 01:12:35.166 But what a tuple is-- it's very similar in spirit to a list, 260 01:12:35.166 --> 01:12:37.000 but it is indeed just immutable. 261 01:12:37.000 --> 01:12:39.791 And what I mean by that is I can still index into it 262 01:12:39.791 --> 01:12:45.041 numerically by saying student [0] for the item 263 01:12:45.041 --> 01:12:47.000 in the first location in that tuple. 264 01:12:47.000 --> 01:12:50.416 And then over here, instead of house, I can say student [1]. 265 01:12:50.416 --> 01:12:54.291 student [1] is going to give me the second location in that tuple. 266 01:12:54.291 --> 01:12:56.166 Let me go ahead and clear my terminal window. 267 01:12:56.166 --> 01:12:58.083 Again, run Python of student.py. 268 01:12:58.083 --> 01:12:59.250 Let's type in Harry. 269 01:12:59.250 --> 01:13:04.500 Let's type in Gryffindor, Enter, and we still have some working code. 270 01:13:04.500 --> 01:13:07.208 Let me pause here now and see if there are 271 01:13:07.208 --> 01:13:11.500 any questions on this technique of returning a tuple 272 01:13:11.500 --> 01:13:14.750 and indexing into it in this way. 273 01:13:14.750 --> 01:13:17.125 AUDIENCE: I guess, what's an actual use case where 274 01:13:17.125 --> 01:13:21.333 you would use a tuple versus a list or something else that's similar? 275 01:13:21.333 --> 01:13:23.166 DAVID J. MALAN: It's a really good question. 276 01:13:23.166 --> 01:13:25.291 When would you use a tuple versus a list? 277 01:13:25.291 --> 01:13:28.208 When you want to program defensively, or, in general, 278 01:13:28.208 --> 01:13:31.375 when you know that the values in this variable shouldn't change, 279 01:13:31.375 --> 01:13:34.333 so why would you use a data type that allows them to be changed? 280 01:13:34.333 --> 01:13:37.791 It just invites mistakes, bugs down the line, either 281 01:13:37.791 --> 01:13:40.666 by you or colleagues who are interacting with your code. 282 01:13:40.666 --> 01:13:44.083 So tuple is just another way where you can increase 283 01:13:44.083 --> 01:13:47.708 the probability of correctness by just not letting anyone, yourself included, 284 01:13:47.708 --> 01:13:49.750 change the contents therein. 285 01:13:49.750 --> 01:13:52.250 So it's just another tool in your toolkit. 286 01:13:52.250 --> 01:13:55.333 But let's make clear, then, what I mean by "immutable." 287 01:13:55.333 --> 01:13:58.833 Again, I claim that "immutable" means that you cannot change the value. 288 01:13:58.833 --> 01:14:01.000 Well, let's go ahead and try to do this. 289 01:14:01.000 --> 01:14:03.750 Let me go ahead and run this program once more as is-- 290 01:14:03.750 --> 01:14:05.291 Python of student.py. 291 01:14:05.291 --> 01:14:09.416 Let me go ahead and type in, for instance-- 292 01:14:09.416 --> 01:14:11.375 how about Padma's name? 293 01:14:11.375 --> 01:14:13.208 And I'm going to go ahead and say that Padma 294 01:14:13.208 --> 01:14:14.916 is in Gryffindor as in the movies. 295 01:14:14.916 --> 01:14:16.958 And we see-- Padma from Gryffindor. 296 01:14:16.958 --> 01:14:20.791 But technically, I went down this rabbit hole in looking at Harry Potter 297 01:14:20.791 --> 01:14:21.541 more closely. 298 01:14:21.541 --> 01:14:25.000 Technically, in the books, Padma, I believe, was from Ravenclaw. 299 01:14:25.000 --> 01:14:27.083 So this is actually a mistake or an inconsistency 300 01:14:27.083 --> 01:14:28.750 between the movies and the books. 301 01:14:28.750 --> 01:14:31.625 Let's see if we can't fix this inconsistency in our code. 302 01:14:31.625 --> 01:14:33.208 So how about we do this? 303 01:14:33.208 --> 01:14:36.333 If the student's name that's inputted equals Padma, 304 01:14:36.333 --> 01:14:40.208 why don't we override whatever the house is 305 01:14:40.208 --> 01:14:42.875 and change it to be properly Gryffindor. 306 01:14:42.875 --> 01:14:46.083 Let me go ahead and do if student-- 307 01:14:46.083 --> 01:14:49.708 now, if I want to get at Padma's name, I'm going to have to do student [0]. 308 01:14:49.708 --> 01:14:52.625 I have to know what location the name is in in this tuple. 309 01:14:52.625 --> 01:14:57.750 But if that value equals equals Padma, let's go ahead with this if statement 310 01:14:57.750 --> 01:14:58.666 and make a change. 311 01:14:58.666 --> 01:15:02.208 Let's change the student's [1] value. 312 01:15:02.208 --> 01:15:04.625 So the second value, if we're zero indexing-- 313 01:15:04.625 --> 01:15:07.375 let's change it to be another house in the world of Harry Potter 314 01:15:07.375 --> 01:15:08.916 called Ravenclaw. 315 01:15:08.916 --> 01:15:11.750 So I'm just fixing maybe the user's input. 316 01:15:11.750 --> 01:15:14.166 They watched the movie so they type in Padma Gryffindor, 317 01:15:14.166 --> 01:15:17.333 but, mm-mm, in the books, it was Padma from Ravenclaw. 318 01:15:17.333 --> 01:15:20.250 All right, let me go ahead and go down to my terminal window, 319 01:15:20.250 --> 01:15:24.041 clear my terminal, and do Python of student.py, Enter. 320 01:15:24.041 --> 01:15:27.000 I'm going to do Harry as well as Gryffindor, just to demonstrate 321 01:15:27.000 --> 01:15:29.750 that that is still working as intended. 322 01:15:29.750 --> 01:15:33.958 Let me clear my screen again, though, and run Python of student.py on Padma, 323 01:15:33.958 --> 01:15:37.500 and I'll put her, too, in Gryffindor, as in the movies, and hit Enter. 324 01:15:37.500 --> 01:15:40.916 And now I just see a big mess of errors on the screen. 325 01:15:40.916 --> 01:15:42.708 Some kind of exception has been thrown. 326 01:15:42.708 --> 01:15:45.166 And indeed, a type error has happened. 327 01:15:45.166 --> 01:15:48.750 I'm using a data type wherein there's an error, and what is that error? 328 01:15:48.750 --> 01:15:53.041 Well, 'tuple' object does not support item assignment. 329 01:15:53.041 --> 01:15:57.375 It's a little arcanely expressed-- that is, it's not really very user friendly. 330 01:15:57.375 --> 01:15:59.250 But if you think about what those words mean, 331 01:15:59.250 --> 01:16:01.458 'tuple" object does not support item assignment. 332 01:16:01.458 --> 01:16:03.458 So assignment is copying from right to left. 333 01:16:03.458 --> 01:16:05.208 So somehow, that's invalid. 334 01:16:05.208 --> 01:16:10.250 And here is a manifestation of the immutability of tuples. 335 01:16:10.250 --> 01:16:14.208 You cannot change location 0 or 1 or anything inside. 336 01:16:14.208 --> 01:16:15.250 That is a feature. 337 01:16:15.250 --> 01:16:17.250 That is the design of a tuple. 338 01:16:17.250 --> 01:16:20.000 So if I want to override that, I think I'm 339 01:16:20.000 --> 01:16:23.125 going to have to use a different type of data that we've used before-- 340 01:16:23.125 --> 01:16:24.750 namely a list, and that's fine. 341 01:16:24.750 --> 01:16:28.416 If you want to enable yourself and colleagues using your code 342 01:16:28.416 --> 01:16:31.083 to change the contents of that container, 343 01:16:31.083 --> 01:16:34.375 well, we can go ahead and return not a tuple using 344 01:16:34.375 --> 01:16:37.541 explicit parentheses or no parentheses, just the comma, 345 01:16:37.541 --> 01:16:39.250 but I can use square brackets. 346 01:16:39.250 --> 01:16:41.916 And if I'm using square brackets on the left and the right, 347 01:16:41.916 --> 01:16:44.458 this is indeed explicitly a list. 348 01:16:44.458 --> 01:16:47.041 Same idea, but it's mutable. 349 01:16:47.041 --> 01:16:50.000 That is to say you can change the contents of a list. 350 01:16:50.000 --> 01:16:53.125 So making no other changes, just returning a list 351 01:16:53.125 --> 01:16:57.833 with square brackets instead of a tuple with parentheses or just the comma. 352 01:16:57.833 --> 01:17:01.041 Let me go ahead now and run Python of student.py, Enter. 353 01:17:01.041 --> 01:17:04.000 Let me type in Harry and Gryffindor again. 354 01:17:04.000 --> 01:17:05.458 That's still working. 355 01:17:05.458 --> 01:17:06.416 Good to see. 356 01:17:06.416 --> 01:17:11.416 Let me run this once more and type in Padma and Gryffindor, as in the movies, 357 01:17:11.416 --> 01:17:15.333 but no, now we've corrected it to be Padma from Ravenclaw, 358 01:17:15.333 --> 01:17:17.958 as in the books instead. 359 01:17:17.958 --> 01:17:22.541 Any questions now on tuples versus lists or this idea 360 01:17:22.541 --> 01:17:26.541 of immutability versus mutability. 361 01:17:26.541 --> 01:17:31.833 AUDIENCE: Can we use a nested tuple in Python, like a nest list? 362 01:17:31.833 --> 01:17:32.958 DAVID J. MALAN: Absolutely. 363 01:17:32.958 --> 01:17:35.250 You can have not only nested lists in Python, 364 01:17:35.250 --> 01:17:38.291 where one of the elements in a list could be another list-- so you 365 01:17:38.291 --> 01:17:41.500 have some square brackets out here; you might have some other square brackets 366 01:17:41.500 --> 01:17:42.041 inside. 367 01:17:42.041 --> 01:17:44.833 You can absolutely do the same with a tuple as well. 368 01:17:44.833 --> 01:17:47.833 There is no constraint on the types of values you can put in there. 369 01:17:47.833 --> 01:17:50.125 We've not had occasion to do that in this case. 370 01:17:50.125 --> 01:17:54.041 I'm just returning a simple tuple with two elements. 371 01:17:54.041 --> 01:17:56.291 But yes, you could absolutely do that, too. 372 01:17:56.291 --> 01:17:59.416 Other questions on tuples versus lists? 373 01:17:59.416 --> 01:18:03.208 AUDIENCE: OK, for example, when I see the square brackets, 374 01:18:03.208 --> 01:18:05.250 is it mainly used for the list? 375 01:18:05.250 --> 01:18:07.041 DAVID J. MALAN: Oh, a really good question. 376 01:18:07.041 --> 01:18:07.875 Sort of. 377 01:18:07.875 --> 01:18:12.250 So when you create a value like a list, you use square brackets, 378 01:18:12.250 --> 01:18:14.041 and that would indeed be a visual indicator 379 01:18:14.041 --> 01:18:16.458 that this is definitely a list. 380 01:18:16.458 --> 01:18:19.041 If you instead see parentheses, that's a visual indicator, 381 01:18:19.041 --> 01:18:21.708 when creating a value, that it's definitely a tuple. 382 01:18:21.708 --> 01:18:26.416 However, somewhat confusingly, both lists and tuples 383 01:18:26.416 --> 01:18:30.375 use square brackets when you access the contents of them. 384 01:18:30.375 --> 01:18:34.000 When you index into them at location 0 or location 1 385 01:18:34.000 --> 01:18:36.083 you always use square brackets. 386 01:18:36.083 --> 01:18:37.500 So that's the distinction there. 387 01:18:37.500 --> 01:18:38.875 Good question. 388 01:18:38.875 --> 01:18:42.916 Allow me to propose now, if I may, that we solve this problem yet another way, 389 01:18:42.916 --> 01:18:46.250 and let's see if we're either making things better or for worse than us. 390 01:18:46.250 --> 01:18:49.916 Recall that dictionaries, or dict objects, also exist in Python. 391 01:18:49.916 --> 01:18:53.250 And a dictionary is this collection of keys and values. 392 01:18:53.250 --> 01:18:55.458 And the upside, in particular, of a dictionary 393 01:18:55.458 --> 01:18:57.083 is that they have better semantics. 394 01:18:57.083 --> 01:18:59.625 You don't just have to assume that a name is always 395 01:18:59.625 --> 01:19:03.291 going to be at location 0; house is always going to be at location 1. 396 01:19:03.291 --> 01:19:06.916 That's the kind of thing, especially if you had three, four, or more values-- 397 01:19:06.916 --> 01:19:10.000 eventually you or someone is going to get confused and forget 398 01:19:10.000 --> 01:19:12.958 what the order is and you're going to write buggy code. 399 01:19:12.958 --> 01:19:15.333 So a dictionary is a little more powerful 400 01:19:15.333 --> 01:19:19.000 in that you can semantically associate keys, little descriptions, 401 01:19:19.000 --> 01:19:20.041 with the values-- 402 01:19:20.041 --> 01:19:22.541 those keys and those values, respectively. 403 01:19:22.541 --> 01:19:25.666 So let me go ahead and do this, and we can do this in a few different ways. 404 01:19:25.666 --> 01:19:28.958 But let me propose that we focus on get_student here . 405 01:19:28.958 --> 01:19:30.583 And let's go ahead and do this. 406 01:19:30.583 --> 01:19:35.875 Let me go ahead and delete the implementation of get_student as is. 407 01:19:35.875 --> 01:19:40.083 Let me create a student variable and initialize it to an empty dictionary. 408 01:19:40.083 --> 01:19:43.083 And I can do that with just two curly braces here. 409 01:19:43.083 --> 01:19:46.958 And then let me go ahead and set two keys inside of that dictionary. 410 01:19:46.958 --> 01:19:49.708 Inside of the student, there will be, quote unquote, a "name" 411 01:19:49.708 --> 01:19:53.625 key, and the value of that is going to be whatever the return value of input 412 01:19:53.625 --> 01:19:55.625 is when I prompt the user for their name. 413 01:19:55.625 --> 01:19:59.708 And then the "house" key, inside of that same student dictionary, 414 01:19:59.708 --> 01:20:02.750 is going to be the return value of whatever the user types 415 01:20:02.750 --> 01:20:07.000 in for their house. , And lastly I'm going to go ahead and return student. 416 01:20:07.000 --> 01:20:11.000 So now I am literally returning one thing, still, 417 01:20:11.000 --> 01:20:15.291 but this time, it's a dict rather than a tuple, rather than a list. 418 01:20:15.291 --> 01:20:18.041 But there are still two things in it, technically four things 419 01:20:18.041 --> 01:20:19.708 if you count the keys and the values. 420 01:20:19.708 --> 01:20:22.083 But there's two key value pairs. 421 01:20:22.083 --> 01:20:24.625 Now, my code up here is going to have to change a little bit. 422 01:20:24.625 --> 01:20:27.375 And let's simplify this and remove, for instance, now 423 01:20:27.375 --> 01:20:31.458 the Padma if statement just to focus on what's changing at hand. 424 01:20:31.458 --> 01:20:34.000 And let me go ahead now and leave line 2 alone. 425 01:20:34.000 --> 01:20:37.708 I'm still going to have a student variable that gets assigned the return 426 01:20:37.708 --> 01:20:39.166 value of get_student. 427 01:20:39.166 --> 01:20:45.375 But what I want to do here now is actually access the keys inside of that 428 01:20:45.375 --> 01:20:49.083 dictionary-- not by numeric index, which was for tuples and lists-- 429 01:20:49.083 --> 01:20:52.541 0 and 1, but by way of the keys. 430 01:20:52.541 --> 01:20:55.291 Now, normally, I might be in the habit, as I personally 431 01:20:55.291 --> 01:20:58.916 am, of using double quotes-- quote, unquote, "name" inside of there 432 01:20:58.916 --> 01:21:02.166 and quote, unquote, "house" inside of there. 433 01:21:02.166 --> 01:21:05.833 But before I even run this code and show you a mistake-- 434 01:21:05.833 --> 01:21:08.750 see an error on the screen, does anyone want to call out 435 01:21:08.750 --> 01:21:10.666 what I have done wrong here? 436 01:21:10.666 --> 01:21:12.375 This is just an f string. 437 01:21:12.375 --> 01:21:16.708 I just want to print out the value of the name key, the value of the house 438 01:21:16.708 --> 01:21:18.666 key in this dictionary. 439 01:21:18.666 --> 01:21:21.083 AUDIENCE: [INAUDIBLE] 440 01:21:21.083 --> 01:21:21.333 441 01:21:21.333 --> 01:21:23.625 DAVID J. MALAN: Your audio was a little garbled for us. 442 01:21:23.625 --> 01:21:26.250 But I think I heard double quotes and single quotes. 443 01:21:26.250 --> 01:21:29.708 So I'm going to assume that, indeed, you've identified precisely the issue. 444 01:21:29.708 --> 01:21:31.541 I'm just going to confuse Python right now. 445 01:21:31.541 --> 01:21:35.833 Even though this is an f string inside of double quotes, prefixed with an f, 446 01:21:35.833 --> 01:21:39.000 I can't actually use my double quotes inside my double quotes 447 01:21:39.000 --> 01:21:41.125 because that's going to potentially confuse Python. 448 01:21:41.125 --> 01:21:44.375 If I run this program now, Python of student.py and hit 449 01:21:44.375 --> 01:21:46.166 Enter, I get a syntax error. 450 01:21:46.166 --> 01:21:48.000 So the program didn't even run fully. 451 01:21:48.000 --> 01:21:51.958 It just couldn't be understood because it got confused by those double quotes. 452 01:21:51.958 --> 01:21:54.041 So the simplest fix here would indeed just 453 01:21:54.041 --> 01:21:58.250 be to use not double quotes but single quotes around the keys, 454 01:21:58.250 --> 01:22:02.041 or conversely, flip the double quotes on the outside to single quotes, 455 01:22:02.041 --> 01:22:03.583 then use double quotes in the inside. 456 01:22:03.583 --> 01:22:05.125 You just want to be consistent. 457 01:22:05.125 --> 01:22:09.041 So a subtle detail, but again, this is now specific to dictionary syntax. 458 01:22:09.041 --> 01:22:14.666 This isn't fundamental to how we're solving this current problem at hand. 459 01:22:14.666 --> 01:22:16.083 Well, let's go ahead and try this. 460 01:22:16.083 --> 01:22:18.416 Let me go ahead now and run Python of student.py. 461 01:22:18.416 --> 01:22:19.833 Let's go ahead and type in Harry. 462 01:22:19.833 --> 01:22:20.916 Let's type in Gryffindor. 463 01:22:20.916 --> 01:22:24.083 And hopefully, Harry is back from Gryffindor. 464 01:22:24.083 --> 01:22:25.375 No syntax errors. 465 01:22:25.375 --> 01:22:26.250 No other errors. 466 01:22:26.250 --> 01:22:28.000 I think I'm back in business here. 467 01:22:28.000 --> 01:22:31.125 And what I do like to be clear about using a dictionary 468 01:22:31.125 --> 01:22:34.000 is that it's allowing me just better semantics. 469 01:22:34.000 --> 01:22:38.958 And again, I don't have to remember, memorize, document that 0 is name; 470 01:22:38.958 --> 01:22:40.166 1 is house. 471 01:22:40.166 --> 01:22:43.291 Instead, "name" is name and "house" is house. 472 01:22:43.291 --> 01:22:45.500 It's just a little clearer, a little more expressive. 473 01:22:45.500 --> 01:22:49.250 So that's generally a good thing, especially if we stored more data about 474 01:22:49.250 --> 01:22:53.000 students than just their name and their house-- if you had three fields, four, 475 01:22:53.000 --> 01:22:54.625 five, 10 different fields-- 476 01:22:54.625 --> 01:22:58.875 no one's going to want to remember or be able to remember forever which is zero 477 01:22:58.875 --> 01:23:00.625 which is 1, which is 2, and so forth. 478 01:23:00.625 --> 01:23:04.958 Better to introduce names, like "name" and "house" in this case. 479 01:23:04.958 --> 01:23:07.125 But let me tighten this up further. 480 01:23:07.125 --> 01:23:10.541 I'm typically in the habit of not introducing variables unnecessarily, 481 01:23:10.541 --> 01:23:12.250 unless they make the code more readable. 482 01:23:12.250 --> 01:23:15.958 And an alternative way to format the same code would be this. 483 01:23:15.958 --> 01:23:19.333 Strictly speaking, I don't need to create an empty dictionary, 484 01:23:19.333 --> 01:23:23.375 then add one key to it, then add a second key to it, 485 01:23:23.375 --> 01:23:25.375 and then return that dictionary. 486 01:23:25.375 --> 01:23:29.458 I can actually consolidate this all into one statement, if you will. 487 01:23:29.458 --> 01:23:30.958 Let me go ahead and do this. 488 01:23:30.958 --> 01:23:35.458 Let me go ahead and say name equals inputs return value, 489 01:23:35.458 --> 01:23:38.583 house equals inputs return value, and then, 490 01:23:38.583 --> 01:23:41.666 instead of returning any variable name student, which I'm going to propose 491 01:23:41.666 --> 01:23:44.208 doesn't need to exist anymore, let me just create 492 01:23:44.208 --> 01:23:46.416 and return the dictionary all at once. 493 01:23:46.416 --> 01:23:50.458 Let me do, quote, unquote, "name" in lowercase here, and then the variable. 494 01:23:50.458 --> 01:23:52.041 It's storing the user's name. 495 01:23:52.041 --> 01:23:55.208 Then, quote, unquote, "house," as my second key, the value of which 496 01:23:55.208 --> 01:23:57.083 is going to be house, the variable. 497 01:23:57.083 --> 01:23:58.166 Now, is this better? 498 01:23:58.166 --> 01:23:59.000 Maybe, maybe not. 499 01:23:59.000 --> 01:24:00.958 Maybe the first way was a little more readable, 500 01:24:00.958 --> 01:24:03.416 and that's totally fine to create variables 501 01:24:03.416 --> 01:24:05.833 if they improve the readability of your code. 502 01:24:05.833 --> 01:24:09.833 But just know that you can also create and return a dictionary on the fly 503 01:24:09.833 --> 01:24:12.208 like this, so to speak, all in one line, and I 504 01:24:12.208 --> 01:24:14.333 think it's arguably pretty reasonable in this case. 505 01:24:14.333 --> 01:24:14.875 Why? 506 01:24:14.875 --> 01:24:16.083 It's just pretty short. 507 01:24:16.083 --> 01:24:18.875 I probably wouldn't do this if it got longer and longer and longer. 508 01:24:18.875 --> 01:24:22.583 I might minimally then start moving my key value pairs to separate lines. 509 01:24:22.583 --> 01:24:27.458 But this would just be a slightly more compact way of doing this as well. 510 01:24:27.458 --> 01:24:29.375 But let me propose we do one more change. 511 01:24:29.375 --> 01:24:32.958 Let's go ahead and introduce that same special casing of Padma 512 01:24:32.958 --> 01:24:36.458 to fix her house from Gryffindor, for instance, to Ravenclaw. 513 01:24:36.458 --> 01:24:38.250 How do we do this with dictionaries? 514 01:24:38.250 --> 01:24:41.625 Well, dictionaries, like lists, are mutable. 515 01:24:41.625 --> 01:24:45.416 You can change what is in them, just like you can lists. 516 01:24:45.416 --> 01:24:46.250 How do you do that? 517 01:24:46.250 --> 01:24:48.041 It's just a little different syntactically. 518 01:24:48.041 --> 01:24:50.291 So let's go back into main and do this fix. 519 01:24:50.291 --> 01:24:57.708 If the student variable has a name key that equals equals Padma, then, 520 01:24:57.708 --> 01:25:04.375 indented, go ahead and change the value of the house key inside of that student 521 01:25:04.375 --> 01:25:08.666 dictionary to be, quote, unquote, "Ravenclaw" instead. 522 01:25:08.666 --> 01:25:11.416 So very similar in spirit to what we did with a list. 523 01:25:11.416 --> 01:25:16.250 But instead of using location 0 and 1, we're much more clearly, explicitly, 524 01:25:16.250 --> 01:25:19.375 semantically using, quote, unquote, "name" and, quote, unquote, 525 01:25:19.375 --> 01:25:24.250 "house," because you index into lists and tuples using numbers, but you index 526 01:25:24.250 --> 01:25:27.916 into dictionaries using strings, as I've done here. 527 01:25:27.916 --> 01:25:31.250 All right, let me go ahead and run Python of student.py. 528 01:25:31.250 --> 01:25:33.208 We'll again do Harry from Gryffindor. 529 01:25:33.208 --> 01:25:35.125 And I think all is well. 530 01:25:35.125 --> 01:25:38.708 Let me run it one more time this time with Padma, who, in the movies, 531 01:25:38.708 --> 01:25:43.375 is from Gryffindor, but should really be from Ravenclaw. 532 01:25:43.375 --> 01:25:49.250 Any questions then on this progression from tuples to lists to dictionaries? 533 01:25:49.250 --> 01:25:53.458 We haven't necessarily introduced anything new, other than those tuples, 534 01:25:53.458 --> 01:25:56.250 which have been available to us all this time. 535 01:25:56.250 --> 01:26:00.000 But the goal at the moment is just to demonstrate this distinction 536 01:26:00.000 --> 01:26:02.250 among these different data types and how they 537 01:26:02.250 --> 01:26:04.333 each work a little bit differently. 538 01:26:04.333 --> 01:26:10.416 AUDIENCE: What if a combination of lists is there in our tuple? 539 01:26:10.416 --> 01:26:16.125 We can change the list because are immutable but lists are mutable? 540 01:26:16.125 --> 01:26:17.125 DAVID J. MALAN: Correct. 541 01:26:17.125 --> 01:26:19.291 You can change the contents of lists, and you 542 01:26:19.291 --> 01:26:23.125 can put most anything you want in them-- other lists or strings, as I've done, 543 01:26:23.125 --> 01:26:24.583 integers, or anything else. 544 01:26:24.583 --> 01:26:27.916 Tuples you can do the exact same thing, but you cannot change them once 545 01:26:27.916 --> 01:26:29.083 you've created them. 546 01:26:29.083 --> 01:26:33.041 A dictionary is more like a list in that it is mutable. 547 01:26:33.041 --> 01:26:34.208 You can change it. 548 01:26:34.208 --> 01:26:37.583 But the way you index into a dictionary is 549 01:26:37.583 --> 01:26:40.541 by way of these keys, these strings, as we keep saying, 550 01:26:40.541 --> 01:26:42.250 rather than by numbers-- 551 01:26:42.250 --> 01:26:45.083 those numeric indices. 552 01:26:45.083 --> 01:26:47.875 All right, well, let me propose that there is yet 553 01:26:47.875 --> 01:26:50.458 another way of solving this problem. 554 01:26:50.458 --> 01:26:53.875 And I would argue that there's now an opportunity at hand. 555 01:26:53.875 --> 01:26:56.750 Even though this program isn't particularly complicated-- 556 01:26:56.750 --> 01:27:01.208 all I'm doing is collecting a name from the user and a house 557 01:27:01.208 --> 01:27:03.166 from the user-- you could imagine wanting, 558 01:27:03.166 --> 01:27:05.291 longer term, to collect even more information, 559 01:27:05.291 --> 01:27:07.791 like the student's patronus or magical spell 560 01:27:07.791 --> 01:27:10.666 or a whole bunch of other information that might belong in a student. 561 01:27:10.666 --> 01:27:15.291 And right now, we're just using these very general purpose data 562 01:27:15.291 --> 01:27:16.458 types in Python-- 563 01:27:16.458 --> 01:27:20.083 a tuple to combine some values together; a list to do the same, 564 01:27:20.083 --> 01:27:23.625 but let us change it later; a dictionary, which is more powerful 565 01:27:23.625 --> 01:27:25.250 because it's a little more structured. 566 01:27:25.250 --> 01:27:28.708 It does have keys, and it has values, not just values. 567 01:27:28.708 --> 01:27:30.250 But you know what? 568 01:27:30.250 --> 01:27:33.500 We wouldn't have to be having this conversation if the authors of Python 569 01:27:33.500 --> 01:27:37.125 had just given us a data type called student. 570 01:27:37.125 --> 01:27:39.000 Wouldn't it have been nice if there were just 571 01:27:39.000 --> 01:27:42.166 a type of variable I could create in my code called student? 572 01:27:42.166 --> 01:27:43.916 Then we wouldn't have to figure out, well, 573 01:27:43.916 --> 01:27:46.416 do we use a tuple or a list or a dictionary? 574 01:27:46.416 --> 01:27:48.333 But that's pretty reasonable. 575 01:27:48.333 --> 01:27:52.166 You can imagine just how slippery of a slope that is, so to speak, 576 01:27:52.166 --> 01:27:55.166 if the creators of a language had to anticipate 577 01:27:55.166 --> 01:27:58.250 all the possible types of data that programmers like you 578 01:27:58.250 --> 01:28:00.375 and me want to store in your programs. 579 01:28:00.375 --> 01:28:02.875 So they just gave us these general purpose tools. 580 01:28:02.875 --> 01:28:05.750 But they gave us another general purpose tool 581 01:28:05.750 --> 01:28:09.375 that's going to allow us to create our own data types as well 582 01:28:09.375 --> 01:28:14.291 and actually give them names, and that terminology is a class. 583 01:28:14.291 --> 01:28:20.125 A class is like a blueprint for pieces of data objects. 584 01:28:20.125 --> 01:28:24.166 A class is a mold that you can define and give a name. 585 01:28:24.166 --> 01:28:26.875 And when you use that mold or you use that blueprint, 586 01:28:26.875 --> 01:28:31.000 you get types of data that are designed exactly as you want. 587 01:28:31.000 --> 01:28:36.208 So in short, classes allow you to invent your own data types in Python 588 01:28:36.208 --> 01:28:37.375 and give them a name. 589 01:28:37.375 --> 01:28:41.291 And this is a primary feature of object oriented programming, 590 01:28:41.291 --> 01:28:44.041 to be able to create your own objects in this way 591 01:28:44.041 --> 01:28:48.291 and, in the case of Python in classes, even give them some custom names. 592 01:28:48.291 --> 01:28:50.333 So what does this mean in real terms? 593 01:28:50.333 --> 01:28:52.375 Well, let me go ahead and come back to VS Code 594 01:28:52.375 --> 01:28:56.500 here, and let me propose that we introduce a little bit of new syntax. 595 01:28:56.500 --> 01:28:58.916 I'm going to go ahead and clear my terminal window first. 596 01:28:58.916 --> 01:29:02.791 I'm going to go to the top of my file, and I'm just going to start a thought 597 01:29:02.791 --> 01:29:03.916 but not finish it yet. 598 01:29:03.916 --> 01:29:09.083 I'm going to use this new keyword for classes, called, literally, class, 599 01:29:09.083 --> 01:29:11.708 so the new keyword we're going to have here. 600 01:29:11.708 --> 01:29:13.916 And if I go back to our slides here, this 601 01:29:13.916 --> 01:29:16.083 would be the official URL where you can read up more 602 01:29:16.083 --> 01:29:18.000 on this particular feature of Python. 603 01:29:18.000 --> 01:29:22.083 In the official tutorial, class is a new keyword we can use. 604 01:29:22.083 --> 01:29:25.500 Now, this is coincidentally related to students because students take classes, 605 01:29:25.500 --> 01:29:28.500 but it has nothing to do with the fact that we're dealing with students. 606 01:29:28.500 --> 01:29:31.791 Class is a general purpose term in a lot of languages-- 607 01:29:31.791 --> 01:29:35.500 Python among them-- that allow you to define these custom containers 608 01:29:35.500 --> 01:29:38.625 with custom names for pieces of data. 609 01:29:38.625 --> 01:29:39.875 So let's go back to VS Code. 610 01:29:39.875 --> 01:29:41.333 Let's use this new keyword. 611 01:29:41.333 --> 01:29:44.500 And let me propose that we create a class called Student. 612 01:29:44.500 --> 01:29:47.000 And by convention, I'm going to use a capital S here, 613 01:29:47.000 --> 01:29:49.583 and I'm going to go ahead, and with a colon, 614 01:29:49.583 --> 01:29:52.500 get to, later, the implementation of this class. 615 01:29:52.500 --> 01:29:55.625 So I'm just going to use dot dot dot, which is a valid placeholder for now, 616 01:29:55.625 --> 01:29:57.833 that just indicates to me that I'm going to come back 617 01:29:57.833 --> 01:29:59.000 to implementing this later. 618 01:29:59.000 --> 01:30:02.125 But as of now, it does, in fact, exist. 619 01:30:02.125 --> 01:30:08.458 I now have a student class defined for me that I can now use in my code here. 620 01:30:08.458 --> 01:30:09.875 How am I going to use it? 621 01:30:09.875 --> 01:30:13.250 Well, first of all, let me go down to get_student, 622 01:30:13.250 --> 01:30:18.625 and let me change this code to no longer use a dictionary but to use this class. 623 01:30:18.625 --> 01:30:19.666 I'm going to do this. 624 01:30:19.666 --> 01:30:22.791 I'm going to give myself a variable called student, as I've done before, 625 01:30:22.791 --> 01:30:26.166 but I'm going to set it equal to capital Student (). 626 01:30:26.166 --> 01:30:28.916 627 01:30:28.916 --> 01:30:32.208 So I'm going to do what appears to be calling a function 628 01:30:32.208 --> 01:30:35.583 and that function, Student with a capital S, notice, 629 01:30:35.583 --> 01:30:39.500 matches the name that I gave this class at the top of my file. 630 01:30:39.500 --> 01:30:41.166 All right, what do I next want to do? 631 01:30:41.166 --> 01:30:43.333 I'm going to go ahead and give this student a name. 632 01:30:43.333 --> 01:30:46.041 Now, if I were still using a dictionary, I 633 01:30:46.041 --> 01:30:50.541 would say student, quote, unquote, "name," using square brackets. 634 01:30:50.541 --> 01:30:52.125 But this is not a dictionary. 635 01:30:52.125 --> 01:30:54.500 It turns out classes have what, for now, we'll 636 01:30:54.500 --> 01:30:58.916 call attributes, properties of sorts that allow you to specify values inside 637 01:30:58.916 --> 01:30:59.416 of them. 638 01:30:59.416 --> 01:31:01.708 And the syntax for that happens to be a dot. 639 01:31:01.708 --> 01:31:03.166 We've seen dots before. 640 01:31:03.166 --> 01:31:06.333 We've used it in the context of modules and libraries, more generally. 641 01:31:06.333 --> 01:31:08.875 This is another similar in spirit use of a dot 642 01:31:08.875 --> 01:31:12.125 that allows you to get at something inside of something else. 643 01:31:12.125 --> 01:31:15.208 So student.name is going to be the syntax 644 01:31:15.208 --> 01:31:18.083 I use for giving this student a name. 645 01:31:18.083 --> 01:31:21.666 And that name is going to be whatever the return value of "Name" is. 646 01:31:21.666 --> 01:31:24.958 And then I'm going to go ahead and say student.house to give 647 01:31:24.958 --> 01:31:29.291 another attribute called "House" and give that the return value of input 648 01:31:29.291 --> 01:31:30.750 here, prompting the user for house. 649 01:31:30.750 --> 01:31:33.625 And then, as before, I'm just going to return student. 650 01:31:33.625 --> 01:31:37.166 But now what's really powerful about class, 651 01:31:37.166 --> 01:31:39.791 and object-oriented programming more generally, 652 01:31:39.791 --> 01:31:43.375 is that I've created this custom data type called, literally, 653 01:31:43.375 --> 01:31:47.333 Student, capital S. I've stored one such student in a variable 654 01:31:47.333 --> 01:31:50.833 like I can always do in a variable called student, lowercase s. 655 01:31:50.833 --> 01:31:52.333 But I could call it anything I want. 656 01:31:52.333 --> 01:31:56.166 It just makes sense to call it student as well, but lowercase for clarity. 657 01:31:56.166 --> 01:31:58.291 And then I'm returning that variable. 658 01:31:58.291 --> 01:32:01.666 And because of my syntax in lines 14 and 15, 659 01:32:01.666 --> 01:32:05.083 that has the result of putting inside of that class 660 01:32:05.083 --> 01:32:09.291 a name attribute and a house attribute. 661 01:32:09.291 --> 01:32:11.625 I just need to make one more change up here. 662 01:32:11.625 --> 01:32:13.416 I'm going to go ahead and remove our Padma 663 01:32:13.416 --> 01:32:15.500 code, just so we can focus only on what's new, 664 01:32:15.500 --> 01:32:17.416 rather than fixing her house. 665 01:32:17.416 --> 01:32:20.208 And I'm going to go in here and change the syntax that 666 01:32:20.208 --> 01:32:21.916 previously was for dictionaries. 667 01:32:21.916 --> 01:32:26.958 Again, dictionaries use square brackets and then strings in quotes-- 668 01:32:26.958 --> 01:32:29.958 either single quotes or double quotes, depending on the context. 669 01:32:29.958 --> 01:32:33.208 Here, though, I'm going to change this to be student.name, 670 01:32:33.208 --> 01:32:37.458 and over here, I'm going to change it to be student.house. 671 01:32:37.458 --> 01:32:40.791 And that's just going to be my new syntax for getting the contents of what 672 01:32:40.791 --> 01:32:43.250 appears to be a class called student. 673 01:32:43.250 --> 01:32:47.458 Let me go ahead and rerun Python of student.py, Enter. 674 01:32:47.458 --> 01:32:49.333 Let's type in Harry's name as before. 675 01:32:49.333 --> 01:32:52.500 Let's put him in Gryffindor, crossing our fingers as we often do, 676 01:32:52.500 --> 01:32:56.500 and Harry is indeed from Gryffindor. 677 01:32:56.500 --> 01:32:58.166 What, though, have I done? 678 01:32:58.166 --> 01:33:01.000 Let's introduce one other bit of terminology here 679 01:33:01.000 --> 01:33:05.583 it turns out that I can create a class, using that class keyword. 680 01:33:05.583 --> 01:33:09.958 But any time you use a class, you're creating what are called objects. 681 01:33:09.958 --> 01:33:14.666 And here is the word objects, as an object-oriented programming, or OOP. 682 01:33:14.666 --> 01:33:16.250 Let me go back to my code here. 683 01:33:16.250 --> 01:33:18.916 And even though I haven't really implemented much of it at all-- 684 01:33:18.916 --> 01:33:20.958 I literally just left it with a dot, dot, dot-- 685 01:33:20.958 --> 01:33:25.416 that's enough code, lines 1 and 2, to just invent a new data type called 686 01:33:25.416 --> 01:33:29.708 Student, capital S, that may or may not have some future functionality as well. 687 01:33:29.708 --> 01:33:31.375 That's enough to create a class. 688 01:33:31.375 --> 01:33:34.166 What, though, am I doing on line 11? 689 01:33:34.166 --> 01:33:40.083 On line 11, what I'm technically doing is creating an object of that class. 690 01:33:40.083 --> 01:33:42.000 So this, too, is another term of art. 691 01:33:42.000 --> 01:33:44.791 You create objects from classes. 692 01:33:44.791 --> 01:33:47.000 So if we go back to that metaphor, that a class is 693 01:33:47.000 --> 01:33:50.916 like a blueprint for a house or a class is like a mold, 694 01:33:50.916 --> 01:33:54.750 an object is when you use that blueprint to build a specific house 695 01:33:54.750 --> 01:33:58.458 or something that comes out of-- in plaster, the mold, when you actually 696 01:33:58.458 --> 01:34:01.250 use that mold to create such an object. 697 01:34:01.250 --> 01:34:04.916 So a class is, again, the definition of a new data type. 698 01:34:04.916 --> 01:34:10.125 The object is the incarnation of, or technically instantiation of. 699 01:34:10.125 --> 01:34:13.583 And another term for objects would actually be an instance. 700 01:34:13.583 --> 01:34:16.208 You have instances of classes as well. 701 01:34:16.208 --> 01:34:17.458 So that's a lot of vocabulary. 702 01:34:17.458 --> 01:34:19.708 But at the end of the day, it just boils down to this. 703 01:34:19.708 --> 01:34:22.750 You can define your own class, which is really your own data type. 704 01:34:22.750 --> 01:34:26.791 You can then store attributes inside of it, using this dot notation here. 705 01:34:26.791 --> 01:34:31.041 And then you can access those same attributes using code like this here. 706 01:34:31.041 --> 01:34:33.958 And now, I have a proper "student" data type, 707 01:34:33.958 --> 01:34:36.625 and I don't have to hack something together 708 01:34:36.625 --> 01:34:39.000 using a tuple or a list or even a dictionary. 709 01:34:39.000 --> 01:34:43.500 I now have a proper data type called "student" that the authors of Python 710 01:34:43.500 --> 01:34:46.041 didn't give me; I gave myself. 711 01:34:46.041 --> 01:34:50.791 Any questions now on classes, this new keyword, class, or this idea 712 01:34:50.791 --> 01:34:53.625 of these objects or instances thereof? 713 01:34:53.625 --> 01:34:58.333 AUDIENCE: Is the class object mutable or immutable? 714 01:34:58.333 --> 01:34:59.666 DAVID J. MALAN: A good question. 715 01:34:59.666 --> 01:35:02.208 And we've clearly laid the stage for having that conversation 716 01:35:02.208 --> 01:35:03.541 about every data type now. 717 01:35:03.541 --> 01:35:07.958 We will see that they are mutable, but you can make them immutable. 718 01:35:07.958 --> 01:35:10.083 So you can get the best of both worlds. 719 01:35:10.083 --> 01:35:13.416 Now, by writing some actual code-- and we'll write more code than the dot, 720 01:35:13.416 --> 01:35:18.208 dot, dot in just a bit, other questions on classes or these objects thereof? 721 01:35:18.208 --> 01:35:21.625 AUDIENCE: Then what would be the properties of those classes? 722 01:35:21.625 --> 01:35:24.750 DAVID J. MALAN: So at the moment, the properties of-- or the attributes of, 723 01:35:24.750 --> 01:35:28.250 as I've been calling them thus far-- would just be "Name" and "House." 724 01:35:28.250 --> 01:35:32.333 It turns out that there may very well be other attributes built into classes 725 01:35:32.333 --> 01:35:33.541 that we may see before long. 726 01:35:33.541 --> 01:35:36.166 But for now, the only two attributes that I care about 727 01:35:36.166 --> 01:35:38.333 are the ones that I myself created-- 728 01:35:38.333 --> 01:35:42.208 namely "Name" and "House" or, again, what I would call attributes. 729 01:35:42.208 --> 01:35:43.916 And in a little bit, we're going to start 730 01:35:43.916 --> 01:35:47.541 calling those same attributes, more technically, instance variables. 731 01:35:47.541 --> 01:35:50.708 "Name" and "House," as I presented them here in VS Code 732 01:35:50.708 --> 01:35:55.750 are really just variables called "name" and called "house" inside 733 01:35:55.750 --> 01:36:00.125 of an object whose type is student. 734 01:36:00.125 --> 01:36:02.583 All right, so what more can we do with these classes? 735 01:36:02.583 --> 01:36:06.208 Well, again, on line 11 is where we're instantiating 736 01:36:06.208 --> 01:36:11.000 an object of the student class and assigning it to a student variable. 737 01:36:11.000 --> 01:36:12.416 We're then adding attributes-- 738 01:36:12.416 --> 01:36:16.000 "Name" and "House," respectively-- on lines 12 and 13 currently. 739 01:36:16.000 --> 01:36:17.958 Both of those have values that are technically 740 01:36:17.958 --> 01:36:21.208 strings or strs, because that's what the return value of the input is. 741 01:36:21.208 --> 01:36:24.000 But those attributes values could actually be any data type. 742 01:36:24.000 --> 01:36:27.000 We're just keeping things simple and focusing on defining students 743 01:36:27.000 --> 01:36:28.333 in terms of two strings-- 744 01:36:28.333 --> 01:36:29.375 "Name" and "House." 745 01:36:29.375 --> 01:36:32.125 And then, on line 14, we're returning that variable. 746 01:36:32.125 --> 01:36:35.333 We're returning that object to main so that we can actually 747 01:36:35.333 --> 01:36:37.708 print out who is from what house. 748 01:36:37.708 --> 01:36:40.958 Well, let's go ahead and add a bit more functionality here because, right now, 749 01:36:40.958 --> 01:36:44.166 on lines 12 and 13, this is a little manual. 750 01:36:44.166 --> 01:36:47.333 And it's a little reckless of me to just be putting anything 751 01:36:47.333 --> 01:36:50.333 I want inside of this student object. 752 01:36:50.333 --> 01:36:53.041 It turns out with classes, unlike with dictionaries, 753 01:36:53.041 --> 01:36:57.625 we can actually standardize, all the more, what those attributes can be 754 01:36:57.625 --> 01:37:00.291 and what kinds of values you can set them to. 755 01:37:00.291 --> 01:37:02.375 So let me go ahead and do this. 756 01:37:02.375 --> 01:37:04.375 Let me propose that it would actually be really 757 01:37:04.375 --> 01:37:08.875 nice if, instead of doing this here, let me go ahead 758 01:37:08.875 --> 01:37:10.833 and simplify my code as follows. 759 01:37:10.833 --> 01:37:13.708 Let me go ahead and give myself a local variable called name 760 01:37:13.708 --> 01:37:17.208 and set it equal to the return value of input, like we've done many times now 761 01:37:17.208 --> 01:37:17.791 already. 762 01:37:17.791 --> 01:37:20.333 Let me give myself one other variable for now, called house, 763 01:37:20.333 --> 01:37:23.333 and set it equal to the return value of input 764 01:37:23.333 --> 01:37:25.291 as well, prompting the user for their house. 765 01:37:25.291 --> 01:37:30.416 And now, instead of creating a student object from my student class 766 01:37:30.416 --> 01:37:34.500 and then manually putting the name attribute inside of it 767 01:37:34.500 --> 01:37:37.166 and the house attribute inside of it, let 768 01:37:37.166 --> 01:37:39.458 me actually do something more powerful. 769 01:37:39.458 --> 01:37:40.583 Let me do this. 770 01:37:40.583 --> 01:37:45.333 Let me call that Student function, which is identical to the class name-- 771 01:37:45.333 --> 01:37:48.125 just by defining a class, you get a function 772 01:37:48.125 --> 01:37:52.583 whose name is identical to the class name, with the capital letter included. 773 01:37:52.583 --> 01:37:54.625 But instead of just doing open parenthesis, 774 01:37:54.625 --> 01:37:58.166 closed parenthesis, let me pass in the name that I 775 01:37:58.166 --> 01:38:02.791 want to fill this object with and the house that I want to put in that object 776 01:38:02.791 --> 01:38:03.375 as well. 777 01:38:03.375 --> 01:38:09.375 And now let me set the return value as before to be student equals like this. 778 01:38:09.375 --> 01:38:11.083 So what have I done that's different? 779 01:38:11.083 --> 01:38:13.583 Fundamentally, I'm still getting user input in the same way. 780 01:38:13.583 --> 01:38:16.750 I'm using input on line 11 and input on line 12. 781 01:38:16.750 --> 01:38:21.125 And I just so happen to be storing those return values in local variables. 782 01:38:21.125 --> 01:38:24.875 And now we're setting the stage for the more powerful features of classes 783 01:38:24.875 --> 01:38:27.208 and object-oriented programming more generally. 784 01:38:27.208 --> 01:38:31.500 Notice that I'm deliberately passing to this capital S Student 785 01:38:31.500 --> 01:38:33.875 function, name, house-- 786 01:38:33.875 --> 01:38:36.291 I'm passing in arguments to the function. 787 01:38:36.291 --> 01:38:39.750 Now, the student class is not going to know what to do with those yet, 788 01:38:39.750 --> 01:38:44.791 but now I'm standardizing how I'm passing data into this student class. 789 01:38:44.791 --> 01:38:47.833 And ultimately, it's going to give me an opportunity to error 790 01:38:47.833 --> 01:38:51.166 check those inputs, to make sure that the name is valid, that it has a value 791 01:38:51.166 --> 01:38:52.875 and it's not just the user hitting Enter. 792 01:38:52.875 --> 01:38:56.041 It's going to allow me to ensure that it's a valid house, that it's 793 01:38:56.041 --> 01:38:59.208 Gryffindor or Hufflepuff or Ravenclaw or Slytherin 794 01:38:59.208 --> 01:39:02.916 or not just hitting Enter or some random value that the user types in. 795 01:39:02.916 --> 01:39:05.916 Because I'm passing "Name" and "House" to the student 796 01:39:05.916 --> 01:39:08.791 class, this particular function, I'm going 797 01:39:08.791 --> 01:39:11.416 to have more control over the correctness of my data. 798 01:39:11.416 --> 01:39:14.541 So let's now go up to the student class, which, up until now, 799 01:39:14.541 --> 01:39:16.125 I left as just dot, dot, dot. 800 01:39:16.125 --> 01:39:19.625 It turns out that, in the context of classes, 801 01:39:19.625 --> 01:39:23.916 there are a number of not just attributes or instance 802 01:39:23.916 --> 01:39:27.041 variables that you can put inside, but also methods. 803 01:39:27.041 --> 01:39:32.083 Classes come with certain methods, or functions inside of them, 804 01:39:32.083 --> 01:39:35.708 that you can define, and they just behave in a special way, 805 01:39:35.708 --> 01:39:37.875 by nature of how Python works. 806 01:39:37.875 --> 01:39:42.625 These functions allow you to determine behavior in a standard way. 807 01:39:42.625 --> 01:39:45.083 They are special methods in that sense. 808 01:39:45.083 --> 01:39:46.333 Now, what do I mean by this. 809 01:39:46.333 --> 01:39:48.083 Well, let me go back to VS Code here. 810 01:39:48.083 --> 01:39:53.000 And let me propose that I start to define a standard function 811 01:39:53.000 --> 01:39:56.916 called underscore underscore, or Dunder, as it's 812 01:39:56.916 --> 01:39:59.916 abbreviated, init, underscore underscore, 813 01:39:59.916 --> 01:40:02.916 and then I'm going to go ahead and do open parentheses, 814 01:40:02.916 --> 01:40:06.875 and then I'm going to put in here, literally, the word self. 815 01:40:06.875 --> 01:40:08.416 More on that in just a moment. 816 01:40:08.416 --> 01:40:11.791 But now, inside of this function, I'm going to have an opportunity 817 01:40:11.791 --> 01:40:16.791 to customize this class's objects. 818 01:40:16.791 --> 01:40:20.333 That is to say this underscore, underscore init method, 819 01:40:20.333 --> 01:40:25.208 or Dunder init method is specifically known as an instance method, 820 01:40:25.208 --> 01:40:26.708 and it's called exactly this. 821 01:40:26.708 --> 01:40:28.875 This is designed by the authors of Python. 822 01:40:28.875 --> 01:40:34.291 And if you want to initialize the contents of an object from a class, 823 01:40:34.291 --> 01:40:37.875 you define this method, and we'll see what it's about to do here. 824 01:40:37.875 --> 01:40:41.250 Let me go back to VS Code, and let me do something like this. 825 01:40:41.250 --> 01:40:47.750 self.name = name, and self.house = house. 826 01:40:47.750 --> 01:40:51.250 But I don't want to just init this object very generically. 827 01:40:51.250 --> 01:40:56.166 I want this method, called init, to take in not just self but name, 828 01:40:56.166 --> 01:40:58.375 house as well. 829 01:40:58.375 --> 01:40:59.916 Now, what in the world is going on? 830 01:40:59.916 --> 01:41:01.708 Because there's a lot of weird syntax here. 831 01:41:01.708 --> 01:41:03.375 There's this Dunder init method-- 832 01:41:03.375 --> 01:41:05.791 double underscore, init, double underscore. 833 01:41:05.791 --> 01:41:08.750 There's, all of a sudden, this parameter called self. 834 01:41:08.750 --> 01:41:12.458 And then there's this new syntax-- self.name and self.house. 835 01:41:12.458 --> 01:41:16.541 Now you're seeing really a manifestation of object-oriented programming. 836 01:41:16.541 --> 01:41:18.250 It's not all that different fundamentally 837 01:41:18.250 --> 01:41:20.541 from what we've been doing for weeks with dictionaries, 838 01:41:20.541 --> 01:41:23.125 by adding keys to dictionaries. 839 01:41:23.125 --> 01:41:28.333 But in this case, we're adding variables to objects, a.k.a. 840 01:41:28.333 --> 01:41:30.500 instance variables to objects. 841 01:41:30.500 --> 01:41:31.458 Now, what's going on? 842 01:41:31.458 --> 01:41:32.541 Let's do this in reverse. 843 01:41:32.541 --> 01:41:34.750 Let's go back to the line of code we wrote earlier. 844 01:41:34.750 --> 01:41:38.708 On line 15, I am treating the name of this class-- 845 01:41:38.708 --> 01:41:41.916 Student with a capital S-- as a function. 846 01:41:41.916 --> 01:41:44.583 And I am passing in two values-- 847 01:41:44.583 --> 01:41:45.916 "Name" and "House." 848 01:41:45.916 --> 01:41:48.708 What I've highlighted here on the screen, on line 15, 849 01:41:48.708 --> 01:41:51.250 is generally known as a constructor call. 850 01:41:51.250 --> 01:41:56.875 This is a line of code that is going to construct a student object for me. 851 01:41:56.875 --> 01:42:02.791 Using synonyms, it is going to instantiate a student object for me. 852 01:42:02.791 --> 01:42:05.583 And again, how is it going to create that object? 853 01:42:05.583 --> 01:42:09.458 It's going to use the student class as a template, as a mold of sorts 854 01:42:09.458 --> 01:42:12.291 so that every student is structured the same. 855 01:42:12.291 --> 01:42:13.875 Every student is going to have a name. 856 01:42:13.875 --> 01:42:15.458 Every student's going to have a house. 857 01:42:15.458 --> 01:42:20.916 But because I can pass in arguments to this Student function, capital S, 858 01:42:20.916 --> 01:42:26.666 I'm going to be able to customize the contents of that object. 859 01:42:26.666 --> 01:42:29.666 So if you think about the real world-- if you've ever been on a street 860 01:42:29.666 --> 01:42:34.666 or a neighborhood where all of the houses look the same but they might be 861 01:42:34.666 --> 01:42:37.541 painted differently; they might be decorated a little bit differently 862 01:42:37.541 --> 01:42:40.958 on the outside, all of those houses might have been built using the exact 863 01:42:40.958 --> 01:42:42.541 same blueprint-- 864 01:42:42.541 --> 01:42:43.875 a mold, if you will. 865 01:42:43.875 --> 01:42:48.083 But then you can specialize exactly the finer points of those houses. 866 01:42:48.083 --> 01:42:51.000 By painting the outside a different color or planting different trees, 867 01:42:51.000 --> 01:42:52.500 you can style them differently. 868 01:42:52.500 --> 01:42:57.750 Similar in spirit here, we have a Student blueprint 869 01:42:57.750 --> 01:43:01.583 that's always going to have now a name and a house, but it's up to you and me 870 01:43:01.583 --> 01:43:04.166 to pass in any name and any house that we want. 871 01:43:04.166 --> 01:43:06.041 Now, where is this function? 872 01:43:06.041 --> 01:43:09.125 The fact that I'm calling Student, capital, S and then a parenthesis 873 01:43:09.125 --> 01:43:11.250 and a closed parenthesis with arguments inside 874 01:43:11.250 --> 01:43:12.958 suggest that there's a function somewhere 875 01:43:12.958 --> 01:43:17.500 in the world that has been defined, with def, that's going to be called. 876 01:43:17.500 --> 01:43:20.416 Well, as you might have guessed by now, the function 877 01:43:20.416 --> 01:43:25.500 that will always be called, by definition of how Python classes work, 878 01:43:25.500 --> 01:43:30.000 is a function called double underscore, init, double underscore. 879 01:43:30.000 --> 01:43:30.500 Why? 880 01:43:30.500 --> 01:43:33.333 It's a crazy name, but it's what the authors of Python 881 01:43:33.333 --> 01:43:38.750 chose to just implement the initialization of an object in Python. 882 01:43:38.750 --> 01:43:40.916 Now, the only weird thing-- 883 01:43:40.916 --> 01:43:43.125 especially weird thing, I will admit, is this. 884 01:43:43.125 --> 01:43:49.125 It would be way clearer, to me, too, if the only two parameters for init we're 885 01:43:49.125 --> 01:43:50.666 just name, house. 886 01:43:50.666 --> 01:43:53.541 That's how we've defined every function thus far in the class. 887 01:43:53.541 --> 01:43:57.291 You just specify the parameters that you want the function to accept. 888 01:43:57.291 --> 01:44:00.333 And that lines up with what I'm doing on line 15. 889 01:44:00.333 --> 01:44:04.541 I am only passing in two things to the student function. 890 01:44:04.541 --> 01:44:07.833 But it turns out that the authors of Python 891 01:44:07.833 --> 01:44:09.541 need to give us a little bit of help here 892 01:44:09.541 --> 01:44:14.458 because suppose that you pass in "Name" and "House" to this init method. 893 01:44:14.458 --> 01:44:17.833 And a method is just a function inside of a class. 894 01:44:17.833 --> 01:44:20.291 What are you going to do with the name and the house? 895 01:44:20.291 --> 01:44:22.333 Literally, where are you going to put them? 896 01:44:22.333 --> 01:44:26.500 If you want to remember the name and the house for this student, 897 01:44:26.500 --> 01:44:29.166 you've got to be able to store those values somewhere. 898 01:44:29.166 --> 01:44:32.416 And how do you store them in the current object 899 01:44:32.416 --> 01:44:34.833 that has just been "instantiated?" 900 01:44:34.833 --> 01:44:37.625 Well, the authors of Python decided that the convention 901 01:44:37.625 --> 01:44:41.958 is going to be that this init method also, semi secretly, takes 902 01:44:41.958 --> 01:44:44.625 a third argument, that has to come first. 903 01:44:44.625 --> 01:44:46.666 By convention, it's called self, but you could 904 01:44:46.666 --> 01:44:48.250 call it technically anything you want. 905 01:44:48.250 --> 01:44:50.208 But the convention is to always call it self. 906 01:44:50.208 --> 01:44:53.500 And self, as its name implies, gives you access 907 01:44:53.500 --> 01:44:56.791 to the current object that was just created. 908 01:44:56.791 --> 01:44:58.000 What does that mean? 909 01:44:58.000 --> 01:45:02.166 Again, now, on line 14, now that it's moved down a little bit, 910 01:45:02.166 --> 01:45:04.083 this line here is a constructor. 911 01:45:04.083 --> 01:45:06.166 It constructs a student object. 912 01:45:06.166 --> 01:45:08.291 But there's nothing in that object initially. 913 01:45:08.291 --> 01:45:09.750 There's no name; there's no house. 914 01:45:09.750 --> 01:45:12.791 But the object exists in the computer's memory. 915 01:45:12.791 --> 01:45:16.541 It's up to, now, you to store the name and the house inside of that object. 916 01:45:16.541 --> 01:45:17.500 How do you do that? 917 01:45:17.500 --> 01:45:21.500 Well, Python will just automatically call this init method for you, 918 01:45:21.500 --> 01:45:27.500 and it's going to automatically pass in a reference to an argument that 919 01:45:27.500 --> 01:45:32.375 represents the current object that it just constructed in memory for you, 920 01:45:32.375 --> 01:45:34.708 and it's up to you to populate it with values. 921 01:45:34.708 --> 01:45:37.666 And what this means is that, inside of your init method, 922 01:45:37.666 --> 01:45:42.875 you can literally do self.name to create a new attribute, a.k.a. 923 01:45:42.875 --> 01:45:46.166 an instance variable, inside of that otherwise empty 924 01:45:46.166 --> 01:45:48.416 object and put this name inside of it. 925 01:45:48.416 --> 01:45:51.875 It allows you to do self.house and store that value of house. 926 01:45:51.875 --> 01:45:54.000 Now, you could call these things anything you want. 927 01:45:54.000 --> 01:45:54.750 They could be n. 928 01:45:54.750 --> 01:45:56.416 They could be h, as before. 929 01:45:56.416 --> 01:46:00.250 But that's really not very self-explanatory. 930 01:46:00.250 --> 01:46:04.250 Much better to do this kind of convention. self.name equals name. 931 01:46:04.250 --> 01:46:06.333 self.house equals house. 932 01:46:06.333 --> 01:46:11.708 And this is like installing into the otherwise empty object the value name 933 01:46:11.708 --> 01:46:16.083 and house and storing them in, really, identically named instance 934 01:46:16.083 --> 01:46:17.708 variables in the object. 935 01:46:17.708 --> 01:46:20.833 And again, an object is just an instance of a class. 936 01:46:20.833 --> 01:46:23.208 Now, I know that was a lot of vocabulary. 937 01:46:23.208 --> 01:46:24.875 That's a lot of weird syntax. 938 01:46:24.875 --> 01:46:28.625 So any questions on this init method, whose purpose in life, 939 01:46:28.625 --> 01:46:33.541 again, is to initialize an otherwise empty object when you first create it? 940 01:46:33.541 --> 01:46:36.791 AUDIENCE: So what is the difference between the init method and default 941 01:46:36.791 --> 01:46:37.500 constructor? 942 01:46:37.500 --> 01:46:38.833 DAVID J. MALAN: A good question. 943 01:46:38.833 --> 01:46:41.583 So in other languages-- if you programmed before. 944 01:46:41.583 --> 01:46:45.166 For instance, Java-- there are functions that are explicitly called 945 01:46:45.166 --> 01:46:47.916 constructors that construct an object. 946 01:46:47.916 --> 01:46:49.416 They initialize it with values. 947 01:46:49.416 --> 01:46:53.291 Python technically calls this init method the initialization method. 948 01:46:53.291 --> 01:46:54.958 It initializes the value. 949 01:46:54.958 --> 01:47:00.875 It's on line 15 now of my code, if I scroll back down, that I'm technically 950 01:47:00.875 --> 01:47:02.416 constructing the object. 951 01:47:02.416 --> 01:47:05.333 It turns out there's another special method in Python, 952 01:47:05.333 --> 01:47:07.291 that we won't talk about in detail today, 953 01:47:07.291 --> 01:47:11.125 called underscore underscore, new, underscore underscore 954 01:47:11.125 --> 01:47:13.375 that actually handles the process of creating 955 01:47:13.375 --> 01:47:15.458 an empty object in memory for us. 956 01:47:15.458 --> 01:47:17.833 But, generally speaking, you, the programmer, 957 01:47:17.833 --> 01:47:19.916 don't need to manipulate the new function. 958 01:47:19.916 --> 01:47:20.916 It just works for you. 959 01:47:20.916 --> 01:47:23.750 Instead, you define your own init method here 960 01:47:23.750 --> 01:47:27.041 and init function inside of your class, and that method 961 01:47:27.041 --> 01:47:29.916 initializes the contents of the object. 962 01:47:29.916 --> 01:47:32.750 So there's technically a distinction between constructing the object 963 01:47:32.750 --> 01:47:35.250 with new and initializing it with init. 964 01:47:35.250 --> 01:47:39.250 But in the world of Python, you pretty much only worry about the init method. 965 01:47:39.250 --> 01:47:42.250 Python generally does the other part for you. 966 01:47:42.250 --> 01:47:43.000 A good question. 967 01:47:43.000 --> 01:47:44.333 Others? 968 01:47:44.333 --> 01:47:48.166 AUDIENCE: What about if you want to store more than one name or more 969 01:47:48.166 --> 01:47:49.083 than one house? 970 01:47:49.083 --> 01:47:49.750 DAVID J. MALAN: A good question. 971 01:47:49.750 --> 01:47:52.666 If you want to store more than one name or more than one house, 972 01:47:52.666 --> 01:47:54.083 you can do this in different ways. 973 01:47:54.083 --> 01:47:57.916 You could create other attributes-- technically called instance variables-- 974 01:47:57.916 --> 01:48:01.708 like self.name1, self.name2. 975 01:48:01.708 --> 01:48:03.708 But we've seen, in the past, that that is not 976 01:48:03.708 --> 01:48:06.291 a very good design, just to have multiple variables to store 977 01:48:06.291 --> 01:48:07.250 multiple things. 978 01:48:07.250 --> 01:48:12.625 Maybe, instead, you have an instance variable called self.names, plural, 979 01:48:12.625 --> 01:48:15.958 and you set it equal to a list of names or a list of houses. 980 01:48:15.958 --> 01:48:19.000 Now, in this case, I don't think that really solves a problem because I'm 981 01:48:19.000 --> 01:48:22.958 trying to implement a student, singular, so it doesn't really make sense 982 01:48:22.958 --> 01:48:24.250 to have multiple first names. 983 01:48:24.250 --> 01:48:27.166 Maybe a nickname, maybe a last name, so we could add those, too. 984 01:48:27.166 --> 01:48:29.375 But I don't think we need multiple names per se 985 01:48:29.375 --> 01:48:31.333 and, in this case, multiple houses. 986 01:48:31.333 --> 01:48:34.500 But absolutely, you could do that using some of our familiar building blocks 987 01:48:34.500 --> 01:48:35.916 like lists. 988 01:48:35.916 --> 01:48:37.541 Other questions? 989 01:48:37.541 --> 01:48:40.291 AUDIENCE: How are classes or objects represented in memory? 990 01:48:40.291 --> 01:48:43.041 DAVID J. MALAN: How are classes and objects represented in memory? 991 01:48:43.041 --> 01:48:44.958 So the class is technically just code. 992 01:48:44.958 --> 01:48:48.333 It is the code on the top of my file-- lines 1 through fou4-- 993 01:48:48.333 --> 01:48:52.250 that defines that blueprint, that template, if you will. 994 01:48:52.250 --> 01:48:54.833 Objects are stored in the computer's memory 995 01:48:54.833 --> 01:48:56.333 by taking up some number of bytes. 996 01:48:56.333 --> 01:48:59.125 So you're probably familiar with bytes or kilobytes or megabytes. 997 01:48:59.125 --> 01:49:01.750 There's some chunk of bytes, probably all 998 01:49:01.750 --> 01:49:03.708 in the same location in the computer's memory 999 01:49:03.708 --> 01:49:07.916 or RAM, where those objects are stored. 1000 01:49:07.916 --> 01:49:11.500 But that's what Python, the program, handles for you. 1001 01:49:11.500 --> 01:49:14.958 Python the interpreter figures out where in the computer's memory to put it. 1002 01:49:14.958 --> 01:49:18.041 You and I, the programmers, get to think and solve problems at this level. 1003 01:49:18.041 --> 01:49:21.916 Python, the interpreter, handles those lower level details for you. 1004 01:49:21.916 --> 01:49:25.166 How about one final question on classes and objects? 1005 01:49:25.166 --> 01:49:28.125 AUDIENCE: So my question is if we can the same do 1006 01:49:28.125 --> 01:49:31.625 the same thing with the dictionaries, so why to use classes? 1007 01:49:31.625 --> 01:49:32.875 DAVID J. MALAN: Good question. 1008 01:49:32.875 --> 01:49:34.750 If you can do the same things as you can with dictionaries, 1009 01:49:34.750 --> 01:49:35.958 why should you use classes? 1010 01:49:35.958 --> 01:49:38.416 Because we are just scratching the surface now of what 1011 01:49:38.416 --> 01:49:39.833 you can do with classes. 1012 01:49:39.833 --> 01:49:42.666 Allow me to go back, now, to my keyboard and show you 1013 01:49:42.666 --> 01:49:44.375 more of what you can do with classes. 1014 01:49:44.375 --> 01:49:47.750 But in short, you can do much more with classes. 1015 01:49:47.750 --> 01:49:51.791 You can ensure the correctness of your data much more with classes. 1016 01:49:51.791 --> 01:49:53.166 You can error-check things. 1017 01:49:53.166 --> 01:49:57.375 And generally, you can design more complicated software more effectively. 1018 01:49:57.375 --> 01:49:59.458 And we'll continue to see, today, features 1019 01:49:59.458 --> 01:50:02.416 of Python and object-oriented programming more generally 1020 01:50:02.416 --> 01:50:04.875 that allows us to do just that. 1021 01:50:04.875 --> 01:50:09.333 So let me propose, in fact, that first, let's just tighten up this current 1022 01:50:09.333 --> 01:50:13.916 implementation, which again has us with an init method that just declares two 1023 01:50:13.916 --> 01:50:16.875 instance variables-- self.name and self.house, house, which, again, 1024 01:50:16.875 --> 01:50:21.416 just creates those variables inside of the otherwise empty object and assigns 1025 01:50:21.416 --> 01:50:22.125 them values-- 1026 01:50:22.125 --> 01:50:23.708 name and house, respectively. 1027 01:50:23.708 --> 01:50:26.000 Let me go ahead and just do one little thing here. 1028 01:50:26.000 --> 01:50:27.791 I don't really need this student variable. 1029 01:50:27.791 --> 01:50:31.750 Let me just tighten this up so that each time we improve or change the code, 1030 01:50:31.750 --> 01:50:35.000 we're focusing, really, on just the minimal changes alone. 1031 01:50:35.000 --> 01:50:37.250 So I've not fundamentally done anything different. 1032 01:50:37.250 --> 01:50:39.291 I just got rid of the variable name, and I'm just 1033 01:50:39.291 --> 01:50:43.125 returning the return value of this student function 1034 01:50:43.125 --> 01:50:45.125 that's constructing my new object for me. 1035 01:50:45.125 --> 01:50:48.833 So I'm just tightening things up as we've done many times in the past. 1036 01:50:48.833 --> 01:50:53.291 Well, what if something goes wrong in creating this student? 1037 01:50:53.291 --> 01:50:56.791 For instance, what if the user does not give us a name, and they just hit 1038 01:50:56.791 --> 01:50:58.791 Enter when prompted for name. 1039 01:50:58.791 --> 01:51:01.250 I don't want to put in my computer's memory 1040 01:51:01.250 --> 01:51:04.375 a bogus student object that has no name. 1041 01:51:04.375 --> 01:51:07.833 I'd ideally like to check for errors before I even create it 1042 01:51:07.833 --> 01:51:09.583 so I don't create a nameless student. 1043 01:51:09.583 --> 01:51:13.791 It would just be weird and probably a bug to have an object that has no name. 1044 01:51:13.791 --> 01:51:16.208 Similarly, I don't want the user to be able to type 1045 01:51:16.208 --> 01:51:18.708 in something random as their house. 1046 01:51:18.708 --> 01:51:21.375 At least in the world of Harry Potter, there's really 1047 01:51:21.375 --> 01:51:23.791 only four houses, at Hogwarts at least. 1048 01:51:23.791 --> 01:51:25.666 There's, again, Gryffindor and Hufflepuff 1049 01:51:25.666 --> 01:51:29.625 and Ravenclaw and Slytherin-- a list of four valid houses. 1050 01:51:29.625 --> 01:51:34.291 It would be nice if I somehow validated that the user's input is indeed 1051 01:51:34.291 --> 01:51:35.541 in that list. 1052 01:51:35.541 --> 01:51:39.916 Now, I could do all of that validation in my get_student function. 1053 01:51:39.916 --> 01:51:41.750 I could check, is the name empty? 1054 01:51:41.750 --> 01:51:44.416 If so, don't create the student object. 1055 01:51:44.416 --> 01:51:46.666 Is the house one of those four houses? 1056 01:51:46.666 --> 01:51:49.083 If not, don't create the student object. 1057 01:51:49.083 --> 01:51:52.750 But that would be rather decoupled from the student itself. 1058 01:51:52.750 --> 01:51:57.625 get_student currently exists as just my own function in my student.py file. 1059 01:51:57.625 --> 01:52:01.125 But classes-- and really, object-oriented programming-- more 1060 01:52:01.125 --> 01:52:05.291 generally encourages you to encapsulate, inside 1061 01:52:05.291 --> 01:52:09.750 of a class, all functionality related to that class. 1062 01:52:09.750 --> 01:52:12.375 So if you want to validate that a name exists-- 1063 01:52:12.375 --> 01:52:14.750 if you want to validate that a house is correct, 1064 01:52:14.750 --> 01:52:20.041 that belongs just fundamentally in the class called student itself, 1065 01:52:20.041 --> 01:52:22.583 not in some random function that you wrote elsewhere. 1066 01:52:22.583 --> 01:52:24.875 Again, this is just methodology because, again, 1067 01:52:24.875 --> 01:52:28.541 if we think about writing code that gets longer and longer, more and more 1068 01:52:28.541 --> 01:52:31.250 complicated, it should make just intuitive sense that, 1069 01:52:31.250 --> 01:52:34.250 if you keep all the house-- 1070 01:52:34.250 --> 01:52:37.208 all of the name and all of the house-related code in the student, 1071 01:52:37.208 --> 01:52:38.708 it's just better organization. 1072 01:52:38.708 --> 01:52:41.583 Keep all of the related code together, and that's probably 1073 01:52:41.583 --> 01:52:43.125 going to set you up for more success. 1074 01:52:43.125 --> 01:52:45.166 And indeed, that's part of this methodology 1075 01:52:45.166 --> 01:52:47.125 of object-oriented programming. 1076 01:52:47.125 --> 01:52:51.916 Let me go ahead now and change my students classes 1077 01:52:51.916 --> 01:52:53.875 init method to do this. 1078 01:52:53.875 --> 01:52:56.375 If the name is blank-- 1079 01:52:56.375 --> 01:52:59.333 so if not name-- and we've seen this kind of syntax before. 1080 01:52:59.333 --> 01:53:02.666 If you say in Python, Pythonically, if not name, 1081 01:53:02.666 --> 01:53:04.500 that's doing something like this. 1082 01:53:04.500 --> 01:53:07.291 If name equals, equals, quote, unquote-- 1083 01:53:07.291 --> 01:53:09.041 but I can do this a little more elegantly. 1084 01:53:09.041 --> 01:53:11.958 Just say, if not name, would be the more Pythonic. 1085 01:53:11.958 --> 01:53:15.583 Well, I want to return an error. 1086 01:53:15.583 --> 01:53:17.208 I might want to do something like this. 1087 01:53:17.208 --> 01:53:18.916 Print missing name. 1088 01:53:18.916 --> 01:53:20.541 But this is not good enough. 1089 01:53:20.541 --> 01:53:24.000 It does not suffice to just print out missing name 1090 01:53:24.000 --> 01:53:26.083 and then let the rest of the code go through. 1091 01:53:26.083 --> 01:53:27.791 All right, well, what could I do instead? 1092 01:53:27.791 --> 01:53:30.791 In the past, we've seen another technique I could do sys.exit, 1093 01:53:30.791 --> 01:53:33.583 and I could say something like missing name, and I could go up here 1094 01:53:33.583 --> 01:53:34.750 and I could import sys. 1095 01:53:34.750 --> 01:53:37.958 But this is a really obnoxious solution to the problem. 1096 01:53:37.958 --> 01:53:41.250 Just because you or maybe a colleague messed up 1097 01:53:41.250 --> 01:53:43.750 and called a function with an invalid name, 1098 01:53:43.750 --> 01:53:45.666 you're going to quit my whole program? 1099 01:53:45.666 --> 01:53:49.750 That's really, really extreme of a response, 1100 01:53:49.750 --> 01:53:52.125 and you probably don't want to do that if your program is 1101 01:53:52.125 --> 01:53:53.041 in the middle of running. 1102 01:53:53.041 --> 01:53:54.625 You might want to clean some stuff up. 1103 01:53:54.625 --> 01:53:57.958 You might want to save files you don't want to just exit a program sometimes 1104 01:53:57.958 --> 01:54:01.208 in some arbitrary line, just because input was invalid. 1105 01:54:01.208 --> 01:54:03.333 So I don't think we want to do that either. 1106 01:54:03.333 --> 01:54:07.375 But we do, now, have a mechanism for signaling errors. 1107 01:54:07.375 --> 01:54:09.500 Unfortunately, I can't do something like this. 1108 01:54:09.500 --> 01:54:13.625 I could try returning none and say, uh-uh, this student does not exist. 1109 01:54:13.625 --> 01:54:15.666 I'm going to hand you back none instead. 1110 01:54:15.666 --> 01:54:16.916 But it's too late. 1111 01:54:16.916 --> 01:54:21.416 If we scroll back down to where I'm creating the student, it's on line 17 1112 01:54:21.416 --> 01:54:23.083 now where I've highlighted this code. 1113 01:54:23.083 --> 01:54:25.541 The student has already been created. 1114 01:54:25.541 --> 01:54:28.750 There is an object somewhere in the computer's memory 1115 01:54:28.750 --> 01:54:30.375 that's structured as a student. 1116 01:54:30.375 --> 01:54:32.708 It just doesn't have any values inside of it. 1117 01:54:32.708 --> 01:54:35.541 But it's too late, therefore, to return none. 1118 01:54:35.541 --> 01:54:36.666 That ship has sailed. 1119 01:54:36.666 --> 01:54:37.958 The object exists. 1120 01:54:37.958 --> 01:54:40.458 You can't just suddenly say, nope, nope, there is no object. 1121 01:54:40.458 --> 01:54:41.625 There is an object. 1122 01:54:41.625 --> 01:54:43.583 It's up to you to signal an error. 1123 01:54:43.583 --> 01:54:45.333 And how do you signal an error? 1124 01:54:45.333 --> 01:54:48.083 Well, we've actually seen this before, but we haven't had occasion 1125 01:54:48.083 --> 01:54:49.583 to create our own errors. 1126 01:54:49.583 --> 01:54:54.750 It turns out, in Python, there's another keyword related to exceptions 1127 01:54:54.750 --> 01:54:58.583 that Python itself uses to raise all of those exceptions we've 1128 01:54:58.583 --> 01:54:59.750 talked about in the past. 1129 01:54:59.750 --> 01:55:04.500 When you've caught things like value errors or other such exceptions that 1130 01:55:04.500 --> 01:55:09.083 come with Python, well, it turns out you, the programmer can raise-- 1131 01:55:09.083 --> 01:55:12.875 that is create your own exceptions when something just really goes wrong-- 1132 01:55:12.875 --> 01:55:15.750 not wrong enough that you want to quit and exit the whole program, 1133 01:55:15.750 --> 01:55:18.958 but enough that you need to somehow alert the programmer 1134 01:55:18.958 --> 01:55:20.583 that there has been an error. 1135 01:55:20.583 --> 01:55:23.583 Something exceptional, in a very bad way-- something 1136 01:55:23.583 --> 01:55:29.250 exceptional has happened, and let them try to catch that exception as needed. 1137 01:55:29.250 --> 01:55:32.250 So let me go back to VS Code here and propose 1138 01:55:32.250 --> 01:55:36.500 that, if the user passes in an invalid name-- it's just empty, 1139 01:55:36.500 --> 01:55:37.833 so there's not a name. 1140 01:55:37.833 --> 01:55:39.875 Well, what I really want to do is this. 1141 01:55:39.875 --> 01:55:43.041 I want to raise a value error. 1142 01:55:43.041 --> 01:55:45.208 And we've seen the value errors before. 1143 01:55:45.208 --> 01:55:47.833 We've created value errors accidentally before. 1144 01:55:47.833 --> 01:55:51.416 And generally, you and I have tried to catch them if they happen. 1145 01:55:51.416 --> 01:55:55.375 Well, the flip side of this feature of exceptions in a language like Python 1146 01:55:55.375 --> 01:55:58.125 is that you, the programmer, can also raise exceptions 1147 01:55:58.125 --> 01:56:00.000 when something exceptional happens. 1148 01:56:00.000 --> 01:56:01.791 And you can even be more precise. 1149 01:56:01.791 --> 01:56:05.250 You don't have to raise a generic value error and let the programmer figure out 1150 01:56:05.250 --> 01:56:06.083 what went wrong. 1151 01:56:06.083 --> 01:56:10.291 You can treat value error and all exceptions in Python like functions 1152 01:56:10.291 --> 01:56:14.583 and actually pass to them an explanatory message like, quote, unquote, 1153 01:56:14.583 --> 01:56:17.875 "Missing name," so that at least the programmer, when they encounter 1154 01:56:17.875 --> 01:56:19.458 this error, knows, oh, I messed up. 1155 01:56:19.458 --> 01:56:22.833 I didn't make sure that the user has a name. 1156 01:56:22.833 --> 01:56:25.125 And now, what do you want to do instead? 1157 01:56:25.125 --> 01:56:28.708 Well, now, if you're the programmer, you could do something like this. 1158 01:56:28.708 --> 01:56:34.708 You could try to create a student except if there's a value error. 1159 01:56:34.708 --> 01:56:37.208 Then you could handle it in some way. 1160 01:56:37.208 --> 01:56:39.500 And I'm going to wave my hand with a dot, dot, dot, 1161 01:56:39.500 --> 01:56:40.791 at how you would handle it. 1162 01:56:40.791 --> 01:56:44.666 But you would handle it using try and accept, just like we have in the past, 1163 01:56:44.666 --> 01:56:46.666 and that would allow you, the programmer, 1164 01:56:46.666 --> 01:56:48.166 to try to create the student. 1165 01:56:48.166 --> 01:56:52.291 But if something goes wrong, OK, I'll handle it nonetheless. 1166 01:56:52.291 --> 01:56:54.958 So what's new here, again, is this raise keyword, 1167 01:56:54.958 --> 01:56:59.250 that just lets you and I actually raise our own exceptions 1168 01:56:59.250 --> 01:57:00.458 to signal these errors. 1169 01:57:00.458 --> 01:57:03.000 Well, let me go back to my code here, and I'm just 1170 01:57:03.000 --> 01:57:06.208 going to go ahead and not bother trying or catching this error. 1171 01:57:06.208 --> 01:57:09.083 For now, we'll just focus on raising it and assume 1172 01:57:09.083 --> 01:57:12.375 that, from our recon exceptions, you could add try and accept 1173 01:57:12.375 --> 01:57:13.833 as needed in places. 1174 01:57:13.833 --> 01:57:16.916 Let me go back to the code here and propose that something else could 1175 01:57:16.916 --> 01:57:18.416 go wrong with house. 1176 01:57:18.416 --> 01:57:19.791 If there is a name, we're good. 1177 01:57:19.791 --> 01:57:22.083 But if we're given a house but it's invalid, 1178 01:57:22.083 --> 01:57:24.958 we should probably raise an exception for that, too. 1179 01:57:24.958 --> 01:57:26.208 So what if we do this? 1180 01:57:26.208 --> 01:57:32.208 If house is not in the list containing "Gryffindor," quote, unquote, 1181 01:57:32.208 --> 01:57:34.708 "Hufflepuff," quote, unquote-- 1182 01:57:34.708 --> 01:57:38.875 let's see, "Ravenclaw," quote, unquote, or "Slytherin," 1183 01:57:38.875 --> 01:57:41.833 quote, unquote, then, with my colon, let's 1184 01:57:41.833 --> 01:57:43.416 raise another type of value error. 1185 01:57:43.416 --> 01:57:45.458 But rather than raise a generic value error, 1186 01:57:45.458 --> 01:57:49.750 let's pass in an argument, quote, unquote, "Invalid house." 1187 01:57:49.750 --> 01:57:52.708 And so here we now see a capability that we 1188 01:57:52.708 --> 01:57:55.916 can do with classes that we can't with dictionaries. 1189 01:57:55.916 --> 01:58:00.541 If you add an attribute to a dictionary, a key to a dictionary, 1190 01:58:00.541 --> 01:58:02.083 it's going in no matter what. 1191 01:58:02.083 --> 01:58:06.000 Even if the name is empty, even if the house is a completely random string 1192 01:58:06.000 --> 01:58:07.833 of text that's not one of these four houses, 1193 01:58:07.833 --> 01:58:09.333 it's going into that dictionary. 1194 01:58:09.333 --> 01:58:12.708 But with a class, and by way of this init method, 1195 01:58:12.708 --> 01:58:17.750 you and I can now control exactly what's going to be installed, if you will, 1196 01:58:17.750 --> 01:58:19.041 inside of this object. 1197 01:58:19.041 --> 01:58:22.458 You have a little more control now over correctness. 1198 01:58:22.458 --> 01:58:26.583 And so now let me go ahead and scroll back down to my terminal window 1199 01:58:26.583 --> 01:58:27.291 and clear it. 1200 01:58:27.291 --> 01:58:29.500 Let me run Python of student.py. 1201 01:58:29.500 --> 01:58:31.250 Let me type in something like Harry. 1202 01:58:31.250 --> 01:58:33.958 Let me type in Gryffindor, Enter, and we see 1203 01:58:33.958 --> 01:58:35.666 that, indeed, Harry is from Gryffindor. 1204 01:58:35.666 --> 01:58:37.666 What if I made a mistake, though? 1205 01:58:37.666 --> 01:58:40.750 What if I ran Python of student.py and typed Harry as the name, 1206 01:58:40.750 --> 01:58:44.416 but this time typed in Number Four, Privet Drive, which 1207 01:58:44.416 --> 01:58:47.500 is where he grew up, instead of his proper Hogwarts house. 1208 01:58:47.500 --> 01:58:51.333 Let me hit Enter now, and now you see a value error. 1209 01:58:51.333 --> 01:58:54.458 But this isn't one that Python generated for us, per se. 1210 01:58:54.458 --> 01:58:56.166 I raised this error. 1211 01:58:56.166 --> 01:58:59.666 And therefore, if I went in and wrote more code in my get_student function, 1212 01:58:59.666 --> 01:59:04.666 I could also catch this error with our usual try except syntax. 1213 01:59:04.666 --> 01:59:09.250 So all we have now is not just classes in our toolkit, but even more powers 1214 01:59:09.250 --> 01:59:12.583 when it comes to exceptions, and not just catching them ourselves 1215 01:59:12.583 --> 01:59:15.333 but raising them ourselves, too. 1216 01:59:15.333 --> 01:59:21.708 Any questions now on this use of classes and init and now this ability 1217 01:59:21.708 --> 01:59:26.000 to raise exceptions when something goes wrong inside of the initialization? 1218 01:59:26.000 --> 01:59:29.416 AUDIENCE: So what if the user has a middle name-- 1219 01:59:29.416 --> 01:59:31.291 name, middle name, and last name? 1220 01:59:31.291 --> 01:59:32.750 How would you fix that? 1221 01:59:32.750 --> 01:59:35.250 DAVID J. MALAN: Good question. 1222 01:59:35.250 --> 01:59:38.916 If you wanted the student to have a first name, middle name, and last name, 1223 01:59:38.916 --> 01:59:41.041 we could do this in a bunch of different ways. 1224 01:59:41.041 --> 01:59:44.458 The simplest, though, if-- let me clear my screen here, and let 1225 01:59:44.458 --> 01:59:46.041 me just temporarily do this. 1226 01:59:46.041 --> 01:59:51.333 Let me propose that the init method take in a first argument, a middle argument, 1227 01:59:51.333 --> 01:59:53.000 and a last argument. 1228 01:59:53.000 --> 01:59:57.833 And then what I think I would do down here is ultimately have first = first, 1229 01:59:57.833 --> 02:00:00.916 and then I would do the same thing for middle and last. 1230 02:00:00.916 --> 02:00:05.541 So middle and middle, and then last and last. 1231 02:00:05.541 --> 02:00:08.583 And then what I would have to do here is, 1232 02:00:08.583 --> 02:00:11.208 when I actually ask the user for their name, 1233 02:00:11.208 --> 02:00:12.750 I might need to really go all out. 1234 02:00:12.750 --> 02:00:15.166 I might need to ask them first for their first name 1235 02:00:15.166 --> 02:00:18.916 and store that in a variable called first, and therefore pass in first. 1236 02:00:18.916 --> 02:00:21.833 I might similarly need to ask them for their middle name 1237 02:00:21.833 --> 02:00:25.500 and store that in a variable and then pass in a second argument, middle. 1238 02:00:25.500 --> 02:00:28.375 And then lastly, if you will, let me go ahead and create 1239 02:00:28.375 --> 02:00:31.916 a third variable called last, get the input for their last name, 1240 02:00:31.916 --> 02:00:34.041 and pass that in as well. 1241 02:00:34.041 --> 02:00:38.125 I could instead just use one input and just ask them for their whole name. 1242 02:00:38.125 --> 02:00:42.583 So type in David Malan, Enter, or David J. Malan-- all three of them, 1243 02:00:42.583 --> 02:00:45.666 and maybe I could use Python's split function, 1244 02:00:45.666 --> 02:00:47.583 maybe a regular expression to tease it apart. 1245 02:00:47.583 --> 02:00:49.625 That's probably going to be messy because there's 1246 02:00:49.625 --> 02:00:52.083 going to be people who don't have just two or three names. 1247 02:00:52.083 --> 02:00:53.458 They might have four or five. 1248 02:00:53.458 --> 02:00:55.958 So maybe sometimes it's better to have multiple prompts. 1249 02:00:55.958 --> 02:00:58.291 But that's not a problem because, with a class, 1250 02:00:58.291 --> 02:01:02.291 we have the expressiveness to take in more arguments if we want. 1251 02:01:02.291 --> 02:01:04.041 We could even take a list if we wanted. 1252 02:01:04.041 --> 02:01:06.916 But I think we'd probably want to have even more error checking then, 1253 02:01:06.916 --> 02:01:11.458 not just for name but for first, and then maybe for middle, and then 1254 02:01:11.458 --> 02:01:12.208 maybe for last. 1255 02:01:12.208 --> 02:01:14.541 So it just is more and more code, though there would be 1256 02:01:14.541 --> 02:01:17.041 ways to perhaps consolidate that, too. 1257 02:01:17.041 --> 02:01:22.500 Let me undo all of that and see if there are other questions now on classes. 1258 02:01:22.500 --> 02:01:24.291 AUDIENCE: I assume classes are something I 1259 02:01:24.291 --> 02:01:25.916 might do at the beginning of a project. 1260 02:01:25.916 --> 02:01:28.583 Can I just put them in a different file and import them 1261 02:01:28.583 --> 02:01:32.083 into my project, or my main code as needed? 1262 02:01:32.083 --> 02:01:33.208 DAVID J. MALAN: Absolutely. 1263 02:01:33.208 --> 02:01:34.208 A really good question. 1264 02:01:34.208 --> 02:01:36.083 You could imagine wanting to use this student 1265 02:01:36.083 --> 02:01:40.666 class, not just in student.py but in other files or other projects of yours. 1266 02:01:40.666 --> 02:01:44.458 And absolutely, you can create your own library of classes 1267 02:01:44.458 --> 02:01:48.000 by putting the student class in your own module or package, 1268 02:01:48.000 --> 02:01:50.583 per our discussion in the past about libraries more generally. 1269 02:01:50.583 --> 02:01:52.250 And absolutely, you can do that. 1270 02:01:52.250 --> 02:01:54.833 And later today, what we see is we've actually 1271 02:01:54.833 --> 02:01:58.166 been using classes-- you and I-- before, in third party libraries. 1272 02:01:58.166 --> 02:02:00.416 So you, too, can absolutely do the same. 1273 02:02:00.416 --> 02:02:03.666 How about one more question on classes? 1274 02:02:03.666 --> 02:02:06.541 AUDIENCE: Can you have optional variables in classes? 1275 02:02:06.541 --> 02:02:09.666 And two, can you have your own error names, like-- 1276 02:02:09.666 --> 02:02:13.250 let's be egotistical and say I want to raise Eric error? 1277 02:02:13.250 --> 02:02:16.166 DAVID J. MALAN: Short answer, yes. 1278 02:02:16.166 --> 02:02:18.750 These init functions are just like Python functions more 1279 02:02:18.750 --> 02:02:21.000 generally, even though they're special in that they're 1280 02:02:21.000 --> 02:02:23.666 going to get called automatically by Python for you. 1281 02:02:23.666 --> 02:02:25.375 But if you wanted to make house optional, 1282 02:02:25.375 --> 02:02:26.750 you could do something like this. 1283 02:02:26.750 --> 02:02:31.791 You could give it a default value in the init function's signature 1284 02:02:31.791 --> 02:02:34.250 so to speak-- in that first line of code on line two. 1285 02:02:34.250 --> 02:02:36.875 And that would allow me to not have to pass in house. 1286 02:02:36.875 --> 02:02:39.750 In this case, I'm going to continue to always pass in name and house, 1287 02:02:39.750 --> 02:02:41.500 but you could make things optional. 1288 02:02:41.500 --> 02:02:45.458 And yes, to your second question, if you wanted to have your own error message, 1289 02:02:45.458 --> 02:02:50.791 like an Eric error, you could actually create your own Eric error exception. 1290 02:02:50.791 --> 02:02:53.000 And we'll see, in a little bit, that there's actually 1291 02:02:53.000 --> 02:02:58.041 a whole suite of exceptions that exist, and you, too, can invent those as well. 1292 02:02:58.041 --> 02:03:01.375 Let me propose, though, that we now introduce 1293 02:03:01.375 --> 02:03:06.250 one other aspect of this whereby we try printing out what a student looks like. 1294 02:03:06.250 --> 02:03:08.916 At the moment, if I scroll back down to my main function, 1295 02:03:08.916 --> 02:03:11.791 I'm still printing the student's name and house very manually. 1296 02:03:11.791 --> 02:03:14.833 I'm going inside of the object, doing student.name, 1297 02:03:14.833 --> 02:03:17.708 and I'm going inside of the object again and getting student.house, 1298 02:03:17.708 --> 02:03:20.750 just to see where the student is from. 1299 02:03:20.750 --> 02:03:23.666 But wouldn't it be nice if I could just print the student, 1300 02:03:23.666 --> 02:03:25.583 like I've been printing for weeks-- 1301 02:03:25.583 --> 02:03:29.458 any, int, or float, or str, or any other data type? 1302 02:03:29.458 --> 02:03:32.500 Well, let's see what happens if I just try printing the student, 1303 02:03:32.500 --> 02:03:36.166 instead of manually going inside and trying to create that sentence myself. 1304 02:03:36.166 --> 02:03:39.250 Well, in my terminal window-- let me go ahead and run Python of student.py 1305 02:03:39.250 --> 02:03:40.166 again. 1306 02:03:40.166 --> 02:03:41.250 Let me type in Harry. 1307 02:03:41.250 --> 02:03:42.541 Let me type in Gryffindor. 1308 02:03:42.541 --> 02:03:50.333 And voila, Harry-- whoa, OK, main student object at 0x102733e80. 1309 02:03:50.333 --> 02:03:51.916 Well, what is going on? 1310 02:03:51.916 --> 02:03:53.708 Well, if you were to run the same code, you 1311 02:03:53.708 --> 02:03:56.000 might actually see something different on your computer 1312 02:03:56.000 --> 02:03:57.208 in terms of that number. 1313 02:03:57.208 --> 02:04:01.958 But what you're really seeing is the underlying representation, as a string, 1314 02:04:01.958 --> 02:04:03.625 of this specific object. 1315 02:04:03.625 --> 02:04:06.875 In particular, you're seeing where in the computer's memory it is. 1316 02:04:06.875 --> 02:04:12.750 This number, 30x102733e80, refers to, essentially, a specific location 1317 02:04:12.750 --> 02:04:14.541 in the computer's memory or RAM. 1318 02:04:14.541 --> 02:04:19.166 That's not really that interesting for me or you or, generally speaking, 1319 02:04:19.166 --> 02:04:22.250 programmers, but it's just the default way of describing, 1320 02:04:22.250 --> 02:04:25.791 via print, what this thing is. 1321 02:04:25.791 --> 02:04:28.250 But I can override this as well. 1322 02:04:28.250 --> 02:04:31.791 It turns out that there are other special methods in Python 1323 02:04:31.791 --> 02:04:32.958 when it comes to classes-- 1324 02:04:32.958 --> 02:04:36.375 not just underscore underscore, init, underscore underscore, 1325 02:04:36.375 --> 02:04:40.541 but, continuing in that same pattern, underscore underscore, str, 1326 02:04:40.541 --> 02:04:42.041 underscore underscore. 1327 02:04:42.041 --> 02:04:47.000 So this, too, is a special method that, if you define it inside of your class, 1328 02:04:47.000 --> 02:04:50.541 Python will just automatically call this function 1329 02:04:50.541 --> 02:04:56.541 for you any time some other function wants to see your object as a string. 1330 02:04:56.541 --> 02:04:59.750 Print wants to see your object as a string. 1331 02:04:59.750 --> 02:05:02.958 But by default, if you don't have this method defined in your class, 1332 02:05:02.958 --> 02:05:06.416 it's going to print out that very ugly esoteric incarnation thereof, 1333 02:05:06.416 --> 02:05:10.416 where it says main__.Student object at 0x, dot, dot, dot. 1334 02:05:10.416 --> 02:05:13.250 Well, how can I then define my own str function? 1335 02:05:13.250 --> 02:05:17.041 Well, here, back in VS Code, let me propose that I go in 1336 02:05:17.041 --> 02:05:23.416 and define not just __ init, but let me define a second function in this class 1337 02:05:23.416 --> 02:05:25.041 here, as follows-- 1338 02:05:25.041 --> 02:05:28.416 def __ str __. 1339 02:05:28.416 --> 02:05:29.333 There are two. 1340 02:05:29.333 --> 02:05:32.541 Even though the font in VS Code is putting the two underscore so close, 1341 02:05:32.541 --> 02:05:34.375 it just looks like a longer underscore. 1342 02:05:34.375 --> 02:05:37.583 There are indeed two there, on the left and the right just like for init. 1343 02:05:37.583 --> 02:05:42.791 This one only takes one argument that, by convention, is always called self 1344 02:05:42.791 --> 02:05:44.500 so that you have access to it. 1345 02:05:44.500 --> 02:05:47.708 And then, indented below that after a colon, 1346 02:05:47.708 --> 02:05:51.125 I'm going to go ahead and create a format string and return it. 1347 02:05:51.125 --> 02:05:53.500 So let me go ahead and return-- 1348 02:05:53.500 --> 02:05:56.375 how about something generic first like "a student." 1349 02:05:56.375 --> 02:05:58.916 So I'm not going to bother even trying to figure out 1350 02:05:58.916 --> 02:06:00.625 what this student's name or house is. 1351 02:06:00.625 --> 02:06:02.875 I'm just going to always return "a student." 1352 02:06:02.875 --> 02:06:08.666 Let me go back now to my earlier code, which has print (student) on line 16. 1353 02:06:08.666 --> 02:06:13.041 Let me clear my terminal window and rerun Python of student.py, Enter. 1354 02:06:13.041 --> 02:06:14.958 Type in Harry, type in Gryffindor. 1355 02:06:14.958 --> 02:06:17.333 Last time, I saw that very cryptic output. 1356 02:06:17.333 --> 02:06:20.958 This time, I see, more generically, "a student." 1357 02:06:20.958 --> 02:06:23.375 More readable but not very enlightening. 1358 02:06:23.375 --> 02:06:24.541 Which student is this? 1359 02:06:24.541 --> 02:06:31.791 Well, notice that the double underscore str method takes in this self argument 1360 02:06:31.791 --> 02:06:36.708 by default. It's just the way the Python authors designed this method. 1361 02:06:36.708 --> 02:06:41.833 It will always be passed a reference to the current student object. 1362 02:06:41.833 --> 02:06:43.000 What do I mean by that? 1363 02:06:43.000 --> 02:06:45.916 When this line of code on line 6 is called, 1364 02:06:45.916 --> 02:06:48.666 print, because it's hoping it's going to get a string, 1365 02:06:48.666 --> 02:06:52.750 is going to trigger the underscore underscore, str, underscore 1366 02:06:52.750 --> 02:06:54.458 underscore method to be called. 1367 02:06:54.458 --> 02:06:58.416 And Python, for you, automatically is going to pass into that method 1368 02:06:58.416 --> 02:07:01.208 a reference to the object that's trying to be 1369 02:07:01.208 --> 02:07:05.291 printed so that you, the programmer, can do something like this. 1370 02:07:05.291 --> 02:07:08.000 Here's an f string with double quotes as usual. 1371 02:07:08.000 --> 02:07:10.458 I'm going to use some curly braces and say print out 1372 02:07:10.458 --> 02:07:14.250 self.name from self.house. 1373 02:07:14.250 --> 02:07:17.458 So there's nothing new in what I've just done. 1374 02:07:17.458 --> 02:07:19.958 It's just an f string-- an f on the beginning, 1375 02:07:19.958 --> 02:07:22.708 two double quotes, a couple of pairs of curly braces. 1376 02:07:22.708 --> 02:07:28.750 But because, automatically, this str method gets passed self, so to speak, 1377 02:07:28.750 --> 02:07:32.333 a reference to the current object, I can go inside of that object 1378 02:07:32.333 --> 02:07:33.083 and grab the name. 1379 02:07:33.083 --> 02:07:36.000 I can go inside that object again and grab the house. 1380 02:07:36.000 --> 02:07:38.791 So now, when I go back to my terminal window-- 1381 02:07:38.791 --> 02:07:41.041 previously it just printed out a student. 1382 02:07:41.041 --> 02:07:44.416 But now, if I run Python of student.py, Enter-- 1383 02:07:44.416 --> 02:07:48.083 type in Harry, type in Gryffindor, and one more time hit Enter, 1384 02:07:48.083 --> 02:07:50.750 Harry is again from Gryffindor. 1385 02:07:50.750 --> 02:07:52.500 But if I run this yet again-- 1386 02:07:52.500 --> 02:07:57.125 let's, for instance, do Draco is from Slytherin, Enter. 1387 02:07:57.125 --> 02:07:58.625 Draco's from Slytherin. 1388 02:07:58.625 --> 02:08:04.291 Now it's customized to the specific object that we're trying to print. 1389 02:08:04.291 --> 02:08:08.250 Questions on this function here-- 1390 02:08:08.250 --> 02:08:11.666 this Dunder str method. 1391 02:08:11.666 --> 02:08:14.541 AUDIENCE: Is there anything else that the underscore underscore, 1392 02:08:14.541 --> 02:08:16.666 str method can do? 1393 02:08:16.666 --> 02:08:19.416 The other question is, what's the difference between str and repr. 1394 02:08:19.416 --> 02:08:20.750 DAVID J. MALAN: A good question. 1395 02:08:20.750 --> 02:08:24.458 So there are many other methods that come with Python classes 1396 02:08:24.458 --> 02:08:26.041 that start with underscore underscore. 1397 02:08:26.041 --> 02:08:27.958 We're just scratching the surface, and we'll pretty much 1398 02:08:27.958 --> 02:08:29.083 focus primarily on these. 1399 02:08:29.083 --> 02:08:30.833 But yes, there are many others, and we'll 1400 02:08:30.833 --> 02:08:33.666 see at least one other in just a little bit. 1401 02:08:33.666 --> 02:08:37.375 Among the others is one called repr, which is 1402 02:08:37.375 --> 02:08:39.583 a representation of the Python object. 1403 02:08:39.583 --> 02:08:43.000 Generally speaking, the underscore underscore, repr, 1404 02:08:43.000 --> 02:08:45.791 underscore underscore method is meant for developers' eyes. 1405 02:08:45.791 --> 02:08:49.166 It typically has more information than "Harry from Gryffindor." 1406 02:08:49.166 --> 02:08:53.083 It would also say what type of object it is, like a student, capital 1407 02:08:53.083 --> 02:08:56.583 S, whereas underscore underscore, str, underscore underscore is generally 1408 02:08:56.583 --> 02:08:58.125 meant for users-- 1409 02:08:58.125 --> 02:09:01.500 the users of the program, and it's meant to be even more user-friendly. 1410 02:09:01.500 --> 02:09:04.208 But both of those can be overridden as you see fit. 1411 02:09:04.208 --> 02:09:08.041 Well, let me propose now that we pick up where we've left off on student 1412 02:09:08.041 --> 02:09:11.958 and just add even more functionality, but not just these special methods 1413 02:09:11.958 --> 02:09:15.166 like double underscore init and double underscore str. 1414 02:09:15.166 --> 02:09:18.750 Let's create our own methods because therein lies the real power 1415 02:09:18.750 --> 02:09:21.875 and flexibility of classes if you and I as the programmers 1416 02:09:21.875 --> 02:09:25.291 can invent new functionality that's specific to students. 1417 02:09:25.291 --> 02:09:29.125 For instance, students at Hogwarts, over the time in school, 1418 02:09:29.125 --> 02:09:31.500 learn how to cast a certain type of spell. 1419 02:09:31.500 --> 02:09:34.083 So when they say, Expecto Patronum, something 1420 02:09:34.083 --> 02:09:36.083 comes out of their wand that typically resembles 1421 02:09:36.083 --> 02:09:37.458 an animal or something like that. 1422 02:09:37.458 --> 02:09:40.000 It's a special spell that they have to practice and practice. 1423 02:09:40.000 --> 02:09:44.041 So let's see if we can't store, not just the student's name and their house, 1424 02:09:44.041 --> 02:09:47.041 but also their "patronus," what, actually, they 1425 02:09:47.041 --> 02:09:48.625 conjure when using this spell. 1426 02:09:48.625 --> 02:09:50.750 Well, let me go ahead and clear my terminal window. 1427 02:09:50.750 --> 02:09:56.583 And in the top of my code here, in the init method of Student, let me go ahead 1428 02:09:56.583 --> 02:10:00.875 and start expecting a third argument, in addition to self, which automatically 1429 02:10:00.875 --> 02:10:02.833 gets passed in, called patronus. 1430 02:10:02.833 --> 02:10:06.125 And I'm not going to worry, for now, on validating 1431 02:10:06.125 --> 02:10:10.041 the patronus from an official list of valid patronuses, or patroni. 1432 02:10:10.041 --> 02:10:13.041 I'm instead going to go ahead and just blindly assign 1433 02:10:13.041 --> 02:10:16.458 it to self.patronus = patronus, and we're 1434 02:10:16.458 --> 02:10:19.083 going to let the user type whatever they want for now. 1435 02:10:19.083 --> 02:10:21.333 But I could certainly add more error checking 1436 02:10:21.333 --> 02:10:25.583 if I wanted to limit the patronus to a specific list of them here. 1437 02:10:25.583 --> 02:10:29.000 Let me go ahead now and prompt the user for this patronus, 1438 02:10:29.000 --> 02:10:33.666 as by-- in my get_student function-- defining a variable 1439 02:10:33.666 --> 02:10:36.375 called patronus or anything else, prompting the user 1440 02:10:36.375 --> 02:10:38.333 for input for their patronus. 1441 02:10:38.333 --> 02:10:42.708 And now I'm going to go ahead and pass in that third variable here. 1442 02:10:42.708 --> 02:10:46.041 So again, similar in spirit to just adding more and more attributes 1443 02:10:46.041 --> 02:10:47.875 to the class, I'm going to pass in all three 1444 02:10:47.875 --> 02:10:49.791 of these values instead of just two. 1445 02:10:49.791 --> 02:10:52.333 I'm not going to do anything interesting with that value yet. 1446 02:10:52.333 --> 02:10:55.583 But just to make sure I haven't made things worse by breaking my code, 1447 02:10:55.583 --> 02:10:57.875 let me run Python of student.py. 1448 02:10:57.875 --> 02:10:59.083 I'll type in Harry. 1449 02:10:59.083 --> 02:11:00.416 I'll type in Gryffindor. 1450 02:11:00.416 --> 02:11:02.500 And it turns out his patronus was a stag, . 1451 02:11:02.500 --> 02:11:06.125 And hit Enter I haven't seen what his patronus is in my output 1452 02:11:06.125 --> 02:11:08.708 because I didn't change my str method yet. 1453 02:11:08.708 --> 02:11:10.625 But at least I don't have any syntax errors. 1454 02:11:10.625 --> 02:11:12.958 So at least I've not made anything worse. 1455 02:11:12.958 --> 02:11:16.416 But suppose, now, I want to have functionality, not just 1456 02:11:16.416 --> 02:11:19.708 for initializing a student and printing out a student. 1457 02:11:19.708 --> 02:11:23.666 If my class is really meant to be a student, what I can do 1458 02:11:23.666 --> 02:11:27.416 is not just remember information about data about students. 1459 02:11:27.416 --> 02:11:31.291 What's powerful about classes, unlike dictionaries alone, 1460 02:11:31.291 --> 02:11:36.708 is that classes can have, not just variables or instance variables-- 1461 02:11:36.708 --> 02:11:38.625 those attributes we keep creating. 1462 02:11:38.625 --> 02:11:42.458 They can also have functions built in, a.k.a. 1463 02:11:42.458 --> 02:11:43.083 methods. 1464 02:11:43.083 --> 02:11:45.958 When a function is inside of a class, it's called a "method," 1465 02:11:45.958 --> 02:11:47.666 but it's still just a function. 1466 02:11:47.666 --> 02:11:51.000 At this point, we've seen two functions already-- two methods-- 1467 02:11:51.000 --> 02:11:54.958 called a double underscore init and double underscore str, 1468 02:11:54.958 --> 02:11:57.958 but those are special methods in that they just work. 1469 02:11:57.958 --> 02:12:01.416 If you define them, Python calls them automatically for you. 1470 02:12:01.416 --> 02:12:04.958 But what if you wanted to create more functionality for a student 1471 02:12:04.958 --> 02:12:09.000 so that your class really represents this real world, or maybe "fantasy" 1472 02:12:09.000 --> 02:12:13.375 world notion of a student, where students not only have names and houses 1473 02:12:13.375 --> 02:12:16.375 and patronuses; they also have functionality. 1474 02:12:16.375 --> 02:12:22.541 They have actions they can perform like casting a charm, a spell, magically. 1475 02:12:22.541 --> 02:12:26.333 Could we implement, therefore, a function called charm, that 1476 02:12:26.333 --> 02:12:30.291 actually uses their magical knowledge? 1477 02:12:30.291 --> 02:12:33.791 Well, let's go ahead and define our very own function as follows. 1478 02:12:33.791 --> 02:12:37.583 Let me clear my terminal window, scroll back up to my student class. 1479 02:12:37.583 --> 02:12:40.916 And instead of creating yet another function that's special, 1480 02:12:40.916 --> 02:12:45.416 with double underscores, I'm going to invent my own function, or method, 1481 02:12:45.416 --> 02:12:46.916 inside of this class. 1482 02:12:46.916 --> 02:12:51.500 I want to give Harry and Hermione and all of the other students 1483 02:12:51.500 --> 02:12:55.000 the ability to cast charms, so I'm going to define a function that I 1484 02:12:55.000 --> 02:12:57.291 can completely, on my own, call charm. 1485 02:12:57.291 --> 02:12:59.125 I could call this function anything I want. 1486 02:12:59.125 --> 02:13:01.708 But because it's a method inside of a class, 1487 02:13:01.708 --> 02:13:06.083 the convention is that it's always going to take at least one argument, called 1488 02:13:06.083 --> 02:13:11.000 self, by convention so that you have access to the current object, 1489 02:13:11.000 --> 02:13:13.083 even if you don't plan to use it, per se. 1490 02:13:13.083 --> 02:13:16.708 All right, let me go ahead and propose that we implement charm in such a way 1491 02:13:16.708 --> 02:13:21.750 that the method returns an emoji that's appropriate for each student's 1492 02:13:21.750 --> 02:13:23.125 patronus. 1493 02:13:23.125 --> 02:13:25.750 How to implement this-- well, inside of the charm method, 1494 02:13:25.750 --> 02:13:29.041 let's go ahead and match on self.patronus, 1495 02:13:29.041 --> 02:13:31.416 which is the instance variable containing a string that 1496 02:13:31.416 --> 02:13:33.458 represents each student's patronus. 1497 02:13:33.458 --> 02:13:38.208 And in the case that it matches a stag, for instance, for Harry, let's go ahead 1498 02:13:38.208 --> 02:13:40.333 and return maybe the closest emoji-- 1499 02:13:40.333 --> 02:13:41.750 this horse here. 1500 02:13:41.750 --> 02:13:44.625 How about in the case of an otter? 1501 02:13:44.625 --> 02:13:49.708 Well, in that case, let's go ahead and return, oh, maybe the closest match 1502 02:13:49.708 --> 02:13:52.583 to the otter, which might be this emoji here. 1503 02:13:52.583 --> 02:13:57.000 And let's see, in the case of a-- for Ron, rather than Hermione-- 1504 02:13:57.000 --> 02:14:02.791 a Jack Russell terrier, let's go ahead and return-- 1505 02:14:02.791 --> 02:14:04.625 don't have as many options here. 1506 02:14:04.625 --> 02:14:08.750 Why don't we go ahead and return the cutest available dog in that case. 1507 02:14:08.750 --> 02:14:14.250 And in the case of no patronus recognized, 1508 02:14:14.250 --> 02:14:16.375 as might cover someone like Draco, let's go ahead 1509 02:14:16.375 --> 02:14:19.750 and use a default case using the underscore as in the past, 1510 02:14:19.750 --> 02:14:22.791 and let's go ahead and return for this-- oh, what should happen 1511 02:14:22.791 --> 02:14:24.250 if someone doesn't have a patronus? 1512 02:14:24.250 --> 02:14:26.541 Why don't we just see a magical wand that 1513 02:14:26.541 --> 02:14:28.958 seems to fizzle out, as in this case? 1514 02:14:28.958 --> 02:14:31.333 All right, well, now, rather than just print the student, 1515 02:14:31.333 --> 02:14:33.375 let's go about printing their actual patronus. 1516 02:14:33.375 --> 02:14:36.000 So I'm going to go down to my main function here. 1517 02:14:36.000 --> 02:14:39.250 I'm going to still get a student, using the get_student function. 1518 02:14:39.250 --> 02:14:44.208 But rather than print student, let's go ahead and declare "Expecto Patronum!" 1519 02:14:44.208 --> 02:14:46.791 printing out just that as pure text. 1520 02:14:46.791 --> 02:14:50.750 And now let's go ahead and print out, not the student but, rather, 1521 02:14:50.750 --> 02:14:54.416 the return value of their own charm method. 1522 02:14:54.416 --> 02:14:58.583 So let me go back down to my terminal window and run Python of student.py 1523 02:14:58.583 --> 02:14:59.708 and Enter. 1524 02:14:59.708 --> 02:15:01.250 Name-- let's start with Harry. 1525 02:15:01.250 --> 02:15:03.500 He lives in Gryffindor. 1526 02:15:03.500 --> 02:15:05.083 Patronus is a stag. 1527 02:15:05.083 --> 02:15:06.291 And let's see-- 1528 02:15:06.291 --> 02:15:08.125 Expecto Patronum! 1529 02:15:08.125 --> 02:15:10.708 And of course, we'd see the stag emoji. 1530 02:15:10.708 --> 02:15:13.333 What about someone like Draco, who, at least in the books, 1531 02:15:13.333 --> 02:15:15.333 doesn't have a known patronus? 1532 02:15:15.333 --> 02:15:17.416 Well, let's go ahead and clear my terminal window, 1533 02:15:17.416 --> 02:15:22.625 rerun Python of student.py, and this time, let's type in Draco for name, 1534 02:15:22.625 --> 02:15:25.750 Slytherin for house, and Patronus is unknown. 1535 02:15:25.750 --> 02:15:27.583 So I'm just going to go ahead and hit Enter. 1536 02:15:27.583 --> 02:15:29.666 And now, Expecto Patronum! 1537 02:15:29.666 --> 02:15:32.916 And it just of sizzles instead. 1538 02:15:32.916 --> 02:15:38.208 Well, let me propose, now, that we remove this patronus code just 1539 02:15:38.208 --> 02:15:41.833 to simplify our world and focus on some of the other core 1540 02:15:41.833 --> 02:15:43.208 capabilities of classes. 1541 02:15:43.208 --> 02:15:44.958 So at the risk of disappointing, I'm going 1542 02:15:44.958 --> 02:15:47.916 to get rid of all of these beautiful emoji and charms, 1543 02:15:47.916 --> 02:15:51.875 and I'm going to go ahead and stop asking the user now for their patronus. 1544 02:15:51.875 --> 02:15:54.708 And I'm going to stop passing it into init here. 1545 02:15:54.708 --> 02:15:57.541 And I'm going to stop doing this here. 1546 02:15:57.541 --> 02:16:03.333 And I'm going to instead just go ahead and restore our use of print student 1547 02:16:03.333 --> 02:16:06.541 here, and I'm going to go ahead and get rid of patronus down here. 1548 02:16:06.541 --> 02:16:10.541 So just essentially undo all of the fun charms we just created. 1549 02:16:10.541 --> 02:16:16.291 So we're now back at the point in the story where we have a student class, 1550 02:16:16.291 --> 02:16:18.041 with only two methods-- 1551 02:16:18.041 --> 02:16:19.416 init and str. 1552 02:16:19.416 --> 02:16:21.375 The first of those takes, of course, self 1553 02:16:21.375 --> 02:16:25.125 as the first argument as it always will, plus two more now-- name and house, 1554 02:16:25.125 --> 02:16:26.208 no more patronus. 1555 02:16:26.208 --> 02:16:28.041 We're validating name up here. 1556 02:16:28.041 --> 02:16:29.833 We're validating house down here. 1557 02:16:29.833 --> 02:16:32.375 And then we're assigning name and house, respectively, 1558 02:16:32.375 --> 02:16:35.625 to two instance variables called name and house also. 1559 02:16:35.625 --> 02:16:38.875 But we use self to get access to the current object, 1560 02:16:38.875 --> 02:16:40.750 to store those values therein. 1561 02:16:40.750 --> 02:16:44.583 We then still have our str method here, which takes one argument-- 1562 02:16:44.583 --> 02:16:46.208 by default, self, and that's it. 1563 02:16:46.208 --> 02:16:49.750 And that function is going to be called automatically any time 1564 02:16:49.750 --> 02:16:52.625 you want to convert a student object to a string, 1565 02:16:52.625 --> 02:16:55.125 just like print might want to do here. 1566 02:16:55.125 --> 02:16:57.791 So let me go ahead and just make sure I haven't broken anything. 1567 02:16:57.791 --> 02:16:59.750 Let me run Python of student.py. 1568 02:16:59.750 --> 02:17:00.916 I'll type in Harry. 1569 02:17:00.916 --> 02:17:03.041 I'll type in Gryffindor, Enter. 1570 02:17:03.041 --> 02:17:04.416 OK, we're back in business. 1571 02:17:04.416 --> 02:17:08.166 Gone are the charms and patronus, but at least I'm back to a situation 1572 02:17:08.166 --> 02:17:10.250 where I have names and houses. 1573 02:17:10.250 --> 02:17:14.416 But it turns out, at the moment, our use of classes 1574 02:17:14.416 --> 02:17:20.083 is not very robust, even though we have this mechanism, very cleverly, 1575 02:17:20.083 --> 02:17:23.041 if I may, in our init method of making sure 1576 02:17:23.041 --> 02:17:27.125 that we're validating name and house, making sure that name is not blank, 1577 02:17:27.125 --> 02:17:32.625 and making sure that house is a valid house among those four Hogwarts houses. 1578 02:17:32.625 --> 02:17:35.208 It turns out that classes will still let me 1579 02:17:35.208 --> 02:17:38.541 get at those attributes, those so-called instance variables, 1580 02:17:38.541 --> 02:17:40.750 using dot notation anyway. 1581 02:17:40.750 --> 02:17:44.500 Let me scroll down then and try to do this a little adversarially. 1582 02:17:44.500 --> 02:17:48.500 Suppose that online 16 I go ahead and call get_student, 1583 02:17:48.500 --> 02:17:52.541 which exists as before, and then I store the return value in a student 1584 02:17:52.541 --> 02:17:54.791 variable-- again, on line 16. 1585 02:17:54.791 --> 02:18:00.291 That will ensure that get_student gets called, which calls input and input. 1586 02:18:00.291 --> 02:18:02.916 And then it calls the student constructor, 1587 02:18:02.916 --> 02:18:05.541 which invokes, automatically, this init method. 1588 02:18:05.541 --> 02:18:08.458 So by way of how we've laid out my code, we're 1589 02:18:08.458 --> 02:18:11.750 going to ensure that name is not blank and house is definitely 1590 02:18:11.750 --> 02:18:12.916 one of those four values. 1591 02:18:12.916 --> 02:18:16.541 My error correction-- or error checking is in place. 1592 02:18:16.541 --> 02:18:20.708 But if I'm a little adversarial, I can still circumvent it. 1593 02:18:20.708 --> 02:18:25.541 Suppose that-- fine, going to require me to type in Harry and Gryffindor? 1594 02:18:25.541 --> 02:18:28.083 I'm going to go ahead and type in student.house 1595 02:18:28.083 --> 02:18:31.541 equals, quote, unquote, "Number Four, Privet Drive," 1596 02:18:31.541 --> 02:18:33.333 and you're not going to be able to stop me. 1597 02:18:33.333 --> 02:18:34.083 Why? 1598 02:18:34.083 --> 02:18:36.708 Well, it turns out, with classes and objects 1599 02:18:36.708 --> 02:18:40.375 thereof, you and I can still access those instance variables 1600 02:18:40.375 --> 02:18:42.083 using this familiar dot notation. 1601 02:18:42.083 --> 02:18:45.125 That's how we began the story of classes-- just setting these attributes 1602 02:18:45.125 --> 02:18:45.875 ourselves. 1603 02:18:45.875 --> 02:18:48.541 But you can also read these attributes themselves 1604 02:18:48.541 --> 02:18:50.750 and change them later if you want. 1605 02:18:50.750 --> 02:18:55.833 And this will effectively circumvent the if condition and the other if condition 1606 02:18:55.833 --> 02:19:00.000 in our init method because that is only called when you first 1607 02:19:00.000 --> 02:19:02.125 create the student object. 1608 02:19:02.125 --> 02:19:04.541 There's nothing stopping me, at the moment, 1609 02:19:04.541 --> 02:19:08.291 from just changing the house or the name after. 1610 02:19:08.291 --> 02:19:12.541 So if I now clear my terminal window and run Python of student.py, 1611 02:19:12.541 --> 02:19:16.208 I'll still type in Harry and Gryffindor to meet my requirements 1612 02:19:16.208 --> 02:19:17.916 that the house be one of those four. 1613 02:19:17.916 --> 02:19:21.375 But when it's printed, notice, I've still overridden it. 1614 02:19:21.375 --> 02:19:26.083 So it seems that, while classes do allow us a little more control over the data 1615 02:19:26.083 --> 02:19:30.250 we're storing, it doesn't necessarily prevent the user-- 1616 02:19:30.250 --> 02:19:33.625 or rather the programmer-- be it myself or maybe a colleague, 1617 02:19:33.625 --> 02:19:35.791 from still messing things up. 1618 02:19:35.791 --> 02:19:39.083 So here, too, in the spirit of programming a little more 1619 02:19:39.083 --> 02:19:43.916 defensively, allow me to introduce another feature of Python as well-- 1620 02:19:43.916 --> 02:19:46.208 namely properties. 1621 02:19:46.208 --> 02:19:49.375 So a property is really just an attribute 1622 02:19:49.375 --> 02:19:52.750 that has even more defense mechanisms put into place, 1623 02:19:52.750 --> 02:19:58.208 a little more functionality implemented by you to prevent programmers, like me 1624 02:19:58.208 --> 02:20:01.083 and you, from messing things up like these attributes. 1625 02:20:01.083 --> 02:20:04.375 So again, a property is going to be an attribute that you and I just 1626 02:20:04.375 --> 02:20:06.083 have more control over. 1627 02:20:06.083 --> 02:20:06.750 How? 1628 02:20:06.750 --> 02:20:09.791 We just write a little more code, using some Python conventions. 1629 02:20:09.791 --> 02:20:14.333 And how we're going to do that is going to use, in just a moment, a feature-- 1630 02:20:14.333 --> 02:20:18.083 a keyword known as @property, which is technically a function. 1631 02:20:18.083 --> 02:20:19.916 Property is a function in Python. 1632 02:20:19.916 --> 02:20:24.791 But we're about to see some new @ syntax that allows you to decorate functions. 1633 02:20:24.791 --> 02:20:26.125 And this, too, is a term of art. 1634 02:20:26.125 --> 02:20:28.916 In the world of Python, you can have decorators, 1635 02:20:28.916 --> 02:20:33.166 which are functions that modify the behavior of other functions, 1636 02:20:33.166 --> 02:20:36.708 if you will, and we'll leave it at that without going too much into the weeds. 1637 02:20:36.708 --> 02:20:39.291 And we'll see, by, example how you can use these decorators, 1638 02:20:39.291 --> 02:20:41.541 specifically to define properties. 1639 02:20:41.541 --> 02:20:43.708 So let me go back to VS Code here. 1640 02:20:43.708 --> 02:20:46.791 And let me propose that I do this. 1641 02:20:46.791 --> 02:20:49.375 I'm going to go ahead and create-- 1642 02:20:49.375 --> 02:20:54.291 how about a property called house as follows. 1643 02:20:54.291 --> 02:21:00.833 Inside of my student class, I'm going to go ahead-- and below my init method 1644 02:21:00.833 --> 02:21:03.958 and below my str method, I'm going to go ahead and define 1645 02:21:03.958 --> 02:21:09.000 a function called house that takes, as it always must, one argument at least, 1646 02:21:09.000 --> 02:21:10.041 called self. 1647 02:21:10.041 --> 02:21:15.458 And what I'm going to do now is return self.house. 1648 02:21:15.458 --> 02:21:17.625 So I'm just going to define a method called 1649 02:21:17.625 --> 02:21:23.375 house, whose sole purpose in life is to return the value of house. 1650 02:21:23.375 --> 02:21:27.375 But I'm going to define one other method, curiously also called house, 1651 02:21:27.375 --> 02:21:31.375 but that's going to take into, as arguments, two values-- 1652 02:21:31.375 --> 02:21:35.625 self as always and also a value called house. 1653 02:21:35.625 --> 02:21:37.291 And I'm going to now do this. 1654 02:21:37.291 --> 02:21:40.375 1655 02:21:40.375 --> 02:21:43.958 I'm going to do self.house = house. 1656 02:21:43.958 --> 02:21:45.250 Now, what have I done? 1657 02:21:45.250 --> 02:21:47.625 Well, let me just temporarily add some comments here. 1658 02:21:47.625 --> 02:21:50.166 In a moment, we're going to start referring to this generally 1659 02:21:50.166 --> 02:21:51.083 as a getter. 1660 02:21:51.083 --> 02:21:54.083 And down here, I'm going to refer to this as a setter. 1661 02:21:54.083 --> 02:21:56.875 And this is terminology frequently see in the world of Java. 1662 02:21:56.875 --> 02:21:58.791 Some of you have programmed in Java before. 1663 02:21:58.791 --> 02:22:01.875 But as the names imply, a getter is a function 1664 02:22:01.875 --> 02:22:05.333 for a class that gets some attributes. 1665 02:22:05.333 --> 02:22:10.291 A setter is a function in some class that sets some value. 1666 02:22:10.291 --> 02:22:14.125 And now, even though we're not done, and there's a bit of a mistake in the code 1667 02:22:14.125 --> 02:22:17.916 I've already written, intuitively, what we're going to do is this. 1668 02:22:17.916 --> 02:22:21.000 We're trying to prevent programmers, myself included, 1669 02:22:21.000 --> 02:22:23.541 from circumventing my error checking that I 1670 02:22:23.541 --> 02:22:25.666 put into place for name and house. 1671 02:22:25.666 --> 02:22:26.958 How can I do that? 1672 02:22:26.958 --> 02:22:29.500 Well, we don't have that many building blocks in programming. 1673 02:22:29.500 --> 02:22:33.541 We have things like variables for data, and we have functions for actions. 1674 02:22:33.541 --> 02:22:35.000 Well, why don't we do this? 1675 02:22:35.000 --> 02:22:39.958 Why don't we somehow require that, in order to access an attribute, 1676 02:22:39.958 --> 02:22:41.375 you go through some function. 1677 02:22:41.375 --> 02:22:44.583 And let's require that, in order to set some attribute, 1678 02:22:44.583 --> 02:22:46.291 you go through some function. 1679 02:22:46.291 --> 02:22:51.958 And conventionally, those functions are called a getter function and a setter 1680 02:22:51.958 --> 02:22:52.708 function. 1681 02:22:52.708 --> 02:22:56.541 And why are we using functions or, in this case, methods inside of a class? 1682 02:22:56.541 --> 02:23:00.000 Well, once you have functions, those are just actions or verbs 1683 02:23:00.000 --> 02:23:01.583 that you and I can create ourselves. 1684 02:23:01.583 --> 02:23:05.208 We can put any error correction I want in these functions 1685 02:23:05.208 --> 02:23:08.416 because it's code that's going to get executed top to bottom. 1686 02:23:08.416 --> 02:23:15.541 So how can I now prevent the user from setting the house to an invalid value? 1687 02:23:15.541 --> 02:23:20.500 Let me borrow some logic from before rather than blindly do this-- just set 1688 02:23:20.500 --> 02:23:25.291 self.house equal to the house value that's passed in-- let's add our error 1689 02:23:25.291 --> 02:23:26.166 checking there. 1690 02:23:26.166 --> 02:23:34.791 So if house is not in the following list of Gryffindor or Hufflepuff 1691 02:23:34.791 --> 02:23:40.708 or Ravenclaw or Slytherin, just as before, 1692 02:23:40.708 --> 02:23:44.541 let's go ahead and raise a value error, just to signify that, uh-uh, something 1693 02:23:44.541 --> 02:23:45.291 has gone wrong. 1694 02:23:45.291 --> 02:23:46.333 I'll be more explicit. 1695 02:23:46.333 --> 02:23:50.541 I'll include a message like, "invalid house," quote, unquote. 1696 02:23:50.541 --> 02:23:56.166 Otherwise, I'm going to proceed on, now, line 21 to set self.house to house. 1697 02:23:56.166 --> 02:24:00.416 So I've just copied, if you will, or retyped my error checking inside 1698 02:24:00.416 --> 02:24:02.333 of this so-called setter function. 1699 02:24:02.333 --> 02:24:03.666 Now, why have I done that? 1700 02:24:03.666 --> 02:24:07.458 Well, to be clear, whenever the user or the programmer 1701 02:24:07.458 --> 02:24:12.375 writes code like this, student.house equals, what's 1702 02:24:12.375 --> 02:24:16.166 about to happen magically is Python will not just 1703 02:24:16.166 --> 02:24:19.916 let the programmer access student house directly-- that attribute, 1704 02:24:19.916 --> 02:24:21.750 that instance variable, a.k.a. 1705 02:24:21.750 --> 02:24:22.875 self house. 1706 02:24:22.875 --> 02:24:26.833 It's instead going to magically automatically call 1707 02:24:26.833 --> 02:24:28.791 this setter function for me. 1708 02:24:28.791 --> 02:24:30.708 How does Python know to do that? 1709 02:24:30.708 --> 02:24:35.666 Well, if it's see that, on the left-hand side, there is self.house, 1710 02:24:35.666 --> 02:24:42.125 where house is the name of the getter or setter, and then it sees an equal sign, 1711 02:24:42.125 --> 02:24:45.625 indicating assignment, that's just enough of a visual clue to say, 1712 02:24:45.625 --> 02:24:46.208 wait a minute. 1713 02:24:46.208 --> 02:24:49.250 I'm not going to let you access that attribute directly. 1714 02:24:49.250 --> 02:24:51.791 I'm going to use the setter instead. 1715 02:24:51.791 --> 02:24:52.291 Why? 1716 02:24:52.291 --> 02:24:54.291 Because the equal sign means I'm trying to set. 1717 02:24:54.291 --> 02:24:58.250 I'm trying to assign a value from right to left into that attribute. 1718 02:24:58.250 --> 02:25:00.500 So what Python's is going to do automatically 1719 02:25:00.500 --> 02:25:02.083 is call this function for me. 1720 02:25:02.083 --> 02:25:06.708 And that's amazing because now I can execute code-- an algorithm to check, 1721 02:25:06.708 --> 02:25:08.625 do I want to let the user-- 1722 02:25:08.625 --> 02:25:12.041 the programmer set that attribute to that value? 1723 02:25:12.041 --> 02:25:13.916 If not, I'm going to raise a value error, 1724 02:25:13.916 --> 02:25:15.833 and you're just not going to be able to do it. 1725 02:25:15.833 --> 02:25:17.500 If so fine. 1726 02:25:17.500 --> 02:25:19.416 I'll go ahead and set it for you. 1727 02:25:19.416 --> 02:25:21.958 But in order to do this, we need a little more syntax. 1728 02:25:21.958 --> 02:25:25.375 And I'm going to get rid of my comment, and I'm going to use that decorator. 1729 02:25:25.375 --> 02:25:29.583 I need to tell Python to treat this method as a getter. 1730 02:25:29.583 --> 02:25:32.083 And then the syntax for the setter is a little different. 1731 02:25:32.083 --> 02:25:34.500 You now say house.setter. 1732 02:25:34.500 --> 02:25:37.500 I wish one was getter and the other was setter. 1733 02:25:37.500 --> 02:25:39.458 That's not the way they designed it. 1734 02:25:39.458 --> 02:25:44.291 When you want to define a getter, you just say @property above the function. 1735 02:25:44.291 --> 02:25:48.833 And you name the function exactly like you would like the property 1736 02:25:48.833 --> 02:25:51.000 to be called-- quote, unquote, "house." 1737 02:25:51.000 --> 02:25:54.416 Once you do that, you can now use a new decorator 1738 02:25:54.416 --> 02:25:57.625 that's automatically created for you called @house, 1739 02:25:57.625 --> 02:25:58.750 because I called it house. 1740 02:25:58.750 --> 02:26:01.416 And then you literally say, @house.setter. 1741 02:26:01.416 --> 02:26:05.333 And this whole line, on line 17, is a clue to Python 1742 02:26:05.333 --> 02:26:08.750 that here comes a function, whose name is identical-- 1743 02:26:08.750 --> 02:26:12.541 but notice that it takes two arguments-- both self, so you have access 1744 02:26:12.541 --> 02:26:15.208 to the contents of the object, and house, which is just 1745 02:26:15.208 --> 02:26:20.041 going to be a str that comes from the programmer from the human input return 1746 02:26:20.041 --> 02:26:23.958 value so that you can set that value as well. 1747 02:26:23.958 --> 02:26:27.375 But there's one fix I need to make now, here. 1748 02:26:27.375 --> 02:26:29.625 Everything else, I think, is still good. 1749 02:26:29.625 --> 02:26:31.250 However watch this. 1750 02:26:31.250 --> 02:26:34.500 I no longer need this error check here. 1751 02:26:34.500 --> 02:26:35.333 Why? 1752 02:26:35.333 --> 02:26:39.375 Because, if I scroll back down to my code here, 1753 02:26:39.375 --> 02:26:46.250 I claimed a moment ago that code like this, with student.house equals, 1754 02:26:46.250 --> 02:26:49.666 is going to automatically get Python to call my setter for me. 1755 02:26:49.666 --> 02:26:50.666 Guess what? 1756 02:26:50.666 --> 02:26:55.875 Even up here, in my init method, calling self.house equals 1757 02:26:55.875 --> 02:27:00.208 is also going to call my setter method, which is amazing 1758 02:27:00.208 --> 02:27:06.083 because now I can keep all of my error checking in one place in the setter, 1759 02:27:06.083 --> 02:27:10.833 and it will now get called either when I create the object for the first time, 1760 02:27:10.833 --> 02:27:14.541 because of init, or even if the programmer 1761 02:27:14.541 --> 02:27:19.375 tries to circumvent that init method and change the value of this attribute, 1762 02:27:19.375 --> 02:27:21.041 my setter will also get called. 1763 02:27:21.041 --> 02:27:26.000 My setter will get called any time I access .house. 1764 02:27:26.000 --> 02:27:28.458 But there's one fix I need to make. 1765 02:27:28.458 --> 02:27:32.958 Unfortunately, I have collided names. 1766 02:27:32.958 --> 02:27:38.458 Right now, if we go up here, on line 5, this is an instance variable. 1767 02:27:38.458 --> 02:27:42.333 It's a string inside of my self, inside of the current student object, 1768 02:27:42.333 --> 02:27:43.083 called name. 1769 02:27:43.083 --> 02:27:46.958 And this is another instance variable called house. 1770 02:27:46.958 --> 02:27:51.291 Unfortunately, if I have an instance variable called name and house, 1771 02:27:51.291 --> 02:27:54.750 I cannot also have functions called house. 1772 02:27:54.750 --> 02:27:55.916 They're going to collide. 1773 02:27:55.916 --> 02:27:57.041 You've got to decide. 1774 02:27:57.041 --> 02:27:59.250 Do you want the variable to be called house? 1775 02:27:59.250 --> 02:28:01.708 Or do you want the function to be called house? 1776 02:28:01.708 --> 02:28:04.208 Unfortunately, you can't have both because now Python 1777 02:28:04.208 --> 02:28:06.041 is going to confuse one for the other. 1778 02:28:06.041 --> 02:28:09.291 So the conventional fix for this is to do this-- 1779 02:28:09.291 --> 02:28:15.250 to have the setter not store the value that's passed in self.house, 1780 02:28:15.250 --> 02:28:19.125 but to use an almost identical name, but to use a little indicator that 1781 02:28:19.125 --> 02:28:21.208 means you know doing this correctly. 1782 02:28:21.208 --> 02:28:23.666 You typically, by convention, put an underscore 1783 02:28:23.666 --> 02:28:27.041 in front of the instance variable's name. 1784 02:28:27.041 --> 02:28:31.208 And when you return it up here, you similarly put an underscore. 1785 02:28:31.208 --> 02:28:36.541 So now, technically, my instance variable is called _house, 1786 02:28:36.541 --> 02:28:41.791 but my property, which is a fancier attribute, if you will, 1787 02:28:41.791 --> 02:28:44.916 is called house alone. 1788 02:28:44.916 --> 02:28:48.666 Huge amount of syntax, I know, but it's a very powerful feature. 1789 02:28:48.666 --> 02:28:52.708 And again, this is why you can graduate from dictionaries alone and have 1790 02:28:52.708 --> 02:28:55.500 so much more functionality at your disposal. 1791 02:28:55.500 --> 02:28:58.916 Let me go ahead and clear my terminal window and run Python of student.py, 1792 02:28:58.916 --> 02:29:00.458 Enter, name. 1793 02:29:00.458 --> 02:29:02.708 All right, let's go ahead and type in Harry. 1794 02:29:02.708 --> 02:29:04.625 Let's go ahead and type in Gryffindor. 1795 02:29:04.625 --> 02:29:06.416 Crossing my fingers as always. 1796 02:29:06.416 --> 02:29:09.083 And now, look, "Invalid house." 1797 02:29:09.083 --> 02:29:10.250 This is a good thing. 1798 02:29:10.250 --> 02:29:10.750 Why? 1799 02:29:10.750 --> 02:29:13.958 Because, notice, in my main function, I'm 1800 02:29:13.958 --> 02:29:19.125 still trying, maliciously, if you will, to change Harry's house 1801 02:29:19.125 --> 02:29:21.000 to not be one of the four valid ones. 1802 02:29:21.000 --> 02:29:24.833 I'm trying to change it to his childhood home of Number Four, Privet Drive. 1803 02:29:24.833 --> 02:29:29.208 But because Python knows that, wait a minute, you're trying to assign-- 1804 02:29:29.208 --> 02:29:30.708 that is, set a value-- 1805 02:29:30.708 --> 02:29:34.208 and that value, a.k.a. house, is now defined 1806 02:29:34.208 --> 02:29:38.333 as a property you're going to have to go through the setter function instead 1807 02:29:38.333 --> 02:29:40.958 to even let you change that value. 1808 02:29:40.958 --> 02:29:44.208 And because I have this raise ValueError. 1809 02:29:44.208 --> 02:29:46.916 If the house is not as intended, you're not 1810 02:29:46.916 --> 02:29:49.458 going to be allowed to change it to an invalid value. 1811 02:29:49.458 --> 02:29:52.458 So I'm protecting the data on the way in, through the init method, 1812 02:29:52.458 --> 02:29:56.708 and I'm even defending the data if you try to override it there. 1813 02:29:56.708 --> 02:29:59.208 So I think the only solution for me, the programmer, 1814 02:29:59.208 --> 02:30:01.250 is, don't try to break my own code. 1815 02:30:01.250 --> 02:30:04.083 Let me remove that line because it's just not going to work. 1816 02:30:04.083 --> 02:30:07.250 Let me run Python of student.py and, again, type in Harry; 1817 02:30:07.250 --> 02:30:10.916 type in Gryffindor, Enter, and Harry's indeed from Gryffindor. 1818 02:30:10.916 --> 02:30:15.708 If I did something incorrect, like Harry from Number Four, 1819 02:30:15.708 --> 02:30:20.541 Privet Drive, Enter, we're again going to see the value error 1820 02:30:20.541 --> 02:30:25.083 because my code just doesn't let that value in via manual input now 1821 02:30:25.083 --> 02:30:28.541 or via that adversarial change. 1822 02:30:28.541 --> 02:30:29.958 All right, that was a lot. 1823 02:30:29.958 --> 02:30:35.208 But any question on properties? 1824 02:30:35.208 --> 02:30:37.125 AUDIENCE: Why we are using getter then setter? 1825 02:30:37.125 --> 02:30:41.541 It's just for the purpose so that we can find 1826 02:30:41.541 --> 02:30:43.791 that method, that function in our code. 1827 02:30:43.791 --> 02:30:46.833 DAVID J. MALAN: The reason that I'm going through the trouble of defining 1828 02:30:46.833 --> 02:30:52.375 this getter or setter is because I want to make sure that programmers cannot do 1829 02:30:52.375 --> 02:30:53.250 things like this. 1830 02:30:53.250 --> 02:30:55.500 If I'm going through the trouble of validating 1831 02:30:55.500 --> 02:30:58.708 the attributes for these student objects, 1832 02:30:58.708 --> 02:31:02.041 I don't want you to be able to go in there and just change them at will. 1833 02:31:02.041 --> 02:31:04.625 I want to have some control over that object 1834 02:31:04.625 --> 02:31:07.708 so that you can just trust that it's going to be correct as designed. 1835 02:31:07.708 --> 02:31:11.041 So using a getter and setter really just enables 1836 02:31:11.041 --> 02:31:15.416 Python to automatically detect when you're trying to manually set a value. 1837 02:31:15.416 --> 02:31:18.333 The equal sign and the dot, as I've highlighted here, 1838 02:31:18.333 --> 02:31:20.666 is enough of a clue to Python to realize, wait a minute, 1839 02:31:20.666 --> 02:31:21.916 you're trying to set a value. 1840 02:31:21.916 --> 02:31:24.750 Let me see if this class has a setter defined. 1841 02:31:24.750 --> 02:31:26.875 And if so, I'm going to call that, and I'm not just 1842 02:31:26.875 --> 02:31:30.125 going to blindly assign the value from right to left. 1843 02:31:30.125 --> 02:31:32.541 So it's just giving me more control. 1844 02:31:32.541 --> 02:31:35.041 Other questions on properties. 1845 02:31:35.041 --> 02:31:39.458 AUDIENCE: When we use getters, we just have just one argument. 1846 02:31:39.458 --> 02:31:43.291 And if we use setters, it's always going to be two arguments? 1847 02:31:43.291 --> 02:31:44.291 Is that normal? 1848 02:31:44.291 --> 02:31:45.291 DAVID J. MALAN: Correct. 1849 02:31:45.291 --> 02:31:47.833 It's always going to be one argument-- self 1850 02:31:47.833 --> 02:31:52.625 for the getter, two arguments for the setter-- self and something else. 1851 02:31:52.625 --> 02:31:55.375 And the intuition for that is, if you're getting a value, 1852 02:31:55.375 --> 02:31:58.875 you don't need to pass anything else in because you already know the object. 1853 02:31:58.875 --> 02:32:00.291 It's called student in this case. 1854 02:32:00.291 --> 02:32:02.791 So you're just going to get the value of that property. 1855 02:32:02.791 --> 02:32:05.458 But if you want to set the property to something else, 1856 02:32:05.458 --> 02:32:07.125 you've got to pass in that argument. 1857 02:32:07.125 --> 02:32:09.625 You've got to pass in the value to which you want to set it. 1858 02:32:09.625 --> 02:32:11.291 So it's always 0 or 1. 1859 02:32:11.291 --> 02:32:17.541 However, you see it as 1 or 2 because, again, any function inside of a class, 1860 02:32:17.541 --> 02:32:18.041 a.k.a. 1861 02:32:18.041 --> 02:32:22.291 a method, is going to be automatically passed self so that you have access 1862 02:32:22.291 --> 02:32:25.208 to that current object in memory. 1863 02:32:25.208 --> 02:32:27.708 How about one other question on properties? 1864 02:32:27.708 --> 02:32:31.791 AUDIENCE: Why didn't we use the same underscore house init method? 1865 02:32:31.791 --> 02:32:33.125 DAVID J. MALAN: A good question. 1866 02:32:33.125 --> 02:32:36.750 So even though I'm using the underscore house here, in my setter, 1867 02:32:36.750 --> 02:32:39.125 and the underscore house here, in my getter, 1868 02:32:39.125 --> 02:32:41.958 I deliberately did not use it up here. 1869 02:32:41.958 --> 02:32:47.333 The reason for that is that, by using self.house and this equal sign, 1870 02:32:47.333 --> 02:32:50.375 that's the same pattern that I want Python to recognize. 1871 02:32:50.375 --> 02:32:53.208 I want Python to automatically call the setter, 1872 02:32:53.208 --> 02:32:56.791 even when I'm passing in the house via the init method. 1873 02:32:56.791 --> 02:33:01.458 If I were to change this to do this, that would circumvent the setter, 1874 02:33:01.458 --> 02:33:05.041 and now there's no error checking in init whatsoever. 1875 02:33:05.041 --> 02:33:06.333 So it's such a fine line. 1876 02:33:06.333 --> 02:33:10.041 The only thing standing between us and error checking or no error checking 1877 02:33:10.041 --> 02:33:12.750 is the presence or absence of this underscore. 1878 02:33:12.750 --> 02:33:14.625 But that's typically the convention. 1879 02:33:14.625 --> 02:33:17.375 By not using the underscore there, make sure 1880 02:33:17.375 --> 02:33:19.708 that even that assignment goes through the setter 1881 02:33:19.708 --> 02:33:22.416 so that, honestly, I, don't have to copy paste the same error 1882 02:33:22.416 --> 02:33:23.583 checking in two places. 1883 02:33:23.583 --> 02:33:25.375 I can put it just in the setter. 1884 02:33:25.375 --> 02:33:28.791 So it's a better design, and that's why I manually retyped it at first, 1885 02:33:28.791 --> 02:33:31.166 but then I deleted it from init. 1886 02:33:31.166 --> 02:33:34.625 Well, allow me to propose that we make one other change to this file. 1887 02:33:34.625 --> 02:33:38.708 Might as well go ahead and define a property for name as well. 1888 02:33:38.708 --> 02:33:41.458 And let me go ahead and do this-- maybe above the house property 1889 02:33:41.458 --> 02:33:45.041 just to keep things in the same order as I defined them earlier. 1890 02:33:45.041 --> 02:33:47.375 Let me give myself another property. 1891 02:33:47.375 --> 02:33:49.125 This one is going to be called name. 1892 02:33:49.125 --> 02:33:51.583 It's going to take one argument called self, as always. 1893 02:33:51.583 --> 02:33:55.625 And this one, very similarly, is just going to return self._name. 1894 02:33:55.625 --> 02:33:57.333 So I'm going to anticipate that I'm going 1895 02:33:57.333 --> 02:34:01.166 to have to rename name also so that I don't have that same collision as 1896 02:34:01.166 --> 02:34:01.875 before. 1897 02:34:01.875 --> 02:34:05.541 But now let me go ahead and define another setter-- 1898 02:34:05.541 --> 02:34:06.750 this one for name. 1899 02:34:06.750 --> 02:34:09.416 So the convention is @name.setter. 1900 02:34:09.416 --> 02:34:10.375 Why name? 1901 02:34:10.375 --> 02:34:13.916 Because the property I just created is called name. 1902 02:34:13.916 --> 02:34:18.833 So the getter and setter work in conjunction in this way, if you will. 1903 02:34:18.833 --> 02:34:22.291 Let me go down under that name setter and define another function, 1904 02:34:22.291 --> 02:34:23.375 also called name. 1905 02:34:23.375 --> 02:34:25.791 But the key thing here is that it's not identical. 1906 02:34:25.791 --> 02:34:30.000 It's not the exact same function name and the exact same number of arguments. 1907 02:34:30.000 --> 02:34:32.500 The setter, again, takes a second argument. 1908 02:34:32.500 --> 02:34:34.250 And I can call it anything I want, but I'm 1909 02:34:34.250 --> 02:34:36.750 going to call it name because that's what's being passed in. 1910 02:34:36.750 --> 02:34:39.041 And I'm going to put my error checking here. 1911 02:34:39.041 --> 02:34:43.541 If not name, just like we used to do, let's go ahead and raise a value error, 1912 02:34:43.541 --> 02:34:48.750 and let's put an explanatory message like "Missing name," quote, unquote. 1913 02:34:48.750 --> 02:34:55.250 Otherwise, let's go ahead and update self._name to equal name. 1914 02:34:55.250 --> 02:34:58.500 And I don't have to change init except to get rid 1915 02:34:58.500 --> 02:35:02.333 of this duplicate error checking now because, again, 1916 02:35:02.333 --> 02:35:07.166 if I use self.name equals here and self.house equals here 1917 02:35:07.166 --> 02:35:09.916 with no underscore, both of those assignments 1918 02:35:09.916 --> 02:35:13.375 are going to go through my two setter functions now. 1919 02:35:13.375 --> 02:35:15.416 Before we run this, let me go ahead and remove 1920 02:35:15.416 --> 02:35:18.791 this adversarial code, which we know won't work because we're catching it. 1921 02:35:18.791 --> 02:35:22.375 Let me go back down to my terminal window and run Python of student.py, 1922 02:35:22.375 --> 02:35:22.916 Enter. 1923 02:35:22.916 --> 02:35:23.958 Let's type in Harry. 1924 02:35:23.958 --> 02:35:25.125 Let's type in Gryffindor. 1925 02:35:25.125 --> 02:35:26.708 And that seems to work. 1926 02:35:26.708 --> 02:35:29.458 Let's try though, again, to run Python of student.py 1927 02:35:29.458 --> 02:35:32.708 with Harry from Number Four, Privet Drive. 1928 02:35:32.708 --> 02:35:34.416 This will not work. 1929 02:35:34.416 --> 02:35:37.291 A value error with invalid house, because that's not 1930 02:35:37.291 --> 02:35:38.791 one of the four Hogwarts houses. 1931 02:35:38.791 --> 02:35:41.041 And now, for good measure, let's run it one more time. 1932 02:35:41.041 --> 02:35:42.583 And let's not even give it a name. 1933 02:35:42.583 --> 02:35:44.375 Let's just hit Enter when prompted. 1934 02:35:44.375 --> 02:35:45.875 I can type anything for the house. 1935 02:35:45.875 --> 02:35:47.958 I'll go ahead and still give it Gryffindor, Enter. 1936 02:35:47.958 --> 02:35:52.958 And now we get another value error, but this one is for missing name. 1937 02:35:52.958 --> 02:35:56.500 So we seem, now, to have all the more of a defense mechanism in place 1938 02:35:56.500 --> 02:35:58.833 to ensure that name is as we expect. 1939 02:35:58.833 --> 02:36:01.000 It's got to have some value that's not blank. 1940 02:36:01.000 --> 02:36:02.500 And house is as we expect. 1941 02:36:02.500 --> 02:36:05.000 It's got to have one of those four values. 1942 02:36:05.000 --> 02:36:08.000 But at the risk of bursting everyone's bubble 1943 02:36:08.000 --> 02:36:11.958 and making you wonder, why did we just go through all of that, 1944 02:36:11.958 --> 02:36:18.875 unfortunately Python really focuses on conventions, not hard constraints. 1945 02:36:18.875 --> 02:36:20.291 And by that, I mean this. 1946 02:36:20.291 --> 02:36:25.208 If I go back into my main function after I've gotten a student on line 30 1947 02:36:25.208 --> 02:36:30.375 and I try to adversarially do something like this-- student.house equals 1948 02:36:30.375 --> 02:36:33.625 "Number Four, Privet Drive," we know this 1949 02:36:33.625 --> 02:36:37.416 won't work because my setter for house is going to catch this. 1950 02:36:37.416 --> 02:36:38.333 Watch again. 1951 02:36:38.333 --> 02:36:40.833 Python of student.py. 1952 02:36:40.833 --> 02:36:42.041 Let's type in Harry. 1953 02:36:42.041 --> 02:36:44.541 Let's type in Gryffindor, which will at least pass 1954 02:36:44.541 --> 02:36:46.958 our check that's induced by init. 1955 02:36:46.958 --> 02:36:52.041 But line 31 is going to trigger the same setter to be called, 1956 02:36:52.041 --> 02:36:55.583 and we're going to raise a value error saying "Invalid house." 1957 02:36:55.583 --> 02:36:58.250 Unfortunately, and if some of you are already 1958 02:36:58.250 --> 02:37:02.375 thinking a little adversarially, tragically, look what you can do. 1959 02:37:02.375 --> 02:37:05.500 You can change .house to be ._house. 1960 02:37:05.500 --> 02:37:06.041 Why? 1961 02:37:06.041 --> 02:37:10.416 Well, the instance variable is now called _house. 1962 02:37:10.416 --> 02:37:13.125 The property is called house, no underscore. 1963 02:37:13.125 --> 02:37:19.041 But the underlying attribute implemented as an instance variable is still called 1964 02:37:19.041 --> 02:37:19.791 _house. 1965 02:37:19.791 --> 02:37:23.541 And tragically, Python of student.py. 1966 02:37:23.541 --> 02:37:25.000 Let's type in Harry. 1967 02:37:25.000 --> 02:37:27.375 Let's type in Gryffindor, which is correct. 1968 02:37:27.375 --> 02:37:28.958 But watch what happens now. 1969 02:37:28.958 --> 02:37:30.333 Oh, my God. 1970 02:37:30.333 --> 02:37:32.000 We slip through. 1971 02:37:32.000 --> 02:37:35.708 So what was the point of all of this emphasis from me 1972 02:37:35.708 --> 02:37:39.541 on doing things the "right way," the Python quick way by having this getter 1973 02:37:39.541 --> 02:37:40.291 and setter? 1974 02:37:40.291 --> 02:37:43.208 Well, unlike languages like Java, that just 1975 02:37:43.208 --> 02:37:46.208 prevent you from doing things like this, Python 1976 02:37:46.208 --> 02:37:49.041 itself allows you to specify that certain instance variables can 1977 02:37:49.041 --> 02:37:52.666 be public and accessible to anyone's code, or protected, 1978 02:37:52.666 --> 02:37:56.791 or private, which means that no one else should be able to change these values. 1979 02:37:56.791 --> 02:38:00.125 In the world of Python, it's just the honor system. 1980 02:38:00.125 --> 02:38:02.416 It's not baked into the language itself that there's 1981 02:38:02.416 --> 02:38:05.500 a notion of visibility, public or private or even somewhere in between 1982 02:38:05.500 --> 02:38:06.250 protected. 1983 02:38:06.250 --> 02:38:08.208 Instead, you're on the honor system. 1984 02:38:08.208 --> 02:38:11.666 And the convention generally is, if an instance 1985 02:38:11.666 --> 02:38:15.750 variable starts with an underscore, please don't touch it. 1986 02:38:15.750 --> 02:38:16.708 Just don't. 1987 02:38:16.708 --> 02:38:19.750 That's on you if you touch that variable and break things. 1988 02:38:19.750 --> 02:38:22.000 The underscore is meant to signify a convention 1989 02:38:22.000 --> 02:38:24.583 that this is meant to be "private," but it really just 1990 02:38:24.583 --> 02:38:25.875 means, please don't touch this. 1991 02:38:25.875 --> 02:38:27.958 Sometimes, if there's two underscores, which you can use, 1992 02:38:27.958 --> 02:38:30.541 too, that's an even greater effort by programmers to say, 1993 02:38:30.541 --> 02:38:32.000 really don't touch this. 1994 02:38:32.000 --> 02:38:35.166 But technically speaking, there's nothing stopping you or me 1995 02:38:35.166 --> 02:38:38.875 from circumventing all of these mechanisms, these properties, 1996 02:38:38.875 --> 02:38:40.125 these getters and setters. 1997 02:38:40.125 --> 02:38:42.250 We're ultimately just on the honor system 1998 02:38:42.250 --> 02:38:46.291 not to do so when we see instance variables prefixed with one, 1999 02:38:46.291 --> 02:38:48.041 or perhaps even two underscores. 2000 02:38:48.041 --> 02:38:50.583 All right, so this is a lot all at once-- 2001 02:38:50.583 --> 02:38:52.875 this Introduction to object-oriented programming. 2002 02:38:52.875 --> 02:38:56.166 But it might come as quite a surprise that, even though we 2003 02:38:56.166 --> 02:39:00.458 might have identified OOP by name in weeks past, 2004 02:39:00.458 --> 02:39:05.458 we've all been using classes and objects for weeks now in this class. 2005 02:39:05.458 --> 02:39:08.791 In fact, if you think back on one of the very first things we did in this class, 2006 02:39:08.791 --> 02:39:12.083 we used integers and just got integers from the user. 2007 02:39:12.083 --> 02:39:13.791 But if you haven't already-- 2008 02:39:13.791 --> 02:39:17.500 if you go and dig into the documentation for integers, 2009 02:39:17.500 --> 02:39:19.916 which, again, lives at this URL here, you 2010 02:39:19.916 --> 02:39:26.166 would actually find that int itself is and has been for weeks a class. 2011 02:39:26.166 --> 02:39:30.541 And in fact, this is the signature of the constructor call for an int, 2012 02:39:30.541 --> 02:39:35.125 whereby you pass in x, like a number, quote, unquote, "50" or, quote, 2013 02:39:35.125 --> 02:39:38.458 unquote, something else-- you pass in optionally the base-- 2014 02:39:38.458 --> 02:39:41.750 10 for decimal, 2 for binary or anything else. 2015 02:39:41.750 --> 02:39:46.208 And that int function will actually return to you, all this time, 2016 02:39:46.208 --> 02:39:49.625 an object of type int. 2017 02:39:49.625 --> 02:39:52.083 That is to say int is a class. 2018 02:39:52.083 --> 02:39:55.250 It is a template, a blueprint for creating integers in memory. 2019 02:39:55.250 --> 02:39:59.125 And any time you and I have converted a string, for, instance to an int, 2020 02:39:59.125 --> 02:40:03.958 you and I have been creating an object of type int that was calling, 2021 02:40:03.958 --> 02:40:07.875 apparently, the underscore underscore, init, underscore underscore method, 2022 02:40:07.875 --> 02:40:09.666 that someone else-- the authors of Python-- 2023 02:40:09.666 --> 02:40:12.500 wrote to give us back that proper integer. 2024 02:40:12.500 --> 02:40:16.541 Besides that, if you can believe it, strs, strings in Python 2025 02:40:16.541 --> 02:40:20.000 have been classes since the first week of this class as well. 2026 02:40:20.000 --> 02:40:22.000 If you look up the documentation for a str, 2027 02:40:22.000 --> 02:40:26.541 which lives at a similar URL there, you will find that, when you instantiate-- 2028 02:40:26.541 --> 02:40:28.083 that is, create a str-- 2029 02:40:28.083 --> 02:40:32.166 it takes, optionally, a parameter called object here, 2030 02:40:32.166 --> 02:40:34.666 the default value of which is just, quote, unquote, 2031 02:40:34.666 --> 02:40:38.000 which allows you to create, in effect, an empty string, a blank string, 2032 02:40:38.000 --> 02:40:38.500 if you will. 2033 02:40:38.500 --> 02:40:42.541 But any time you and I have created strs or even used explicitly 2034 02:40:42.541 --> 02:40:47.333 the str function, you are getting back an object of type str. 2035 02:40:47.333 --> 02:40:52.375 Any time you and I have forced a string to lowercase per the documentation, 2036 02:40:52.375 --> 02:40:58.041 using syntax like this, you and I have been taking an object of type str 2037 02:40:58.041 --> 02:41:03.125 and forcing it all to lowercase by calling a method called lower, 2038 02:41:03.125 --> 02:41:07.958 a method that the authors of Python built into the str class, 2039 02:41:07.958 --> 02:41:11.041 but it's been there from the get-go, so this notion of methods is not 2040 02:41:11.041 --> 02:41:11.833 even new today. 2041 02:41:11.833 --> 02:41:13.750 You wouldn't have been doing it for this long. 2042 02:41:13.750 --> 02:41:17.416 If you've ever called strip to remove the leading and the trailing whitespace 2043 02:41:17.416 --> 02:41:22.166 from a string in Python, you are calling another method that came with Python-- 2044 02:41:22.166 --> 02:41:23.750 written by the authors of Python. 2045 02:41:23.750 --> 02:41:26.000 And even though we didn't call it a class at the time, 2046 02:41:26.000 --> 02:41:29.708 a str, all this time, has been a class. 2047 02:41:29.708 --> 02:41:33.541 And instances of strings are, themselves, objects. 2048 02:41:33.541 --> 02:41:36.291 And those objects come therefore with these functions 2049 02:41:36.291 --> 02:41:39.458 built in-- a.k.a. methods that allow us to do things like force 2050 02:41:39.458 --> 02:41:42.791 to lowercase and strip whitespace from the beginning and end. 2051 02:41:42.791 --> 02:41:43.958 Let's do another. 2052 02:41:43.958 --> 02:41:49.541 list-- any time you've created a list, either syntactically with square 2053 02:41:49.541 --> 02:41:53.541 brackets or literally with L-I-S-T, open parentheses, closed parentheses, 2054 02:41:53.541 --> 02:41:56.375 which is also possible, you have been using a class. 2055 02:41:56.375 --> 02:41:59.833 If you go to the documentation for list, at this similar URL 2056 02:41:59.833 --> 02:42:04.083 here, or more specifically, the tutorial on lists here in Python, 2057 02:42:04.083 --> 02:42:07.208 you will see that a list is and has been, 2058 02:42:07.208 --> 02:42:10.958 since the early weeks of this class, a class itself. 2059 02:42:10.958 --> 02:42:16.166 And that list class takes, as part of its initialization, 2060 02:42:16.166 --> 02:42:21.000 an optional iterable, something that can be iterated over-- like 1, 2, 3, 2061 02:42:21.000 --> 02:42:24.125 or some list of values, and you can then get back 2062 02:42:24.125 --> 02:42:27.416 a list containing those same iterable values. 2063 02:42:27.416 --> 02:42:30.666 If you've ever appended something to a list in this class, 2064 02:42:30.666 --> 02:42:32.583 as I have myself in the past, you've been 2065 02:42:32.583 --> 02:42:37.416 using a method called append that comes with the list class that, per the x 2066 02:42:37.416 --> 02:42:40.541 here, takes an argument that allows you to append something 2067 02:42:40.541 --> 02:42:42.833 to the current list, a.k.a. 2068 02:42:42.833 --> 02:42:45.125 Self in the context of that method. 2069 02:42:45.125 --> 02:42:46.416 We can do this all day long. 2070 02:42:46.416 --> 02:42:49.625 If you've used a dictionary or a dict in Python-- 2071 02:42:49.625 --> 02:42:53.125 I've actually, all this time, been calling them dict objects, 2072 02:42:53.125 --> 02:42:54.500 and that's for a reason. 2073 02:42:54.500 --> 02:42:57.250 dict itself is a class in Python, if you pull up 2074 02:42:57.250 --> 02:42:59.166 its official documentation here. 2075 02:42:59.166 --> 02:43:03.375 And you'll see that it is defined, indeed, as itself a class. 2076 02:43:03.375 --> 02:43:05.333 And that class comes with methods as well. 2077 02:43:05.333 --> 02:43:07.291 And so any time we've manipulated dictionaries, 2078 02:43:07.291 --> 02:43:11.916 we've been underneath the hood, using all of those same methods. 2079 02:43:11.916 --> 02:43:14.208 And in fact, we can see this if we're really curious. 2080 02:43:14.208 --> 02:43:17.375 Let me go back over here to VS Code. 2081 02:43:17.375 --> 02:43:20.250 And let me go ahead and create a new file that, very simply, does 2082 02:43:20.250 --> 02:43:22.500 something play around with data types. 2083 02:43:22.500 --> 02:43:24.833 And let me go ahead and create a new file, for instance, 2084 02:43:24.833 --> 02:43:30.666 called, say, type.py, just so that I can poke around inside of some values. 2085 02:43:30.666 --> 02:43:34.333 And in type.py, I'm just going to go ahead and do this. 2086 02:43:34.333 --> 02:43:38.375 I'm going to print out whatever the type is of, say, the number 50. 2087 02:43:38.375 --> 02:43:41.500 And This is a function you've not necessarily seen me use already, 2088 02:43:41.500 --> 02:43:44.500 and it's not one you would frequently use in your own code. 2089 02:43:44.500 --> 02:43:46.958 There are other ways to detect, if you need to, 2090 02:43:46.958 --> 02:43:48.625 what the type is of a variable. 2091 02:43:48.625 --> 02:43:53.791 But in this case, type of 50 is just going to tell me and then print out 2092 02:43:53.791 --> 02:43:55.833 what the data type is of that value. 2093 02:43:55.833 --> 02:43:58.416 Now, hopefully, all of us could guess that 50 is indeed 2094 02:43:58.416 --> 02:43:59.458 going to be an integer-- 2095 02:43:59.458 --> 02:44:01.666 that is, an int, but we can see it in this way. 2096 02:44:01.666 --> 02:44:04.541 And this, too, is what's powerful about knowing a bit of programming. 2097 02:44:04.541 --> 02:44:06.416 If you want to know the answer to a question, 2098 02:44:06.416 --> 02:44:08.208 just try it out, like I am here. 2099 02:44:08.208 --> 02:44:11.125 So let me go ahead and run Python of type.py, Enter. 2100 02:44:11.125 --> 02:44:12.208 And there it is. 2101 02:44:12.208 --> 02:44:17.458 When you print out the type of the number 50, you'll see on the screen, 2102 02:44:17.458 --> 02:44:20.625 in this cryptic syntax, class 'int.' 2103 02:44:20.625 --> 02:44:23.541 This is not something that you probably want to show to the user. 2104 02:44:23.541 --> 02:44:26.625 But if you yourself just want to poke around and see what's going on 2105 02:44:26.625 --> 02:44:29.166 or maybe use that information somehow, it's 2106 02:44:29.166 --> 02:44:32.833 certainly at your disposal to use this type function for that. 2107 02:44:32.833 --> 02:44:34.583 Let's change it around a little bit. 2108 02:44:34.583 --> 02:44:38.375 Instead of passing as the argument to type 50, as an int, 2109 02:44:38.375 --> 02:44:41.291 let's type something also familiar, like "hello, world," 2110 02:44:41.291 --> 02:44:43.166 in double or single quotes. 2111 02:44:43.166 --> 02:44:45.541 Let me go back to my terminal window, clear the screen, 2112 02:44:45.541 --> 02:44:47.208 and run Python of type.py again. 2113 02:44:47.208 --> 02:44:49.333 And now, voila, there it is. 2114 02:44:49.333 --> 02:44:52.750 All this time, a str is also a class. 2115 02:44:52.750 --> 02:44:54.333 We can do this a few more times, For. 2116 02:44:54.333 --> 02:44:54.833 Instance. 2117 02:44:54.833 --> 02:44:58.875 Let's go ahead and change "hello, world" to just an empty list-- 2118 02:44:58.875 --> 02:45:01.291 open square bracket, closed square bracket. 2119 02:45:01.291 --> 02:45:03.375 And this is starting to look a little cryptic, 2120 02:45:03.375 --> 02:45:04.791 but, again, notice what I'm doing. 2121 02:45:04.791 --> 02:45:06.708 In square brackets is an empty list. 2122 02:45:06.708 --> 02:45:08.083 We've done that before. 2123 02:45:08.083 --> 02:45:11.583 That is the sole argument to this new type function. 2124 02:45:11.583 --> 02:45:14.666 And that's just being passed to the print function 2125 02:45:14.666 --> 02:45:18.458 so that the return value of type is the argument to print. 2126 02:45:18.458 --> 02:45:22.541 So if I now run this code, Python of type.py, there it is. 2127 02:45:22.541 --> 02:45:24.458 A list is a class, too. 2128 02:45:24.458 --> 02:45:28.000 You might recall that I said that you can also create an empty list 2129 02:45:28.000 --> 02:45:31.541 by literally doing list (). 2130 02:45:31.541 --> 02:45:34.375 This is a bit of an inconsistency, as we can now 2131 02:45:34.375 --> 02:45:39.958 identify that int and str and now list-- they're technically all lowercase. 2132 02:45:39.958 --> 02:45:44.708 And I went to great lengths of creating my student class to have that capital 2133 02:45:44.708 --> 02:45:46.583 S. That's a convention. 2134 02:45:46.583 --> 02:45:51.250 Because int and stir and list and others come with Python, 2135 02:45:51.250 --> 02:45:55.416 they decided to make their built-in data types-- even though they're classes-- 2136 02:45:55.416 --> 02:45:56.375 all lowercase. 2137 02:45:56.375 --> 02:45:59.750 But the convention, the recommendation in the Python community when creating 2138 02:45:59.750 --> 02:46:03.541 your classes is to capitalize the first letter, as I did, 2139 02:46:03.541 --> 02:46:10.541 in something like Student, capital S. But list () is identical to really just 2140 02:46:10.541 --> 02:46:12.041 two empty square brackets. 2141 02:46:12.041 --> 02:46:16.416 If I clear my screen and run type.py again, you see the exact same thing. 2142 02:46:16.416 --> 02:46:17.750 The class is called list. 2143 02:46:17.750 --> 02:46:19.041 Let's do one more. 2144 02:46:19.041 --> 02:46:23.041 Let me change the list to be not square brackets but curly braces. 2145 02:46:23.041 --> 02:46:24.125 We've done this before. 2146 02:46:24.125 --> 02:46:27.916 Any time I've done two curly braces with nothing in between, this, of course, 2147 02:46:27.916 --> 02:46:31.458 is an empty dictionary, or a dict object in Python. 2148 02:46:31.458 --> 02:46:32.750 Well, we can see that now. 2149 02:46:32.750 --> 02:46:35.500 Let me clear my screen, run Python of type.py, Enter, 2150 02:46:35.500 --> 02:46:37.666 and there it is-- class 'dict." 2151 02:46:37.666 --> 02:46:39.458 It's been there this whole time. 2152 02:46:39.458 --> 02:46:42.333 We just didn't call it a class until today. 2153 02:46:42.333 --> 02:46:44.625 I can similarly do this one explicitly. 2154 02:46:44.625 --> 02:46:48.500 Instead of two curly braces, let's write out dict with two parentheses. 2155 02:46:48.500 --> 02:46:51.541 Now we have a lot of parentheses again, like with list. 2156 02:46:51.541 --> 02:46:55.250 But this is just making even more clear that the type of a dict object 2157 02:46:55.250 --> 02:46:58.333 is indeed the class, dict, itself. 2158 02:46:58.333 --> 02:47:02.375 So this is to say that, as new as a lot of today's idea and syntax, 2159 02:47:02.375 --> 02:47:05.708 might be you've actually been using it, perhaps unbeknownst to you, 2160 02:47:05.708 --> 02:47:06.750 for weeks now. 2161 02:47:06.750 --> 02:47:09.416 We now just have terminology to describe what 2162 02:47:09.416 --> 02:47:10.958 it is we've been doing all this time. 2163 02:47:10.958 --> 02:47:14.166 And you now have the expressiveness, with some practice, 2164 02:47:14.166 --> 02:47:18.791 to create your own classes, inside of which are your own instance variables, 2165 02:47:18.791 --> 02:47:23.708 perhaps wrapped with those properties and your own instance methods. 2166 02:47:23.708 --> 02:47:27.125 But it turns out there's other types of methods in the world. 2167 02:47:27.125 --> 02:47:29.375 Thus far, I've been deliberate in calling 2168 02:47:29.375 --> 02:47:34.375 all of our variables instance variables and all of our methods instance 2169 02:47:34.375 --> 02:47:35.125 methods. 2170 02:47:35.125 --> 02:47:40.333 It turns out there's other types of variables and methods out there, 2171 02:47:40.333 --> 02:47:42.875 and one of those is called class methods. 2172 02:47:42.875 --> 02:47:48.333 It turns out that sometimes it's not really necessary or sensible 2173 02:47:48.333 --> 02:47:52.333 to associate a function with objects of a class, 2174 02:47:52.333 --> 02:47:55.000 but rather with the class itself. 2175 02:47:55.000 --> 02:47:59.833 An instance, or an object of a class, is a very specific incarnation thereof. 2176 02:47:59.833 --> 02:48:03.333 Again, on that neighborhood that has a lot of identical looking buildings, 2177 02:48:03.333 --> 02:48:06.458 but they're all a little bit different because of different paint and such, 2178 02:48:06.458 --> 02:48:09.666 sometimes you might have functionality related 2179 02:48:09.666 --> 02:48:13.541 to each of those houses that isn't distinct or unique for any 2180 02:48:13.541 --> 02:48:14.333 of the houses. 2181 02:48:14.333 --> 02:48:17.375 It's functionality that's going to be exactly the same no 2182 02:48:17.375 --> 02:48:19.208 matter the house in question. 2183 02:48:19.208 --> 02:48:21.541 Same in the world of object-oriented programming. 2184 02:48:21.541 --> 02:48:24.375 Sometimes you want some functionality, some action 2185 02:48:24.375 --> 02:48:29.500 to be associated with the class itself, no matter what the specific object's 2186 02:48:29.500 --> 02:48:32.333 own values or instance variables are. 2187 02:48:32.333 --> 02:48:36.125 And for that, we have a keyword called @classmethod. 2188 02:48:36.125 --> 02:48:38.458 This is another decorator-- really, another 2189 02:48:38.458 --> 02:48:43.416 function-- that you can use to specify that this method is not, 2190 02:48:43.416 --> 02:48:46.208 by default, implicitly an instance method that 2191 02:48:46.208 --> 02:48:48.791 has access to self, the object itself. 2192 02:48:48.791 --> 02:48:51.750 This is a class method that's not going to have access to self, 2193 02:48:51.750 --> 02:48:54.166 but it does know what class it's inside. 2194 02:48:54.166 --> 02:48:55.625 So what do I mean by this? 2195 02:48:55.625 --> 02:48:57.416 Well, let me go back to VS Code here. 2196 02:48:57.416 --> 02:49:01.291 And let me propose that we create a new file this time implementing 2197 02:49:01.291 --> 02:49:04.958 the notion of a-- the sorting hat, from the world of Harry Potter as well, 2198 02:49:04.958 --> 02:49:06.041 to stay on theme. 2199 02:49:06.041 --> 02:49:08.666 I'm going to go ahead and run code of hat.py. 2200 02:49:08.666 --> 02:49:12.041 And in hat.py, let's implement the notion of the sorting hat. 2201 02:49:12.041 --> 02:49:14.583 If unfamiliar in the books and in the films 2202 02:49:14.583 --> 02:49:17.333 there is literally a pointy hat that, when 2203 02:49:17.333 --> 02:49:20.916 a student put it's on their head, that sorting hat, so to speak, 2204 02:49:20.916 --> 02:49:23.708 decides what house the student is in-- whether it's 2205 02:49:23.708 --> 02:49:25.416 Gryffindor or something else. 2206 02:49:25.416 --> 02:49:29.500 So let's implement, in code, this notion of a sorting hat such that, 2207 02:49:29.500 --> 02:49:33.250 when we pass to the sorting hat the name of a student, like, quote, unquote, 2208 02:49:33.250 --> 02:49:36.083 "Harry" this sorting hat, implemented in code, 2209 02:49:36.083 --> 02:49:40.041 will tell us what house that student should be in. 2210 02:49:40.041 --> 02:49:41.750 Well, let's go ahead and do this. 2211 02:49:41.750 --> 02:49:46.708 In hat.py, first, let's go ahead and define a class called hat, 2212 02:49:46.708 --> 02:49:49.333 and then let's get back to implementing it itself. 2213 02:49:49.333 --> 02:49:52.041 And I find this to be a helpful technique, not just with teaching 2214 02:49:52.041 --> 02:49:53.375 but when writing code. 2215 02:49:53.375 --> 02:49:55.291 I know I want a hat class. 2216 02:49:55.291 --> 02:49:57.541 I don't necessarily know what I want it to do yet, 2217 02:49:57.541 --> 02:50:00.083 so I'm going to create this placeholder, dot, dot, dot, 2218 02:50:00.083 --> 02:50:01.791 so I'll come back to that. 2219 02:50:01.791 --> 02:50:05.000 Let's now try to use this class as though it existed. 2220 02:50:05.000 --> 02:50:08.250 And from there, I perhaps can realize exactly what 2221 02:50:08.250 --> 02:50:11.625 functionality that class needs to have to support my use case. 2222 02:50:11.625 --> 02:50:14.708 Let me go ahead and create a variable called hat in all lowercase 2223 02:50:14.708 --> 02:50:17.500 and instantiate a hat object. 2224 02:50:17.500 --> 02:50:21.041 So no matter what the hat class ends up looking like, 2225 02:50:21.041 --> 02:50:24.125 this is the common syntax for instantiating 2226 02:50:24.125 --> 02:50:25.916 an object of a certain class. 2227 02:50:25.916 --> 02:50:28.958 In the past, we saw student, all lowercase, 2228 02:50:28.958 --> 02:50:32.708 equals capital Student, open parenthesis, close parentheses, 2229 02:50:32.708 --> 02:50:35.375 and then eventually, we added in things like name and house. 2230 02:50:35.375 --> 02:50:39.375 For now, let's assume that the hat is much simpler than a student, 2231 02:50:39.375 --> 02:50:41.500 and it only has sorting capabilities. 2232 02:50:41.500 --> 02:50:44.791 So I'm not going to even pass any arguments there, too. 2233 02:50:44.791 --> 02:50:50.708 Let me assume that the sorting hat has one function-- one method inside of it 2234 02:50:50.708 --> 02:50:51.583 called, sort. 2235 02:50:51.583 --> 02:50:59.666 And so if I do hat.sort ("Harry"), let's propose that that prints out what house 2236 02:50:59.666 --> 02:51:01.458 that student should be in. 2237 02:51:01.458 --> 02:51:02.541 So that's it. 2238 02:51:02.541 --> 02:51:04.541 I'm going to encapsulate-- 2239 02:51:04.541 --> 02:51:06.916 that is tuck away inside of a hat class-- 2240 02:51:06.916 --> 02:51:11.250 all of this requisite functionality, and I'm going to print out onto the screen 2241 02:51:11.250 --> 02:51:12.458 what hat-- 2242 02:51:12.458 --> 02:51:15.208 what house Harry belongs in. 2243 02:51:15.208 --> 02:51:20.166 Now I think I need to get into the weeds of actually initializing this class. 2244 02:51:20.166 --> 02:51:22.041 Well, let me go ahead and do this. 2245 02:51:22.041 --> 02:51:24.791 If I don't care to parameterize hat-- 2246 02:51:24.791 --> 02:51:29.083 I just want to, for instance sort values, 2247 02:51:29.083 --> 02:51:31.916 let's go ahead and define this function, sort, first. 2248 02:51:31.916 --> 02:51:35.291 So let's define sort, as taking a first argument, self, 2249 02:51:35.291 --> 02:51:39.125 which is always going to be the case when defining an instance method as 2250 02:51:39.125 --> 02:51:39.750 before. 2251 02:51:39.750 --> 02:51:43.750 But the sort method clearly takes one argument from the programmer, me-- 2252 02:51:43.750 --> 02:51:45.583 namely the student's name. 2253 02:51:45.583 --> 02:51:47.708 And again, we've seen this dichotomy before. 2254 02:51:47.708 --> 02:51:52.083 Even though I'm trying to pass in one argument, when I define the method, 2255 02:51:52.083 --> 02:51:55.166 it's got to take that many arguments, plus one more-- 2256 02:51:55.166 --> 02:51:58.208 self which is always going to be automatically passed in by Python 2257 02:51:58.208 --> 02:51:59.583 first. 2258 02:51:59.583 --> 02:52:01.291 What do I want to do? 2259 02:52:01.291 --> 02:52:03.291 Well, let's go ahead and do something like this. 2260 02:52:03.291 --> 02:52:06.166 2261 02:52:06.166 --> 02:52:12.375 Print, this name-- how about "is in," "some house." 2262 02:52:12.375 --> 02:52:14.291 I'm going to, again, use some placeholder code 2263 02:52:14.291 --> 02:52:18.000 for myself because I'm not quite sure how to finish implementing this sorting 2264 02:52:18.000 --> 02:52:18.583 hat. 2265 02:52:18.583 --> 02:52:21.708 But I think that's enough to just test where my code is at now. 2266 02:52:21.708 --> 02:52:25.625 Let me go ahead and run Python of hat.py and hit Enter. 2267 02:52:25.625 --> 02:52:28.250 And it looks like, indeed, Harry is in some house. 2268 02:52:28.250 --> 02:52:31.958 We're not done yet because it's clearly not doing anything interesting, 2269 02:52:31.958 --> 02:52:35.791 but it at least is running correctly with no errors. 2270 02:52:35.791 --> 02:52:40.125 Well, let's go ahead now and decide where-- 2271 02:52:40.125 --> 02:52:43.541 what house Harry should actually be in by introducing a bit of randomness 2272 02:52:43.541 --> 02:52:45.625 and choosing a house randomly. 2273 02:52:45.625 --> 02:52:47.500 While I can do this in a few ways, Let. 2274 02:52:47.500 --> 02:52:48.875 Me go ahead and do this. 2275 02:52:48.875 --> 02:52:51.541 I need to have a list of houses somewhere. 2276 02:52:51.541 --> 02:52:52.833 So where can I put that? 2277 02:52:52.833 --> 02:52:54.708 I could solve this problem in different ways. 2278 02:52:54.708 --> 02:52:56.000 Let me propose that I do this. 2279 02:52:56.000 --> 02:52:58.833 Let me define a method called init, as I've done before, 2280 02:52:58.833 --> 02:53:01.250 that takes in self, but no other arguments. 2281 02:53:01.250 --> 02:53:04.583 And whenever the sorting hat is instantiated, let's do this. 2282 02:53:04.583 --> 02:53:09.958 Let's create a houses instance variable, plural, that equals this list-- 2283 02:53:09.958 --> 02:53:16.958 Gryffindor, Hufflepuff, Ravenclaw, Slytherin, 2284 02:53:16.958 --> 02:53:19.416 so the exact same list that we've used before, 2285 02:53:19.416 --> 02:53:23.333 and I'm storing it in an instance variable inside of this class. 2286 02:53:23.333 --> 02:53:26.708 I'm not taking any arguments beyond self to init, 2287 02:53:26.708 --> 02:53:30.625 but I just need this list of values somewhere, for instance. 2288 02:53:30.625 --> 02:53:32.250 So what can I do here? 2289 02:53:32.250 --> 02:53:36.416 Well, let me go ahead and replace some house with the actual house. 2290 02:53:36.416 --> 02:53:37.541 Well, what could I do here? 2291 02:53:37.541 --> 02:53:39.583 Well, I want to put a house there. 2292 02:53:39.583 --> 02:53:42.083 Well, let's go ahead and create a variable called house. 2293 02:53:42.083 --> 02:53:46.291 And if you think back to our discussion of libraries, in the random module, 2294 02:53:46.291 --> 02:53:51.791 there is a function called choice that, if you pass in a list of choices, 2295 02:53:51.791 --> 02:53:56.333 like self.houses, that will pick a random house out of those four. 2296 02:53:56.333 --> 02:53:58.750 And then on line 7, I can pass it in. 2297 02:53:58.750 --> 02:54:01.916 If I want to tighten this up, let me just go ahead and highlight that code, 2298 02:54:01.916 --> 02:54:03.125 get rid of the variable. 2299 02:54:03.125 --> 02:54:04.541 It's technically unnecessary. 2300 02:54:04.541 --> 02:54:06.666 And because the line of code is still pretty short, 2301 02:54:06.666 --> 02:54:09.458 I'm OK with just putting it all in one line. 2302 02:54:09.458 --> 02:54:13.000 But I could certainly use the variable like I did a moment ago. 2303 02:54:13.000 --> 02:54:14.041 So what have I done? 2304 02:54:14.041 --> 02:54:20.708 In my init function, I have defined a initialization of the object 2305 02:54:20.708 --> 02:54:23.875 that stores in self.houses the list of four houses. 2306 02:54:23.875 --> 02:54:26.583 And then, in sort, I'm accessing that same list, 2307 02:54:26.583 --> 02:54:30.041 but I'm randomly choosing the set of houses there. 2308 02:54:30.041 --> 02:54:31.791 Now, why have I done it in this way? 2309 02:54:31.791 --> 02:54:33.750 This, too, is general convention. 2310 02:54:33.750 --> 02:54:35.916 Any time you have a list of things that-- who knows? 2311 02:54:35.916 --> 02:54:38.000 Maybe will change over time. 2312 02:54:38.000 --> 02:54:41.541 Places like Harvard have constructed new houses over the years, 2313 02:54:41.541 --> 02:54:43.958 so you might have to change the list of available houses. 2314 02:54:43.958 --> 02:54:47.083 It didn't happen in seven books or eight films of Harry Potter. 2315 02:54:47.083 --> 02:54:49.208 But you could imagine maybe Hogwarts eventually 2316 02:54:49.208 --> 02:54:54.291 has a fifth house, so there's generally some value in putting list of constants 2317 02:54:54.291 --> 02:54:57.333 toward the top of your file, toward the top of the class so it's just 2318 02:54:57.333 --> 02:54:59.875 obvious what the list of values is. 2319 02:54:59.875 --> 02:55:02.750 You don't want to necessarily tuck it away in some function, 2320 02:55:02.750 --> 02:55:06.375 like sort, especially if you might want to use that function-- 2321 02:55:06.375 --> 02:55:10.166 sorry, especially if you want to use that list in multiple functions, not 2322 02:55:10.166 --> 02:55:10.708 just sort. 2323 02:55:10.708 --> 02:55:12.583 But if I kept adding to this class, you might 2324 02:55:12.583 --> 02:55:15.083 want to use that same list of houses in multiple functions. 2325 02:55:15.083 --> 02:55:20.541 So let's keep it in the object itself by storing it in self.houses. 2326 02:55:20.541 --> 02:55:23.625 All right, well, we're about to change the course of history here perhaps. 2327 02:55:23.625 --> 02:55:27.666 Let me do Python of hat.py, and I think we're about to assign Harry 2328 02:55:27.666 --> 02:55:30.833 to one of those four houses randomly. 2329 02:55:30.833 --> 02:55:33.041 Huh, NameError. 2330 02:55:33.041 --> 02:55:35.041 Name 'random' is not defined. 2331 02:55:35.041 --> 02:55:38.416 Well, wait a minute, where did I go wrong here? 2332 02:55:38.416 --> 02:55:43.541 Thinking back to our class on libraries, why did my code break and not tell me 2333 02:55:43.541 --> 02:55:45.625 where Harry is to be? 2334 02:55:45.625 --> 02:55:48.708 AUDIENCE: You did not import the random library. 2335 02:55:48.708 --> 02:55:49.708 DAVID J. MALAN: Exactly. 2336 02:55:49.708 --> 02:55:52.541 If the random library or module is something I want to use, 2337 02:55:52.541 --> 02:55:55.583 I need to tell Python that at the top of my file. 2338 02:55:55.583 --> 02:55:58.958 So let me go up here and do import random. 2339 02:55:58.958 --> 02:56:01.708 And then, below that, let me go ahead and clear my terminal window 2340 02:56:01.708 --> 02:56:02.416 and try again. 2341 02:56:02.416 --> 02:56:08.041 Python of hat.py, crossing my fingers, seeing where Harry is going to end up. 2342 02:56:08.041 --> 02:56:12.041 And, OK, Harry as of now is officially in Hufflepuff, 2343 02:56:12.041 --> 02:56:14.500 despite everything you've read or seen. 2344 02:56:14.500 --> 02:56:15.875 Well, let's run this again. 2345 02:56:15.875 --> 02:56:19.833 Let me clear my window and run Python of hat.py, and now he's in Ravenclaw. 2346 02:56:19.833 --> 02:56:21.541 That's consistent with using random. 2347 02:56:21.541 --> 02:56:23.375 Let's clear that and run it again. 2348 02:56:23.375 --> 02:56:25.458 He's still in Ravenclaw, but that could happen, 2349 02:56:25.458 --> 02:56:26.833 even though there's four choices. 2350 02:56:26.833 --> 02:56:27.625 Let's do it again. 2351 02:56:27.625 --> 02:56:29.250 Hufflepuff-- back in Hufflepuff. 2352 02:56:29.250 --> 02:56:31.666 We can't seem to get the right answer. 2353 02:56:31.666 --> 02:56:34.375 Now he's in Gryffindor, albeit randomly. 2354 02:56:34.375 --> 02:56:37.208 So we seem to have a program that, based on these limited tests, 2355 02:56:37.208 --> 02:56:41.166 seems to be assigning Harry to a house randomly. 2356 02:56:41.166 --> 02:56:45.416 Now I'm somewhat lazily just letting sort print out this value. 2357 02:56:45.416 --> 02:56:50.375 I could do something else, like return a string, and then let me, on line 13, 2358 02:56:50.375 --> 02:56:51.916 do the printing for me. 2359 02:56:51.916 --> 02:56:57.000 But for now, I think we have an example of a class called hat that, 2360 02:56:57.000 --> 02:57:00.125 nonetheless, applies some of our lessons learned thus far today, 2361 02:57:00.125 --> 02:57:03.333 where I've created a class-- because a sorting hat is, frankly-- 2362 02:57:03.333 --> 02:57:06.250 well, I was about to say real world entity, but really 2363 02:57:06.250 --> 02:57:07.875 a fantasy world entity. 2364 02:57:07.875 --> 02:57:11.791 And indeed, that's a, perhaps, common heuristic or mental model to have. 2365 02:57:11.791 --> 02:57:16.041 When should you use a class to represent something in your code? 2366 02:57:16.041 --> 02:57:20.625 Very often, when you're trying to represent some real world entity 2367 02:57:20.625 --> 02:57:25.791 or fantasy world entity, like a student, which is something in the real world, 2368 02:57:25.791 --> 02:57:28.625 like a sorting hat, which, OK, doesn't exist, 2369 02:57:28.625 --> 02:57:32.166 but hat's certainly do, so quite reasonable to have a class for hat. 2370 02:57:32.166 --> 02:57:36.500 And that's not always the case that classes represent real world entities. 2371 02:57:36.500 --> 02:57:42.291 But we've seen thus far that int and stir and list and dict-- these 2372 02:57:42.291 --> 02:57:44.666 are all structures that you might have in the real world. 2373 02:57:44.666 --> 02:57:47.416 We have integers and strings of text and other things. 2374 02:57:47.416 --> 02:57:50.833 So it rather makes sense to represent even those things, more technically, 2375 02:57:50.833 --> 02:57:52.625 using a class as well. 2376 02:57:52.625 --> 02:57:56.291 You could use just a dictionary to represent a student or a hat. 2377 02:57:56.291 --> 02:58:00.875 But again, with classes come all this and even more functionality. 2378 02:58:00.875 --> 02:58:06.791 But I honestly am not using classes in, really, the "right way" here. 2379 02:58:06.791 --> 02:58:07.416 Why? 2380 02:58:07.416 --> 02:58:10.625 Well, in the world of Harry Potter there really is only, 2381 02:58:10.625 --> 02:58:12.791 to my knowledge, one sorting hat. 2382 02:58:12.791 --> 02:58:17.000 And yet, here I have gone and implemented a class called hat. 2383 02:58:17.000 --> 02:58:20.333 And again, a class is like a blueprint, a template, 2384 02:58:20.333 --> 02:58:24.625 a mold that allows you to create one or more objects thereof. 2385 02:58:24.625 --> 02:58:27.041 Now, most of my programs Thus far have been pretty simple, 2386 02:58:27.041 --> 02:58:28.750 and I've just created one student. 2387 02:58:28.750 --> 02:58:31.208 But certainly, if I spent more time and wrote more code, 2388 02:58:31.208 --> 02:58:33.583 you could imagine writing one program that 2389 02:58:33.583 --> 02:58:36.208 has a list of students-- many more students 2390 02:58:36.208 --> 02:58:38.708 than just the one we keep demonstrating. 2391 02:58:38.708 --> 02:58:40.625 Yet it would be a little weird-- 2392 02:58:40.625 --> 02:58:43.291 it's a little inconsistent with the real or the fantasy 2393 02:58:43.291 --> 02:58:48.041 world of Harry Potter to instantiate one, two, three or more sorting hats. 2394 02:58:48.041 --> 02:58:49.708 There really is just one. 2395 02:58:49.708 --> 02:58:51.875 Really one singleton, if you will, which is 2396 02:58:51.875 --> 02:58:54.458 a term of art in a lot of contexts of programming. 2397 02:58:54.458 --> 02:58:58.125 So let me propose that we actually improve the design of the sorting hat 2398 02:58:58.125 --> 02:59:03.083 so that we don't have to instantiate a sorting hat because right now this 2399 02:59:03.083 --> 02:59:08.791 is kind of allowing me to do something like hat 1 = hat, hat 2 = hat, hat 3 =, 2400 02:59:08.791 --> 02:59:09.458 and so forth. 2401 02:59:09.458 --> 02:59:11.291 I don't really need that capability. 2402 02:59:11.291 --> 02:59:15.250 I really just need to represent the sorting hat with a class, 2403 02:59:15.250 --> 02:59:17.125 but I don't really need to instantiate it. 2404 02:59:17.125 --> 02:59:17.625 Why? 2405 02:59:17.625 --> 02:59:18.708 Because it already exists. 2406 02:59:18.708 --> 02:59:19.625 I need just one. 2407 02:59:19.625 --> 02:59:23.041 So it turns out, in Python, that, up until now, 2408 02:59:23.041 --> 02:59:25.833 we've been using, as I keep calling them, instance methods-- 2409 02:59:25.833 --> 02:59:30.416 writing functions inside of classes that are automatically passed a reference 2410 02:59:30.416 --> 02:59:32.375 to self, the current object. 2411 02:59:32.375 --> 02:59:34.375 But sometimes you just don't need that. 2412 02:59:34.375 --> 02:59:37.083 Sometimes it suffices to just know what the class is 2413 02:59:37.083 --> 02:59:40.875 and assume that there might not even be any objects of that class. 2414 02:59:40.875 --> 02:59:45.666 So in this sense, you can use a class really as a container for data 2415 02:59:45.666 --> 02:59:50.291 and/or functionality that is just somehow conceptually related-- 2416 02:59:50.291 --> 02:59:52.791 things related to a sorting hat. 2417 02:59:52.791 --> 02:59:56.083 And there's this other decorator or function called @classmethod 2418 02:59:56.083 --> 02:59:57.750 that allows us to do just this. 2419 02:59:57.750 --> 02:59:59.375 So let me go back to my code here. 2420 02:59:59.375 --> 03:00:05.041 And let me propose that, if I'm not going to instantiate multiple houses, 2421 03:00:05.041 --> 03:00:07.916 I don't really need this init method because that's really 2422 03:00:07.916 --> 03:00:12.250 meant to initialize specific objects from that blueprint, that template, 2423 03:00:12.250 --> 03:00:13.083 that mold. 2424 03:00:13.083 --> 03:00:14.708 So let me get rid of this. 2425 03:00:14.708 --> 03:00:17.791 But if I get rid of this, I no longer have access to self. 2426 03:00:17.791 --> 03:00:22.750 But that's OK because it turns out, in addition to their existing class 2427 03:00:22.750 --> 03:00:26.416 methods, there are also what we might call class variables. 2428 03:00:26.416 --> 03:00:30.791 And class variables exist within the class itself. 2429 03:00:30.791 --> 03:00:33.833 And there's just one copy of that variable 2430 03:00:33.833 --> 03:00:35.958 for all of the objects thereof. 2431 03:00:35.958 --> 03:00:40.291 They all share, if you will, the same variable-- be it an int or str 2432 03:00:40.291 --> 03:00:41.750 or, in this case, a list. 2433 03:00:41.750 --> 03:00:49.041 So what I've done here is define, inside of my hat class, in a class variable 2434 03:00:49.041 --> 03:00:50.208 called houses-- 2435 03:00:50.208 --> 03:00:53.083 I don't say self because self is no longer relevant. 2436 03:00:53.083 --> 03:00:54.791 Self refers to specific objects. 2437 03:00:54.791 --> 03:00:58.166 I want a variable inside of this class, a.k.a. 2438 03:00:58.166 --> 03:01:00.791 A class variable that equals that list. 2439 03:01:00.791 --> 03:01:03.708 Because it's inside of this hat, now, class, 2440 03:01:03.708 --> 03:01:07.208 I can use that list in any of my functions. 2441 03:01:07.208 --> 03:01:08.958 I've only got one now, called sort. 2442 03:01:08.958 --> 03:01:11.791 But if I had more, it would be accessible to all of those methods 2443 03:01:11.791 --> 03:01:12.666 as well. 2444 03:01:12.666 --> 03:01:15.791 And with sort, it also doesn't really make sense 2445 03:01:15.791 --> 03:01:19.250 to sort within a specific sorting hat because, again, I 2446 03:01:19.250 --> 03:01:20.500 only want there to be one. 2447 03:01:20.500 --> 03:01:25.875 So I can actually specify that this is class method by saying @classmethod. 2448 03:01:25.875 --> 03:01:28.083 And I don't pass in self anymore. 2449 03:01:28.083 --> 03:01:33.250 I actually, by convention, pass in a reference to the class itself. 2450 03:01:33.250 --> 03:01:35.250 It's typically written as cls. 2451 03:01:35.250 --> 03:01:35.791 Why? 2452 03:01:35.791 --> 03:01:40.916 Well, if you wrote C-L-A-S-S, that would actually conflict with the keyword 2453 03:01:40.916 --> 03:01:42.791 "class" that we keep using up here. 2454 03:01:42.791 --> 03:01:47.125 So the world realized that, oops, we can't reuse that same phrase here. 2455 03:01:47.125 --> 03:01:48.833 So let's just call this class. 2456 03:01:48.833 --> 03:01:51.541 This is useful in some contexts including this one. 2457 03:01:51.541 --> 03:01:52.041 Why? 2458 03:01:52.041 --> 03:01:54.125 Well, notice what I can now do. 2459 03:01:54.125 --> 03:01:56.916 I can now change self to be just class. 2460 03:01:56.916 --> 03:01:57.541 Why? 2461 03:01:57.541 --> 03:02:02.708 Because houses now-- not an instance variable, accessible via self.houses. 2462 03:02:02.708 --> 03:02:07.041 It is now a class variable, accessible via class.houses, 2463 03:02:07.041 --> 03:02:10.375 or technically cls.houses in this case. 2464 03:02:10.375 --> 03:02:12.666 But now the final flourish is this. 2465 03:02:12.666 --> 03:02:19.208 Now, I don't have to instantiate any hat objects as I used to on here, line 13. 2466 03:02:19.208 --> 03:02:22.833 I can just use functionality that comes with this class. 2467 03:02:22.833 --> 03:02:24.791 So I'm going to delete that line altogether. 2468 03:02:24.791 --> 03:02:32.416 I'm going to capitalize the hat on this new line 13 and just say hat.sort, 2469 03:02:32.416 --> 03:02:33.333 ("Harry"). 2470 03:02:33.333 --> 03:02:34.750 So what have I done? 2471 03:02:34.750 --> 03:02:38.541 I've not bothered instantiating an object of type, hat. 2472 03:02:38.541 --> 03:02:42.791 I am just accessing a class method inside of the hat class 2473 03:02:42.791 --> 03:02:43.666 that-- you know what? 2474 03:02:43.666 --> 03:02:45.208 Is just going to work. 2475 03:02:45.208 --> 03:02:46.708 This is how class methods work. 2476 03:02:46.708 --> 03:02:51.041 You use the name of the class, capital letter and all, dot method name, 2477 03:02:51.041 --> 03:02:53.041 passing in any arguments you want. 2478 03:02:53.041 --> 03:02:57.000 Python is going to automatically pass in some variable via which 2479 03:02:57.000 --> 03:03:00.625 you can refer to that class in that function 2480 03:03:00.625 --> 03:03:02.458 that you've implemented inside of that class 2481 03:03:02.458 --> 03:03:04.125 so that I can do something like this. 2482 03:03:04.125 --> 03:03:07.833 It's not that I want a variable called houses locally in this function, 2483 03:03:07.833 --> 03:03:12.541 I want the variable called houses that's associated with this current class 2484 03:03:12.541 --> 03:03:17.166 so I can still access this same list that I defined on line 6. 2485 03:03:17.166 --> 03:03:21.500 And now, if I go back down here to my terminal and run Python of hat.py, 2486 03:03:21.500 --> 03:03:24.875 Enter, Harry is still in Hufflepuff once more. 2487 03:03:24.875 --> 03:03:27.041 Harry is still in Hufflepuff once more. 2488 03:03:27.041 --> 03:03:31.250 Harry is back in Gryffindor, at least randomly. 2489 03:03:31.250 --> 03:03:36.333 Questions, now, on these class variables or these class methods, 2490 03:03:36.333 --> 03:03:41.416 which are in contrast with instance variables and instance methods. 2491 03:03:41.416 --> 03:03:43.875 And the one thing, at least, that's a little strange 2492 03:03:43.875 --> 03:03:47.666 here is that, even though there's a decorator called @classmethod, 2493 03:03:47.666 --> 03:03:51.291 there is not one called @instancemethod. 2494 03:03:51.291 --> 03:03:55.666 A method is just automatically a so-called "instant method" when 2495 03:03:55.666 --> 03:03:57.541 you define it without any decorator. 2496 03:03:57.541 --> 03:04:00.291 AUDIENCE: Can you have a class inside another class? 2497 03:04:00.291 --> 03:04:01.291 DAVID J. MALAN: You can. 2498 03:04:01.291 --> 03:04:03.666 You can define one class inside of another. 2499 03:04:03.666 --> 03:04:06.041 Generally speaking, this isn't done, but there 2500 03:04:06.041 --> 03:04:08.416 are cases where it can be helpful, especially 2501 03:04:08.416 --> 03:04:10.625 for larger, more sophisticated programs. 2502 03:04:10.625 --> 03:04:13.541 So yes, it is possible. 2503 03:04:13.541 --> 03:04:14.750 Other questions. 2504 03:04:14.750 --> 03:04:17.958 AUDIENCE: The question was about the self.houses. 2505 03:04:17.958 --> 03:04:25.083 When we remove it and we pass data, variable is created itself, 2506 03:04:25.083 --> 03:04:27.458 s why we remove the self? 2507 03:04:27.458 --> 03:04:29.625 DAVID J. MALAN: So in the previous examples-- 2508 03:04:29.625 --> 03:04:32.625 both of the hat demonstration and also all of the student 2509 03:04:32.625 --> 03:04:36.708 demonstrations-- we were creating a student 2510 03:04:36.708 --> 03:04:40.625 object by calling Student, capital S, open parenthesis, close parenthesis, 2511 03:04:40.625 --> 03:04:43.125 with, eventually, name and a house passed in. 2512 03:04:43.125 --> 03:04:46.833 And then we were using the double underscore init method 2513 03:04:46.833 --> 03:04:52.708 to initialize the self.name and the self.house instance variables 2514 03:04:52.708 --> 03:04:55.333 therein to those respective values. 2515 03:04:55.333 --> 03:04:57.750 In this latest version of the sorting hat, 2516 03:04:57.750 --> 03:05:02.166 I haven't bothered with self anywhere, only because, conceptually, I 2517 03:05:02.166 --> 03:05:05.708 don't need or want there to be multiple hats in the world. 2518 03:05:05.708 --> 03:05:11.041 I'm just using the class as a container to bundle up this list of houses, 2519 03:05:11.041 --> 03:05:12.500 this sorting functionality. 2520 03:05:12.500 --> 03:05:14.875 Maybe eventually all add more functionality to it. 2521 03:05:14.875 --> 03:05:15.875 But that's it. 2522 03:05:15.875 --> 03:05:18.833 And so sometimes you can use object-oriented programming 2523 03:05:18.833 --> 03:05:22.458 in this somewhat different way when you want there to be functionality 2524 03:05:22.458 --> 03:05:25.333 but it's not specific to any one specific hat. 2525 03:05:25.333 --> 03:05:29.416 It's specific to the sorting hat itself. 2526 03:05:29.416 --> 03:05:33.958 How about one other question now, on these class variables or methods-- just 2527 03:05:33.958 --> 03:05:37.000 another way of using object-oriented programming but to solve 2528 03:05:37.000 --> 03:05:38.541 a somewhat different problem? 2529 03:05:38.541 --> 03:05:40.916 AUDIENCE: Well, what's the difference between the class 2530 03:05:40.916 --> 03:05:44.833 hat and a function of hat? 2531 03:05:44.833 --> 03:05:46.166 DAVID J. MALAN: A good question. 2532 03:05:46.166 --> 03:05:48.500 So why are we using a class at all and not just 2533 03:05:48.500 --> 03:05:51.916 having a file called hat.py with a variable called 2534 03:05:51.916 --> 03:05:54.500 houses and a function called sort? 2535 03:05:54.500 --> 03:05:56.166 Why are we adding this complexity? 2536 03:05:56.166 --> 03:05:58.958 In this particular case, we don't necessarily need to. 2537 03:05:58.958 --> 03:06:01.166 I could absolutely go in here. 2538 03:06:01.166 --> 03:06:02.833 I could get rid of the class. 2539 03:06:02.833 --> 03:06:05.625 I could undo this indentation. 2540 03:06:05.625 --> 03:06:07.666 I could get rid of this decorator. 2541 03:06:07.666 --> 03:06:09.541 And I could get rid of hat dot. 2542 03:06:09.541 --> 03:06:12.291 And I could just do this and additionally 2543 03:06:12.291 --> 03:06:14.208 let's say, let's get rid of class here. 2544 03:06:14.208 --> 03:06:15.833 Let's get rid of class here. 2545 03:06:15.833 --> 03:06:19.833 And now run Python of hat.py, Enter, and it still works. 2546 03:06:19.833 --> 03:06:22.125 Put Harry in the wrong house, but that's what we have-- 2547 03:06:22.125 --> 03:06:23.208 what happens randomly. 2548 03:06:23.208 --> 03:06:24.500 That's fine, too. 2549 03:06:24.500 --> 03:06:27.750 What we're introducing today, by way of object-oriented programming, 2550 03:06:27.750 --> 03:06:30.208 is just a different way of modeling the world. 2551 03:06:30.208 --> 03:06:32.916 It's not really compelling with an example like this, 2552 03:06:32.916 --> 03:06:35.125 frankly, that's relatively simple. 2553 03:06:35.125 --> 03:06:36.125 It's not very complex. 2554 03:06:36.125 --> 03:06:37.416 There's not much functionality. 2555 03:06:37.416 --> 03:06:40.875 Honestly, the version that we just typed up-- these 10 lines-- this is fine. 2556 03:06:40.875 --> 03:06:42.208 This solves this problem. 2557 03:06:42.208 --> 03:06:45.708 But as our code gets longer, as we start collaborating with other people, 2558 03:06:45.708 --> 03:06:48.666 as the problems we're trying to solve with code get more sophisticated, 2559 03:06:48.666 --> 03:06:52.125 you're going to find that your code gets messy quickly. 2560 03:06:52.125 --> 03:06:55.458 And you're going to find that you have a huge number of functions, for instance, 2561 03:06:55.458 --> 03:06:56.500 in one file. 2562 03:06:56.500 --> 03:06:59.958 And some of them are related to each other, but some of them are not. 2563 03:06:59.958 --> 03:07:02.625 Well, at that point, wouldn't it be nice to just organize them 2564 03:07:02.625 --> 03:07:03.500 a little differently? 2565 03:07:03.500 --> 03:07:06.500 And in the world of Harry Potter, let's have a class for student; 2566 03:07:06.500 --> 03:07:09.916 let's have a class for Professor; let's have a class for the sorting hat; 2567 03:07:09.916 --> 03:07:11.708 let's have a class for something else. 2568 03:07:11.708 --> 03:07:14.625 And so once your world gets much more complicated than some 2569 03:07:14.625 --> 03:07:17.791 of the demonstrations we do here in class when we want to focus 2570 03:07:17.791 --> 03:07:21.500 on individual ideas, object-oriented programming is just a way 2571 03:07:21.500 --> 03:07:24.083 of encapsulating related data-- 2572 03:07:24.083 --> 03:07:27.291 that is, variables-- related functionality-- that is, methods-- 2573 03:07:27.291 --> 03:07:29.583 inside of things that have names. 2574 03:07:29.583 --> 03:07:31.625 These things are called classes. 2575 03:07:31.625 --> 03:07:33.750 So it's just another way to solve problems. 2576 03:07:33.750 --> 03:07:36.125 And when we focused on libraries a couple of weeks back, 2577 03:07:36.125 --> 03:07:38.791 that, too, was another solution to the same problem. 2578 03:07:38.791 --> 03:07:42.291 You could define your own modules or packages, put some of your data 2579 03:07:42.291 --> 03:07:44.708 and/or functionality in there, and that's fine, too. 2580 03:07:44.708 --> 03:07:48.000 And sometimes which one you should use overlaps. 2581 03:07:48.000 --> 03:07:51.333 If you're familiar with Venn diagrams, the overlapping region 2582 03:07:51.333 --> 03:07:54.791 might mean that you could use a class; you could use a module or a package; 2583 03:07:54.791 --> 03:07:57.500 you could just use a single local file. 2584 03:07:57.500 --> 03:07:59.916 Over time, you'll develop an instinct and maybe even 2585 03:07:59.916 --> 03:08:04.291 a personal preference for which tool to use. 2586 03:08:04.291 --> 03:08:08.416 All right, let me propose, now, that we apply this same idea of a class method 2587 03:08:08.416 --> 03:08:10.583 to clean up one other thing as well. 2588 03:08:10.583 --> 03:08:16.041 Let me close that hat.py and reopen student.py as we left it earlier, 2589 03:08:16.041 --> 03:08:19.500 and let me go ahead and simplify it just a little bit. 2590 03:08:19.500 --> 03:08:22.750 I'm going to go ahead and get rid of the properties, 2591 03:08:22.750 --> 03:08:24.666 not because there's anything, wrong with them, 2592 03:08:24.666 --> 03:08:27.416 but just because I want us to focus on some of the key ideas 2593 03:08:27.416 --> 03:08:29.333 when we began with this program. 2594 03:08:29.333 --> 03:08:31.750 So I'm going to go ahead and keep main as well. 2595 03:08:31.750 --> 03:08:35.583 I'm not going to adversarially try to change Henry's address there. 2596 03:08:35.583 --> 03:08:38.666 I'm going to instead go ahead, though, and just print the student. 2597 03:08:38.666 --> 03:08:41.750 But this is the thing I want to focus on here. 2598 03:08:41.750 --> 03:08:46.541 This, in our previous student examples, was a missed opportunity 2599 03:08:46.541 --> 03:08:48.125 to clean up my code. 2600 03:08:48.125 --> 03:08:49.708 Well, what do I mean by that? 2601 03:08:49.708 --> 03:08:51.750 Well, up here at the top of this file-- even 2602 03:08:51.750 --> 03:08:53.958 though I've simplified it, but getting rid of the properties 2603 03:08:53.958 --> 03:08:55.708 and all of that error checking-- because I 2604 03:08:55.708 --> 03:08:58.708 want to focus on the essence of this class now-- just the student's name 2605 03:08:58.708 --> 03:09:00.875 and the house and the printing thereof. 2606 03:09:00.875 --> 03:09:05.000 This is, by nature of classes in object-oriented programming, 2607 03:09:05.000 --> 03:09:09.666 theoretically, all of my student-specific functionality. 2608 03:09:09.666 --> 03:09:14.083 That is to say, if I have functionality and data related to a student, you, 2609 03:09:14.083 --> 03:09:16.583 the programmer, my colleague, would assume 2610 03:09:16.583 --> 03:09:18.875 that it's all bundled up, encapsulated, so 2611 03:09:18.875 --> 03:09:20.791 to speak, inside of the student class. 2612 03:09:20.791 --> 03:09:24.208 And yet, if you scroll down further, what is this? 2613 03:09:24.208 --> 03:09:27.458 There's a function called get_student that just exists elsewhere 2614 03:09:27.458 --> 03:09:31.166 in this file that prompts the user for a name, prompts the user for a house, 2615 03:09:31.166 --> 03:09:34.083 creates the student object, and then returns. 2616 03:09:34.083 --> 03:09:35.500 That's not wrong. 2617 03:09:35.500 --> 03:09:36.208 It works. 2618 03:09:36.208 --> 03:09:39.041 And we saw many, many times it kept working. 2619 03:09:39.041 --> 03:09:42.208 But this is a little weird because, if this 2620 03:09:42.208 --> 03:09:45.208 is a function that helps you get a student, 2621 03:09:45.208 --> 03:09:48.916 helps you get the name of a student and the house of a student, why isn't 2622 03:09:48.916 --> 03:09:51.125 that functionality in the class itself? 2623 03:09:51.125 --> 03:09:54.291 After all, as my code gets more and more complicated and does more things, 2624 03:09:54.291 --> 03:09:56.125 I'm going to be looking at the student class 2625 03:09:56.125 --> 03:09:57.750 for all student-related functionality. 2626 03:09:57.750 --> 03:10:00.625 I'm not going to be scrolling down, expecting that, oh, maybe there's 2627 03:10:00.625 --> 03:10:04.708 some other student functionality just randomly later in this file. 2628 03:10:04.708 --> 03:10:05.958 So it's not wrong. 2629 03:10:05.958 --> 03:10:09.625 But this is, again, evidence of maybe bad design-- 2630 03:10:09.625 --> 03:10:11.541 not so much with this small program. 2631 03:10:11.541 --> 03:10:14.458 But this is an example, again, of code smell. 2632 03:10:14.458 --> 03:10:16.000 Something smells a little off here. 2633 03:10:16.000 --> 03:10:17.791 This is probably going to get us in trouble 2634 03:10:17.791 --> 03:10:20.791 by separating related functionality. 2635 03:10:20.791 --> 03:10:24.541 So again it's a design principle, not a correctness concern. 2636 03:10:24.541 --> 03:10:27.958 But class methods allow us to address this, too. 2637 03:10:27.958 --> 03:10:29.791 Let me go ahead and do this. 2638 03:10:29.791 --> 03:10:32.666 I'm going to delete get_student all together, leaving 2639 03:10:32.666 --> 03:10:35.458 only main as my other function here. 2640 03:10:35.458 --> 03:10:38.541 And inside of my student class, I'm going to do this. 2641 03:10:38.541 --> 03:10:42.375 I'm going to define a function, even more simply called, get. 2642 03:10:42.375 --> 03:10:45.291 And by nature of how class methods work, it's 2643 03:10:45.291 --> 03:10:47.625 going to take in the name of the class itself 2644 03:10:47.625 --> 03:10:49.833 or a reference thereto as an argument. 2645 03:10:49.833 --> 03:10:53.083 And I'm going to move the functionality from get_student into the student 2646 03:10:53.083 --> 03:10:53.583 class. 2647 03:10:53.583 --> 03:10:58.750 And I'm going to do this-- name equals input, quote, unquote, name, house 2648 03:10:58.750 --> 03:11:01.708 equals input, quote, unquote, house. 2649 03:11:01.708 --> 03:11:04.291 And then what this function is going to do 2650 03:11:04.291 --> 03:11:11.333 is return a new student object by calling class, which, again, is just 2651 03:11:11.333 --> 03:11:14.250 an automatically passed-in reference to the class 2652 03:11:14.250 --> 03:11:17.666 itself, passing in name and house. 2653 03:11:17.666 --> 03:11:22.916 And I will admit this syntax seems a little strange that now I'm calling cls 2654 03:11:22.916 --> 03:11:24.375 and I'm passing in these arguments. 2655 03:11:24.375 --> 03:11:26.125 But let me do one final fix here. 2656 03:11:26.125 --> 03:11:28.041 Let me go to the top of this function and more 2657 03:11:28.041 --> 03:11:31.125 explicitly say this is a class method. 2658 03:11:31.125 --> 03:11:34.541 This solves a potential chicken and the egg problem, so to speak, 2659 03:11:34.541 --> 03:11:37.291 whereby one needs to come before the other, potentially. 2660 03:11:37.291 --> 03:11:39.250 So what am I doing here? 2661 03:11:39.250 --> 03:11:43.916 Inside of my student class, I now have a function called get. 2662 03:11:43.916 --> 03:11:47.333 It is, I shall claim, a class method what does that mean. 2663 03:11:47.333 --> 03:11:53.000 It just means I can call this method without instantiating a student 2664 03:11:53.000 --> 03:11:54.125 object first. 2665 03:11:54.125 --> 03:11:57.333 Therein lies the potential chicken and the egg problem. 2666 03:11:57.333 --> 03:12:00.375 And if unfamiliar, that's an expression, meaning, well, and did the world 2667 03:12:00.375 --> 03:12:03.291 have chickens first that laid eggs, or was there an egg 2668 03:12:03.291 --> 03:12:06.958 that then yielded the chickens, but how did the egg get there? 2669 03:12:06.958 --> 03:12:08.791 It's this weird, circular problem. 2670 03:12:08.791 --> 03:12:10.208 And that's what we're facing here. 2671 03:12:10.208 --> 03:12:16.375 It would be weird if you had to create a student object in order to call get, 2672 03:12:16.375 --> 03:12:19.791 in order to get another student object. 2673 03:12:19.791 --> 03:12:20.875 That sounds messy. 2674 03:12:20.875 --> 03:12:24.833 Let's just get a student via a class method 2675 03:12:24.833 --> 03:12:29.125 that, by definition, does not require you to create a student object first. 2676 03:12:29.125 --> 03:12:33.166 Just like the hat, in its final form, we use the hat class 2677 03:12:33.166 --> 03:12:35.833 to just say Hat, capital H, dot sort. 2678 03:12:35.833 --> 03:12:37.458 We didn't need to create a hat first. 2679 03:12:37.458 --> 03:12:39.708 We just used the class itself. 2680 03:12:39.708 --> 03:12:41.791 So what am I going to do here now? 2681 03:12:41.791 --> 03:12:43.041 Let me go down to main. 2682 03:12:43.041 --> 03:12:46.791 And instead of saying get_student, notice what I can now do. 2683 03:12:46.791 --> 03:12:51.625 Student.get, and everything else can stay the same. 2684 03:12:51.625 --> 03:12:55.416 All I've done now is I've migrated all of my logic 2685 03:12:55.416 --> 03:12:58.541 from get_student, which was this own standalone function, 2686 03:12:58.541 --> 03:13:01.291 but clearly related to students by name. 2687 03:13:01.291 --> 03:13:05.458 I've moved the same code, really, to inside 2688 03:13:05.458 --> 03:13:09.458 of the student class in a more simply named function called get. 2689 03:13:09.458 --> 03:13:11.541 But I could still call it get_student if I want. 2690 03:13:11.541 --> 03:13:14.791 It just seems a little redundant to call it get_student in a student class, 2691 03:13:14.791 --> 03:13:16.291 so I'm simplifying. 2692 03:13:16.291 --> 03:13:20.208 So I have a method called get, but I'm calling it a class method 2693 03:13:20.208 --> 03:13:22.333 to avoid that chicken and the egg problem. 2694 03:13:22.333 --> 03:13:27.958 I want to be able to call a get without having a student object in my universe 2695 03:13:27.958 --> 03:13:28.791 already. 2696 03:13:28.791 --> 03:13:32.041 And the syntax for that is @classmethod. 2697 03:13:32.041 --> 03:13:35.333 The convention is to give this method at least one argument, 2698 03:13:35.333 --> 03:13:38.291 by convention called cls for class, which is just going 2699 03:13:38.291 --> 03:13:40.125 to be a reference to the class itself. 2700 03:13:40.125 --> 03:13:43.500 Lines 11 and 12 are identical to what they've always been. 2701 03:13:43.500 --> 03:13:46.666 And get_student-- the only new syntax here is this, 2702 03:13:46.666 --> 03:13:50.500 but this, again, is one of the features of object-oriented programming. 2703 03:13:50.500 --> 03:13:57.791 You can now instantiate a student object by just using cls that's passed in. 2704 03:13:57.791 --> 03:14:00.583 I technically could use Student, capital S, 2705 03:14:00.583 --> 03:14:03.333 but it turns out I'm doing what's more conventional because this 2706 03:14:03.333 --> 03:14:07.458 will both solve and avoid problems down the line with more complicated code. 2707 03:14:07.458 --> 03:14:11.916 This line here, on line 13, just means create an object of the current class. 2708 03:14:11.916 --> 03:14:12.708 What class is that? 2709 03:14:12.708 --> 03:14:13.791 Well, whatever cls is. 2710 03:14:13.791 --> 03:14:17.458 Well, that, by definition of how it all works, is going to be student. 2711 03:14:17.458 --> 03:14:22.250 And I want you to initialize it, as always, with name and house. 2712 03:14:22.250 --> 03:14:25.666 So now, scrolling down, my code is this. 2713 03:14:25.666 --> 03:14:27.875 And this is just nice to read. 2714 03:14:27.875 --> 03:14:31.791 You perhaps have to acquire a taste for this-- and I sound a little odd saying, 2715 03:14:31.791 --> 03:14:32.750 this is nice to read. 2716 03:14:32.750 --> 03:14:36.125 But indeed, student.get just tells me what's going on. 2717 03:14:36.125 --> 03:14:37.375 I'm going to get a student. 2718 03:14:37.375 --> 03:14:40.958 I don't need a separate function written by me called get_student in the file 2719 03:14:40.958 --> 03:14:41.750 itself. 2720 03:14:41.750 --> 03:14:44.875 The get functionality is built into the class. 2721 03:14:44.875 --> 03:14:47.416 All my student-related code now is together. 2722 03:14:47.416 --> 03:14:51.041 So let me go down to my terminal window and run Python of student.py, Enter. 2723 03:14:51.041 --> 03:14:52.083 Let's type in Harry. 2724 03:14:52.083 --> 03:14:53.416 Let's type in Gryffindor. 2725 03:14:53.416 --> 03:14:54.958 And we're back to where we began. 2726 03:14:54.958 --> 03:15:00.541 But, but, but everything related to students, now, is in this here class. 2727 03:15:00.541 --> 03:15:04.500 The only other thing in the file is main and this conditional 2728 03:15:04.500 --> 03:15:08.458 that we always use to avoid accidentally executing main when we're making 2729 03:15:08.458 --> 03:15:10.833 a module or a package or the like. 2730 03:15:10.833 --> 03:15:13.541 So again, a solution to a problem-- 2731 03:15:13.541 --> 03:15:16.875 not a big one in the case of a relatively small program, but one 2732 03:15:16.875 --> 03:15:19.041 that you will eventually encounter as your programs 2733 03:15:19.041 --> 03:15:24.083 get longer and longer, with more and more entities to represent. 2734 03:15:24.083 --> 03:15:29.000 Questions now on this use of a class method. 2735 03:15:29.000 --> 03:15:32.291 MICHAEL: Does the class have to be defined before the main function, 2736 03:15:32.291 --> 03:15:34.458 in terms of the order of the program? 2737 03:15:34.458 --> 03:15:36.083 DAVID J. MALAN: A really good question. 2738 03:15:36.083 --> 03:15:37.291 So when in doubt, let's try this. 2739 03:15:37.291 --> 03:15:38.666 So let's try to change the order. 2740 03:15:38.666 --> 03:15:41.208 Let's move main to the top, which I've often encouraged. 2741 03:15:41.208 --> 03:15:44.208 So let's go ahead and, above the class, do this. 2742 03:15:44.208 --> 03:15:47.000 And notice now that, technically, line two 2743 03:15:47.000 --> 03:15:51.458 is mentioning student, which does not exist until line 6 and below. 2744 03:15:51.458 --> 03:15:54.666 Let me go ahead and clear my terminal and run Python of student.py. 2745 03:15:54.666 --> 03:15:55.875 So far, so good. 2746 03:15:55.875 --> 03:15:58.750 Harry-- Gryffindor, OK. 2747 03:15:58.750 --> 03:16:00.458 Indeed, Harry's from Gryffindor. 2748 03:16:00.458 --> 03:16:03.541 The reason, Michael, it does not matter in this case 2749 03:16:03.541 --> 03:16:06.791 is because we're not actually calling main until the very end. 2750 03:16:06.791 --> 03:16:10.375 And just as in the past, that means that Python has a chance to read everything, 2751 03:16:10.375 --> 03:16:11.625 top to bottom, left to right. 2752 03:16:11.625 --> 03:16:13.083 So everything exists. 2753 03:16:13.083 --> 03:16:17.458 I would say, generally classes are defined at the top of the file. 2754 03:16:17.458 --> 03:16:21.916 However, it would be even maybe cleaner to move the Classes definition 2755 03:16:21.916 --> 03:16:25.083 to its own file and then import it, so essentially 2756 03:16:25.083 --> 03:16:28.750 to make reusable code by putting it into your own module or package 2757 03:16:28.750 --> 03:16:31.083 so that not just this program but many others 2758 03:16:31.083 --> 03:16:33.541 can use that definition of student as well. 2759 03:16:33.541 --> 03:16:37.750 Other questions now on classes, class methods, or the like. 2760 03:16:37.750 --> 03:16:44.833 AUDIENCE: I wanted to ask, is there a way to declare all the possible-- 2761 03:16:44.833 --> 03:16:47.583 all the possible attributes of the class? 2762 03:16:47.583 --> 03:16:51.000 Because it looks so inconsistent. 2763 03:16:51.000 --> 03:16:52.750 DAVID J. MALAN: Well, so my takeaway there 2764 03:16:52.750 --> 03:16:55.208 is this is Python's approach to these principles. 2765 03:16:55.208 --> 03:16:57.791 Different languages, like Java, just take a different approach 2766 03:16:57.791 --> 03:16:59.666 but have very similar features. 2767 03:16:59.666 --> 03:17:01.500 The syntax just tends to vary. 2768 03:17:01.500 --> 03:17:05.208 And this is how the Python community chose to implement this idea. 2769 03:17:05.208 --> 03:17:08.750 The right mental model, ultimately, is that these instance variables, 2770 03:17:08.750 --> 03:17:14.958 instant methods belong to or operate on specific objects-- 2771 03:17:14.958 --> 03:17:17.750 a specific student, a specific hat. 2772 03:17:17.750 --> 03:17:22.500 Class variables and class methods operate on the entire class 2773 03:17:22.500 --> 03:17:26.125 itself or, in turn, all objects of that class, which 2774 03:17:26.125 --> 03:17:30.000 we've not seen a demonstration of, but it's a higher level concept. 2775 03:17:30.000 --> 03:17:32.583 So it turns out, besides these class methods, 2776 03:17:32.583 --> 03:17:35.625 which are distinct from those instance methods, which, to be fair, 2777 03:17:35.625 --> 03:17:39.375 do not have their own decorator-- they just are, by default, instance method, 2778 03:17:39.375 --> 03:17:42.458 there's yet other types of Methods You can have in classes in Python. 2779 03:17:42.458 --> 03:17:44.875 They tend to be called static methods, and they, too, 2780 03:17:44.875 --> 03:17:48.041 come with another decorator called @static method, which 2781 03:17:48.041 --> 03:17:49.666 is a rabbit hole we won't go down. 2782 03:17:49.666 --> 03:17:52.125 But realize that there is yet other functionality 2783 03:17:52.125 --> 03:17:54.750 that you can leverage within object-oriented programming. 2784 03:17:54.750 --> 03:17:59.041 But what we thought we'd do is focus really on some final core features 2785 03:17:59.041 --> 03:18:02.166 that you see not just in Python but other languages as well. 2786 03:18:02.166 --> 03:18:05.750 And perhaps one of the most compelling features of object-oriented programming 2787 03:18:05.750 --> 03:18:09.375 that we haven't yet used explicitly-- though it turns out we've seen 2788 03:18:09.375 --> 03:18:11.458 implicitly over the past weeks-- 2789 03:18:11.458 --> 03:18:13.625 is this notion of inheritance. 2790 03:18:13.625 --> 03:18:16.250 It turns out, via object-oriented programming, 2791 03:18:16.250 --> 03:18:19.666 there's actually an opportunity to design your classes 2792 03:18:19.666 --> 03:18:25.125 in a hierarchical fashion, whereby you can have one class inherit from 2793 03:18:25.125 --> 03:18:30.833 or borrow attributes-- that is, methods or variables from another class 2794 03:18:30.833 --> 03:18:33.291 if they all have those in common. 2795 03:18:33.291 --> 03:18:35.083 So what do I mean by this here? 2796 03:18:35.083 --> 03:18:39.708 Well, let me propose that we implement, over in VS Code here, 2797 03:18:39.708 --> 03:18:42.250 a brand new file called wizard.py. 2798 03:18:42.250 --> 03:18:45.583 Let me go ahead and run code of wizard.py. 2799 03:18:45.583 --> 03:18:50.333 And then let's start as before, defining a class called student. 2800 03:18:50.333 --> 03:18:54.416 And let's go ahead and first define the underscore underscore, init method, 2801 03:18:54.416 --> 03:18:57.291 which of course, is minimally going to take an argument traditionally 2802 03:18:57.291 --> 03:18:58.000 called self. 2803 03:18:58.000 --> 03:19:01.375 And in this case, let's also have it take as before a name and a house. 2804 03:19:01.375 --> 03:19:03.875 And then in this init method, let's go ahead 2805 03:19:03.875 --> 03:19:08.208 and assign the instance variables-- self.name = name, 2806 03:19:08.208 --> 03:19:11.208 and self.house = house. 2807 03:19:11.208 --> 03:19:13.375 Let's assume that there's some other functionality 2808 03:19:13.375 --> 03:19:15.083 in this class as well-- dot, dot, dot. 2809 03:19:15.083 --> 03:19:18.416 But let's move on now to implementing the notion of a professor 2810 03:19:18.416 --> 03:19:20.208 in the wizarding world as well. 2811 03:19:20.208 --> 03:19:24.625 So for this class, let's call it Professor. 2812 03:19:24.625 --> 03:19:26.958 And a professor, let's say, is also going 2813 03:19:26.958 --> 03:19:29.416 to have its own initialization method. 2814 03:19:29.416 --> 03:19:30.708 So __ init. 2815 03:19:30.708 --> 03:19:32.000 It's going to take self-- 2816 03:19:32.000 --> 03:19:33.791 always as the first argument. 2817 03:19:33.791 --> 03:19:35.500 A professor also has a name. 2818 03:19:35.500 --> 03:19:37.166 So we'll pass that in second, too. 2819 03:19:37.166 --> 03:19:39.958 And even though some professors are heads of houses, 2820 03:19:39.958 --> 03:19:42.041 let's assume that a professor is really identified 2821 03:19:42.041 --> 03:19:45.500 by their name and their subject area-- the class that they teach. 2822 03:19:45.500 --> 03:19:48.041 So we'll call this third argument, subject. 2823 03:19:48.041 --> 03:19:51.791 Now, as before, let's go ahead and assign self.name = name, 2824 03:19:51.791 --> 03:19:56.083 and let's assign self.subject = subject here. 2825 03:19:56.083 --> 03:19:59.541 And as before, let's assume that there's some more functionality associated 2826 03:19:59.541 --> 03:20:01.083 with professors as well. 2827 03:20:01.083 --> 03:20:05.625 Well, what do you notice already here in my definitions of students 2828 03:20:05.625 --> 03:20:07.333 and professors? 2829 03:20:07.333 --> 03:20:11.750 Typically, we're a bit reluctant to allow for any redundancy in our code. 2830 03:20:11.750 --> 03:20:16.041 And here, I feel like my init method is taking a name for students; 2831 03:20:16.041 --> 03:20:18.625 my init method is also taking a name for a professor; 2832 03:20:18.625 --> 03:20:22.458 and I have these identical lines of code, like self.name = name. 2833 03:20:22.458 --> 03:20:25.208 And this is only going to get exacerbated if I now go and add 2834 03:20:25.208 --> 03:20:26.083 some error checking. 2835 03:20:26.083 --> 03:20:29.916 So for instance, how about if not name, we 2836 03:20:29.916 --> 03:20:32.083 should probably be in the habit of raising something 2837 03:20:32.083 --> 03:20:36.208 like a value error in an explanatory message, like "Missing name." 2838 03:20:36.208 --> 03:20:37.166 And you know what? 2839 03:20:37.166 --> 03:20:39.750 If a professor is missing their name, I should probably 2840 03:20:39.750 --> 03:20:41.291 copy, paste that code down here. 2841 03:20:41.291 --> 03:20:43.666 And that's where red flags should be going off, 2842 03:20:43.666 --> 03:20:45.916 whereby, as soon as you start copy pasting code, 2843 03:20:45.916 --> 03:20:50.208 there's probably a better way so that we can write the code once and perhaps 2844 03:20:50.208 --> 03:20:51.500 reuse it in some way. 2845 03:20:51.500 --> 03:20:54.916 And here, too, object-oriented programming offers a solution. 2846 03:20:54.916 --> 03:20:58.041 It turns out that object-oriented programming in Python 2847 03:20:58.041 --> 03:21:03.458 also supports inheritance, whereby you can define multiple classes that 2848 03:21:03.458 --> 03:21:05.125 somehow relate to one another. 2849 03:21:05.125 --> 03:21:07.833 They don't need to exist in parallel in this way. 2850 03:21:07.833 --> 03:21:10.375 There could actually be some hierarchy between them. 2851 03:21:10.375 --> 03:21:12.333 So for instance, in the wizarding world, we 2852 03:21:12.333 --> 03:21:15.583 could argue that both a student and a professor are, at the end of the day, 2853 03:21:15.583 --> 03:21:16.291 Wizards. 2854 03:21:16.291 --> 03:21:19.666 So maybe what we should really define is a third class, 2855 03:21:19.666 --> 03:21:21.958 for instance, called wizard, that has any 2856 03:21:21.958 --> 03:21:25.916 of the common attributes for students and professors alike. 2857 03:21:25.916 --> 03:21:27.791 And for now, we've kept it relatively simple. 2858 03:21:27.791 --> 03:21:30.666 The only thing they have in common is a name and a name, 2859 03:21:30.666 --> 03:21:32.458 in student and professor, respectively. 2860 03:21:32.458 --> 03:21:35.416 So why don't we minimally factor that out first? 2861 03:21:35.416 --> 03:21:37.083 All right, so let me go ahead here. 2862 03:21:37.083 --> 03:21:39.708 And just to keep things organized, at the top of my file, let's 2863 03:21:39.708 --> 03:21:41.833 define a third class called Wizard. 2864 03:21:41.833 --> 03:21:45.208 And a wizard will have its own initialization method. 2865 03:21:45.208 --> 03:21:49.541 So def __init__(self), as always. 2866 03:21:49.541 --> 03:21:52.333 And a wizard, let's say for now, is only going 2867 03:21:52.333 --> 03:21:55.625 to be initialized with their name in this way. 2868 03:21:55.625 --> 03:21:58.791 And now, I'm going to go ahead and do some of that error checking. 2869 03:21:58.791 --> 03:22:02.708 So if not name will raise a value error in the wizard class. 2870 03:22:02.708 --> 03:22:06.041 Otherwise, we'll go ahead and do self name equals name, 2871 03:22:06.041 --> 03:22:09.666 and, heck, dot, dot, dot, maybe some other functionality as well. 2872 03:22:09.666 --> 03:22:13.041 But not a subject, which is specific to professors, and not a house, 2873 03:22:13.041 --> 03:22:15.166 which I've claimed is specific to students. 2874 03:22:15.166 --> 03:22:18.375 Now, I think we can begin to maybe remove 2875 03:22:18.375 --> 03:22:20.833 some of the redundancies in our other classes here. 2876 03:22:20.833 --> 03:22:24.416 So for instance, down with student, why don't I 2877 03:22:24.416 --> 03:22:28.750 go ahead and remove this error checking here and remove this error-- 2878 03:22:28.750 --> 03:22:32.166 this assignment of self.name = name because I'm already 2879 03:22:32.166 --> 03:22:33.291 doing that in Wizard. 2880 03:22:33.291 --> 03:22:35.958 And similarly, down here, in Professor, why don't I do the same? 2881 03:22:35.958 --> 03:22:37.583 Let's get rid of the error checking. 2882 03:22:37.583 --> 03:22:41.083 Let's get rid of self.name = name because, again, I'm doing that already 2883 03:22:41.083 --> 03:22:43.041 up there for Wizard as well. 2884 03:22:43.041 --> 03:22:45.916 But at the moment, even though they're all in the same file, 2885 03:22:45.916 --> 03:22:50.666 I haven't told Python that a student is a wizard and a professor is a wizard. 2886 03:22:50.666 --> 03:22:53.000 So I really need to link these two together. 2887 03:22:53.000 --> 03:22:57.500 And the way you can prescribe inheritance, whereby one class should 2888 03:22:57.500 --> 03:23:00.041 inherit from another, or conversely, one class 2889 03:23:00.041 --> 03:23:03.541 should descend from another-- we can do this. 2890 03:23:03.541 --> 03:23:05.125 I can say class Student. 2891 03:23:05.125 --> 03:23:08.416 But before the colon, I can go in and say in parentheses, 2892 03:23:08.416 --> 03:23:13.916 a student inherits from, or is a subclass of wizard, which, conversely, 2893 03:23:13.916 --> 03:23:16.666 is the superclass of the student class. 2894 03:23:16.666 --> 03:23:19.583 So this just means that, when I define a student class, 2895 03:23:19.583 --> 03:23:23.958 go ahead and inherit all of the characteristics of a wizard as well. 2896 03:23:23.958 --> 03:23:26.875 And I'm going to do the same thing for Professor. 2897 03:23:26.875 --> 03:23:30.166 So (Wizard) after the class name Professor, 2898 03:23:30.166 --> 03:23:33.833 and that's going to give me access to some of that same functionality. 2899 03:23:33.833 --> 03:23:37.458 But because my student class and my professor class 2900 03:23:37.458 --> 03:23:39.416 still have their same init methods, those 2901 03:23:39.416 --> 03:23:41.291 are the methods that are going to get called. 2902 03:23:41.291 --> 03:23:44.916 Whenever I create a student in code or I create a professor in code, 2903 03:23:44.916 --> 03:23:47.625 I need to somehow explicitly say that I also 2904 03:23:47.625 --> 03:23:51.916 want to use the functionality in the Wizard class's init method. 2905 03:23:51.916 --> 03:23:54.916 And the way to do this in Python is as follows. 2906 03:23:54.916 --> 03:23:56.958 Let me go into my init method for Student, 2907 03:23:56.958 --> 03:23:59.958 and let me call super, with no arguments, which 2908 03:23:59.958 --> 03:24:03.333 is a reference to the superclass of this class. 2909 03:24:03.333 --> 03:24:05.916 So if this class is Student, the superclass-- that is, 2910 03:24:05.916 --> 03:24:07.791 the parent class-- is Wizard. 2911 03:24:07.791 --> 03:24:13.875 So super() will have the effect of accessing the superclass. 2912 03:24:13.875 --> 03:24:17.750 And then I'm going to go ahead and explicitly call its init method, 2913 03:24:17.750 --> 03:24:21.416 and I'm going to pass to the Wizard's init method 2914 03:24:21.416 --> 03:24:24.958 the name that the student's init method was passed. 2915 03:24:24.958 --> 03:24:28.000 And I'm going to go ahead and do the same down here in Wizard. 2916 03:24:28.000 --> 03:24:29.666 This is one line of copy, paste. 2917 03:24:29.666 --> 03:24:32.375 But I think I'm OK with it here because it's still 2918 03:24:32.375 --> 03:24:34.291 allowing me to do all of the name assignment 2919 03:24:34.291 --> 03:24:37.000 and the error checking up in the Wizard class instead. 2920 03:24:37.000 --> 03:24:42.250 I think we're OK now by just calling super.init for both student 2921 03:24:42.250 --> 03:24:43.416 and Professor alike. 2922 03:24:43.416 --> 03:24:47.208 Now, admittedly, this syntax is definitely out there-- the fact 2923 03:24:47.208 --> 03:24:49.541 that we're calling super in parentheses and dots 2924 03:24:49.541 --> 03:24:52.458 and underscore underscore on the left and the right of init here, 2925 03:24:52.458 --> 03:24:54.875 but it's just a combination of these two ideas. 2926 03:24:54.875 --> 03:25:00.916 super() is a way of programmatically accessing a current class's parent 2927 03:25:00.916 --> 03:25:05.291 class, or superclass, and __init, of course, is just referring to, now, 2928 03:25:05.291 --> 03:25:08.041 that class's own initialization method. 2929 03:25:08.041 --> 03:25:09.958 Now, per the dot, dot, dot-- so there could be 2930 03:25:09.958 --> 03:25:11.500 a lot more going on in these classes. 2931 03:25:11.500 --> 03:25:15.208 But what's nice now is that Wizard as a class 2932 03:25:15.208 --> 03:25:18.291 is taking care of all of the assignment of a wizard's name, 2933 03:25:18.291 --> 03:25:20.458 whether that wizard is a student or a professor. 2934 03:25:20.458 --> 03:25:22.416 And it's even doing some error checking to make 2935 03:25:22.416 --> 03:25:25.125 sure the name was actually passed in. 2936 03:25:25.125 --> 03:25:29.166 Meanwhile, student is inheriting all of that functionality 2937 03:25:29.166 --> 03:25:32.333 and using it by calling the superclass's own init method. 2938 03:25:32.333 --> 03:25:35.041 But it's additionally taking the house, that's presumably 2939 03:25:35.041 --> 03:25:37.375 passed into the student constructor function, 2940 03:25:37.375 --> 03:25:40.833 and assigning it to its own instance variable-- self.house, 2941 03:25:40.833 --> 03:25:44.458 and similarly, professor, or restoring in self.subject 2942 03:25:44.458 --> 03:25:47.875 the subject that was passed into that one as well. 2943 03:25:47.875 --> 03:25:49.916 Now, how might we use these classes? 2944 03:25:49.916 --> 03:25:53.500 Well, we'll continue to wave our hands with a little bit of detail here. 2945 03:25:53.500 --> 03:25:56.750 But at the bottom of this file, or any other file that imports this one, 2946 03:25:56.750 --> 03:25:58.833 I could now write code like this. 2947 03:25:58.833 --> 03:26:01.208 I could create a student variable and assign 2948 03:26:01.208 --> 03:26:03.458 it the return value of the student constructor call. 2949 03:26:03.458 --> 03:26:07.625 and maybe that student is named Harry and that student's house, 2950 03:26:07.625 --> 03:26:10.041 for instance, might be Gryffindor. 2951 03:26:10.041 --> 03:26:14.750 And meanwhile, I might do something like this. professor = Professor over here. 2952 03:26:14.750 --> 03:26:18.541 And notice, the lowercase S on the left, capital S on the right. 2953 03:26:18.541 --> 03:26:21.875 Same for professor on the left-- lowercase and uppercase on the right 2954 03:26:21.875 --> 03:26:22.750 respectively. 2955 03:26:22.750 --> 03:26:25.625 Professor, quote, unquote, "Severus," and how 2956 03:26:25.625 --> 03:26:30.500 about Defense Against the Dark Arts will be his subject? 2957 03:26:30.500 --> 03:26:32.791 And meanwhile, if we want, more generically, 2958 03:26:32.791 --> 03:26:37.291 just a wizard, who, at the moment is neither student nor professor 2959 03:26:37.291 --> 03:26:39.375 teaching classes actively, we could even do that. 2960 03:26:39.375 --> 03:26:43.458 We could do wizard = Wizard in capital W on the right-hand side 2961 03:26:43.458 --> 03:26:45.708 of the equal sign, because it's the name of the class. 2962 03:26:45.708 --> 03:26:47.166 And someone like Albus-- 2963 03:26:47.166 --> 03:26:49.958 passing in only Albus's name-- 2964 03:26:49.958 --> 03:26:55.708 not a house, not a subject, because, in this case, he's known only as a wizard. 2965 03:26:55.708 --> 03:26:58.750 Meanwhile, with each of these calls, this line of code 2966 03:26:58.750 --> 03:27:01.833 here will ensure that the init method for the wizard class is called. 2967 03:27:01.833 --> 03:27:05.583 This line of code here will ensure that the init method of the student 2968 03:27:05.583 --> 03:27:09.250 class and, in turn, the init method of the superclass wizard is called. 2969 03:27:09.250 --> 03:27:11.541 And then lastly, on this final line of code, 2970 03:27:11.541 --> 03:27:15.625 will this syntax ensure that the init method of the professor class 2971 03:27:15.625 --> 03:27:20.375 is called, which, in turn, calls the init method of the superclass as well. 2972 03:27:20.375 --> 03:27:24.083 Any questions now on this idea of inheritance, 2973 03:27:24.083 --> 03:27:29.125 which is a key feature of a lot of object-oriented programming languages? 2974 03:27:29.125 --> 03:27:33.458 2975 03:27:33.458 --> 03:27:36.708 MICHAEL: From what I've seen so far, a lot of times, there's a lot of nesting. 2976 03:27:36.708 --> 03:27:38.375 If you do super, does it go one up? 2977 03:27:38.375 --> 03:27:44.375 Is there any situation where it's nested in another class as well, above Wizard, 2978 03:27:44.375 --> 03:27:44.875 let's say? 2979 03:27:44.875 --> 03:27:46.500 DAVID J. MALAN: A really good question. 2980 03:27:46.500 --> 03:27:48.375 If you were to have a super superclass-- so 2981 03:27:48.375 --> 03:27:52.833 your hierarchy is even taller than the two levels of hierarchy 2982 03:27:52.833 --> 03:27:56.625 that we currently have, absolutely. 2983 03:27:56.625 --> 03:27:59.250 What's nice about inheritance, as the name implies, is, 2984 03:27:59.250 --> 03:28:02.583 just as you might have inherited certain traits as a human 2985 03:28:02.583 --> 03:28:06.000 from your grandfather and grandmother or your great-grandfather 2986 03:28:06.000 --> 03:28:08.333 or great-grandmother, some of those properties 2987 03:28:08.333 --> 03:28:13.083 can actually trickle down to you in the context of code as well. 2988 03:28:13.083 --> 03:28:17.291 So when you descend from another class-- 2989 03:28:17.291 --> 03:28:21.541 that is, when you subclass a superclass or a super superclass, 2990 03:28:21.541 --> 03:28:23.958 you actually do inherit all of the functionality, 2991 03:28:23.958 --> 03:28:26.625 not just from one level above you but from two or three, 2992 03:28:26.625 --> 03:28:29.583 so you can indeed access some of that functionality as well. 2993 03:28:29.583 --> 03:28:32.666 And you can even override it if you want some of these classes 2994 03:28:32.666 --> 03:28:35.750 to behave a little bit differently than others. 2995 03:28:35.750 --> 03:28:38.125 Other questions on inheritance. 2996 03:28:38.125 --> 03:28:39.875 AUDIENCE: So it's similar to the last one, 2997 03:28:39.875 --> 03:28:43.125 but can you have two parents on the same level? 2998 03:28:43.125 --> 03:28:44.750 DAVID J. MALAN: A really good question. 2999 03:28:44.750 --> 03:28:49.875 So there are ways to implement descendants from multiple parents. 3000 03:28:49.875 --> 03:28:53.125 And there's different ways to do this, not just in Python but other languages. 3001 03:28:53.125 --> 03:28:57.916 We've kept things simple here, though, by having a single inheritance path. 3002 03:28:57.916 --> 03:28:58.666 A good question. 3003 03:28:58.666 --> 03:29:01.291 How about one more question on inheritance? 3004 03:29:01.291 --> 03:29:07.208 AUDIENCE: Can we have multiple arguments in super.__init? 3005 03:29:07.208 --> 03:29:11.000 DAVID J. MALAN: Yes, but in this case, I'm only passing a name on line 18, 3006 03:29:11.000 --> 03:29:13.666 and I'm only passing in name on line 10. 3007 03:29:13.666 --> 03:29:14.166 Why? 3008 03:29:14.166 --> 03:29:18.208 Because, on line 2, when I define the init method for the Wizard class, 3009 03:29:18.208 --> 03:29:20.375 I only expect a single argument. 3010 03:29:20.375 --> 03:29:23.583 But I could absolutely have other common functionality. 3011 03:29:23.583 --> 03:29:24.750 I could add in a patronus. 3012 03:29:24.750 --> 03:29:27.125 If both students and professors have patronuses 3013 03:29:27.125 --> 03:29:30.958 that can come out of their wands, I could have two arguments instead. 3014 03:29:30.958 --> 03:29:33.708 We've been using this feature of object-oriented programming 3015 03:29:33.708 --> 03:29:37.000 now for quite some time in the form of exceptions. 3016 03:29:37.000 --> 03:29:40.625 Indeed, if you look at the official documentation for exceptions in Python, 3017 03:29:40.625 --> 03:29:43.208 you'll see that there's not even the ones we've seen in class, 3018 03:29:43.208 --> 03:29:44.541 like value error and others. 3019 03:29:44.541 --> 03:29:48.666 There's any number of others as well, but they are all, themselves, 3020 03:29:48.666 --> 03:29:50.125 hierarchical in nature. 3021 03:29:50.125 --> 03:29:54.416 This is just a subset of the available exceptions that come built into Python. 3022 03:29:54.416 --> 03:29:58.125 And you can actually, as a programmer, create your own exceptions as well. 3023 03:29:58.125 --> 03:30:01.916 But as this chart here captures hierarchically, 3024 03:30:01.916 --> 03:30:06.125 all exceptions we've seen thus far actually descend from 3025 03:30:06.125 --> 03:30:09.041 or inherit from superclasses already. 3026 03:30:09.041 --> 03:30:10.833 So for instance, at the bottom of this list 3027 03:30:10.833 --> 03:30:13.250 here is ValueError, which we've seen quite a bit. 3028 03:30:13.250 --> 03:30:17.583 And if you follow the line straight up on this ascii rendition of this chart, 3029 03:30:17.583 --> 03:30:22.083 you'll see that ValueError has a parent, class, or superclass, called exception. 3030 03:30:22.083 --> 03:30:25.958 And the exception class, meanwhile, has a parent class called base exception. 3031 03:30:25.958 --> 03:30:28.125 Why did the authors of Python do this? 3032 03:30:28.125 --> 03:30:32.958 Well, it turns out that, whether you have a value error or a key error 3033 03:30:32.958 --> 03:30:35.416 or an assertion error or any number of others, 3034 03:30:35.416 --> 03:30:40.250 there's a lot of functionality common to all of those types of errors 3035 03:30:40.250 --> 03:30:42.458 that you want-- 3036 03:30:42.458 --> 03:30:44.958 that you want a programmer to be able to use. 3037 03:30:44.958 --> 03:30:48.375 And so it turns out that the authors of Python decided, you know what? 3038 03:30:48.375 --> 03:30:51.791 Let's not have a dozen or more different classes 3039 03:30:51.791 --> 03:30:55.166 that all just have copy, pasted similar functionality. 3040 03:30:55.166 --> 03:30:57.250 Let's create this hierarchy so that, even 3041 03:30:57.250 --> 03:31:00.875 though the exceptions toward the bottom of this list are very precise, 3042 03:31:00.875 --> 03:31:02.291 they at least inherit-- 3043 03:31:02.291 --> 03:31:05.583 that is, borrow some very common functionality up above. 3044 03:31:05.583 --> 03:31:09.541 So it turns out that, when you use the Try and the Accept keyword in Python, 3045 03:31:09.541 --> 03:31:13.291 generally speaking, we've tried to catch very specific exceptions, 3046 03:31:13.291 --> 03:31:14.291 like ValueError. 3047 03:31:14.291 --> 03:31:17.458 But technically, you could capture the parents or even 3048 03:31:17.458 --> 03:31:20.375 the grandparent exception for a given exception, 3049 03:31:20.375 --> 03:31:23.500 especially if you're not necessarily sure which one is going to get raised. 3050 03:31:23.500 --> 03:31:27.041 Or, better yet, there could be many exceptions that get raised, 3051 03:31:27.041 --> 03:31:28.958 but you want to handle them all the same, 3052 03:31:28.958 --> 03:31:31.000 and you don't want to necessarily enumerate them 3053 03:31:31.000 --> 03:31:33.041 in parentheses, separated by commas. 3054 03:31:33.041 --> 03:31:36.625 You want to say you want to handle all exceptions of a certain superclass 3055 03:31:36.625 --> 03:31:38.416 in much the same way. 3056 03:31:38.416 --> 03:31:40.833 So this has been latent this whole time, any time we've 3057 03:31:40.833 --> 03:31:45.166 seen or used or caught or, now, raised exceptions, and built into Python 3058 03:31:45.166 --> 03:31:46.041 is this hierarchy. 3059 03:31:46.041 --> 03:31:47.916 And if you were to invent your own exception, 3060 03:31:47.916 --> 03:31:50.250 generally, you wouldn't want to start from scratch. 3061 03:31:50.250 --> 03:31:54.041 You would want to descend from-- that is, subclass, one of these existing 3062 03:31:54.041 --> 03:31:58.958 exceptions and add your own twist on it, your own functionality as well. 3063 03:31:58.958 --> 03:32:02.208 Well, there's one final feature of object oriented programming 3064 03:32:02.208 --> 03:32:04.750 that we'd like to share with you today, and then it 3065 03:32:04.750 --> 03:32:08.291 will perhaps be quite the eye opener as to what you can really do now 3066 03:32:08.291 --> 03:32:10.208 that you have classes at your disposal. 3067 03:32:10.208 --> 03:32:12.791 And this, too, surprise, has been a feature 3068 03:32:12.791 --> 03:32:15.458 you and I have been taking for granted for weeks now. 3069 03:32:15.458 --> 03:32:18.250 This has just worked, but it's been implemented in a way 3070 03:32:18.250 --> 03:32:20.208 that you can now leverage yourself. 3071 03:32:20.208 --> 03:32:23.166 It turns out that Python, and some other languages, too, 3072 03:32:23.166 --> 03:32:27.041 support this notion of operator overloading, whereby 3073 03:32:27.041 --> 03:32:32.250 you can take very common symbols, like plus or minus or other such syntax 3074 03:32:32.250 --> 03:32:37.416 on the keyboard, and you can implement your own interpretation thereof. 3075 03:32:37.416 --> 03:32:40.791 Plus does not have to equal addition. 3076 03:32:40.791 --> 03:32:43.291 And minus does not have to equal subtraction. 3077 03:32:43.291 --> 03:32:45.708 And in fact, you and I have already seen another context 3078 03:32:45.708 --> 03:32:48.541 in which plus means something else. 3079 03:32:48.541 --> 03:32:52.750 Plus has not always, in Python, meant addition, per se. 3080 03:32:52.750 --> 03:32:56.458 What else has Python used plus for? 3081 03:32:56.458 --> 03:32:57.458 AUDIENCE: Concatenation? 3082 03:32:57.458 --> 03:32:58.875 DAVID J. MALAN: For concatenation. 3083 03:32:58.875 --> 03:33:02.875 For joining two strings, for adding to a list can you use plus as well. 3084 03:33:02.875 --> 03:33:07.666 So plus has actually been, funny enough, overloaded by the authors of Python 3085 03:33:07.666 --> 03:33:08.541 for us. 3086 03:33:08.541 --> 03:33:12.000 And so we can use the same symbol in much the same way as addition 3087 03:33:12.000 --> 03:33:15.250 but with different data types to solve slightly different problems. 3088 03:33:15.250 --> 03:33:18.250 Well, let me propose that we go back over to VS Code here, 3089 03:33:18.250 --> 03:33:22.250 and let me go ahead and create a new final file called vault.py. 3090 03:33:22.250 --> 03:33:24.000 So code of vault.py. 3091 03:33:24.000 --> 03:33:27.416 And let me propose that we implement the idea of a vault 3092 03:33:27.416 --> 03:33:30.541 at Gringotts, keeping on theme, wherein there's 3093 03:33:30.541 --> 03:33:32.291 a bank in the world of Harry Potter. 3094 03:33:32.291 --> 03:33:35.083 And within this bank, families and individuals 3095 03:33:35.083 --> 03:33:38.958 have vaults containing all sorts of money in the wizarding world. 3096 03:33:38.958 --> 03:33:41.583 And the type of money that exists in the world of Harry Potter 3097 03:33:41.583 --> 03:33:44.791 are coins called galleons and sickles and Knuts, 3098 03:33:44.791 --> 03:33:47.083 and those are in descending order of value. 3099 03:33:47.083 --> 03:33:49.750 And so inside of a vault might be a whole bunch of coins-- 3100 03:33:49.750 --> 03:33:53.000 gold, silver, and bronze, essentially, each in those denominations, 3101 03:33:53.000 --> 03:33:53.833 tucked away. 3102 03:33:53.833 --> 03:33:58.083 So how can I go about implementing, first of all, the idea of a vault 3103 03:33:58.083 --> 03:34:02.375 so that I can store, for instance, for Harry Potter, how much coinage 3104 03:34:02.375 --> 03:34:05.916 is in his family's vault, or for Ron Weasley the same? 3105 03:34:05.916 --> 03:34:08.291 Well, let me go ahead and vault.py and first 3106 03:34:08.291 --> 03:34:12.291 create a class called Vault, essentially meant to represent a bank vault. 3107 03:34:12.291 --> 03:34:15.000 Perfect, another real world, or fantasy world, 3108 03:34:15.000 --> 03:34:17.500 entity that I want to represent with code. 3109 03:34:17.500 --> 03:34:19.958 I could use a tuple or a list or a dictionary. 3110 03:34:19.958 --> 03:34:23.083 But again, I'm going to get a lot more functionality with classes, 3111 03:34:23.083 --> 03:34:26.125 and we'll see one final flourish with operators. 3112 03:34:26.125 --> 03:34:28.875 Inside of this vault class, let's go ahead and do this. 3113 03:34:28.875 --> 03:34:32.625 Let me define my init method, taking its first argument of self. 3114 03:34:32.625 --> 03:34:35.625 And let me define three arguments to this. 3115 03:34:35.625 --> 03:34:38.541 When you create a vault, in my code here, 3116 03:34:38.541 --> 03:34:41.750 I want to be able to initialize it with some number of galleons, some number 3117 03:34:41.750 --> 03:34:43.375 of sickles and, some number of Knuts. 3118 03:34:43.375 --> 03:34:46.791 I want the user, the programmer, to be able to pass in one or more 3119 03:34:46.791 --> 03:34:47.833 of those values ideally. 3120 03:34:47.833 --> 03:34:50.083 But they can be optional, so I'll give them defaults. 3121 03:34:50.083 --> 03:34:53.000 So let's go ahead and define a parameter called galleons, 3122 03:34:53.000 --> 03:34:57.041 whose default value will be 0; sickles, whose default value will also be 0; 3123 03:34:57.041 --> 03:35:00.083 and knuts, whose default value will be 0 as well. 3124 03:35:00.083 --> 03:35:04.541 So the programmer can pass in one or two or three or even none of those, 3125 03:35:04.541 --> 03:35:07.125 and they'll all have some implied defaults. 3126 03:35:07.125 --> 03:35:10.500 How do I want to remember those values that are passed in? 3127 03:35:10.500 --> 03:35:11.541 Well, let me do this. 3128 03:35:11.541 --> 03:35:14.583 self.galleons = galleons. 3129 03:35:14.583 --> 03:35:17.416 And self.sickles = sickles. 3130 03:35:17.416 --> 03:35:20.708 And self.knuts = knuts. 3131 03:35:20.708 --> 03:35:24.541 And so I could add some error checking, especially if you don't pass in. 3132 03:35:24.541 --> 03:35:27.708 A number I could turn these into properties to do even more validation. 3133 03:35:27.708 --> 03:35:30.791 But let's keep it simple and, as always, focus only on the new ideas. 3134 03:35:30.791 --> 03:35:33.291 So I'm just going to trust that these values were passed in, 3135 03:35:33.291 --> 03:35:36.708 and I'm going to immediately assign them to these instance variables. 3136 03:35:36.708 --> 03:35:38.416 What, now, do I want to do? 3137 03:35:38.416 --> 03:35:41.208 Well, let's come up with a way of printing out 3138 03:35:41.208 --> 03:35:44.000 what is in someone's vault, ultimately. 3139 03:35:44.000 --> 03:35:45.291 But first let's do this. 3140 03:35:45.291 --> 03:35:50.083 Let's create a vault for the Potters by creating, via assignment, a new vault. 3141 03:35:50.083 --> 03:35:55.125 And let's say that the potters have 100 galleons, 50 sickles, and 24 knuts. 3142 03:35:55.125 --> 03:35:58.541 And that's in that vault. And let's print out, for instance, potter. 3143 03:35:58.541 --> 03:36:00.916 All right, let's run this code and see how it works now. 3144 03:36:00.916 --> 03:36:05.083 Let me go ahead and run Python of vault.py, Enter. 3145 03:36:05.083 --> 03:36:06.083 Seems to work. 3146 03:36:06.083 --> 03:36:08.041 No syntax errors or anything else. 3147 03:36:08.041 --> 03:36:10.791 But this is not very enlightening. 3148 03:36:10.791 --> 03:36:15.291 How do I fix this, thinking back to what we've done before? 3149 03:36:15.291 --> 03:36:17.958 AUDIENCE: You have to use the __str. 3150 03:36:17.958 --> 03:36:18.958 DAVID J. MALAN: Exactly. 3151 03:36:18.958 --> 03:36:22.291 I need to use one of those special methods that comes with classes 3152 03:36:22.291 --> 03:36:26.625 and define for myself how I want a vault to be printed as a string. 3153 03:36:26.625 --> 03:36:27.916 So let me go ahead and do that. 3154 03:36:27.916 --> 03:36:32.541 Let me define the str method taking in self as its sole argument here. 3155 03:36:32.541 --> 03:36:34.500 And let's just return a very simple string 3156 03:36:34.500 --> 03:36:36.833 that just reveals what's in the vault. 3157 03:36:36.833 --> 03:36:39.958 So I'm going to return a formatted f string, 3158 03:36:39.958 --> 03:36:44.166 inside of which is self.galleons and then the word galleon, 3159 03:36:44.166 --> 03:36:45.875 so I know which those are. 3160 03:36:45.875 --> 03:36:49.458 Then let's do self.sickles, and let's output the word, sickles. 3161 03:36:49.458 --> 03:36:53.083 And then lastly let's output self.knuts, and then knuts here. 3162 03:36:53.083 --> 03:36:58.041 So I know, in this string, just how many of each of those coins 3163 03:36:58.041 --> 03:37:01.666 I have in this particular family's vault. All right, let me go ahead 3164 03:37:01.666 --> 03:37:06.166 and run Python of vault.py, changing nothing else except the str method. 3165 03:37:06.166 --> 03:37:11.625 And now, , we, see indeed that Harry has 100 galleons, 50 sickles, and 25 knuts. 3166 03:37:11.625 --> 03:37:13.541 All right, well, let's do one thing more here. 3167 03:37:13.541 --> 03:37:16.875 Below that, let's go ahead and define a Weasley variable. 3168 03:37:16.875 --> 03:37:20.583 And Ron never seemed to have quite as much money in the vault as did Harry. 3169 03:37:20.583 --> 03:37:24.541 So let's say that the Weasley vault will have 25, 50, and 100. 3170 03:37:24.541 --> 03:37:27.041 So I'll just reverse the order of those denominations, 3171 03:37:27.041 --> 03:37:29.541 rather than Harry's 100, 50, 25. 3172 03:37:29.541 --> 03:37:33.125 And now let me go ahead and print Weasley like this. 3173 03:37:33.125 --> 03:37:37.291 And let's go ahead and clear my terminal window, run Python of vault.py. 3174 03:37:37.291 --> 03:37:40.541 This time, that str method will be invoked twice, once 3175 03:37:40.541 --> 03:37:42.416 for each of those vault objects. 3176 03:37:42.416 --> 03:37:44.666 And we'll see, indeed, that the first one for Harry 3177 03:37:44.666 --> 03:37:48.125 has got 100, 50, and 25, respectively, versus Ron's 25, 50, 3178 03:37:48.125 --> 03:37:50.583 and 100, respectively. 3179 03:37:50.583 --> 03:37:52.458 But now let's do something interesting. 3180 03:37:52.458 --> 03:37:56.333 Suppose that you wanted to combine the contents of two vaults, 3181 03:37:56.333 --> 03:37:58.875 be it Harry's and Ron's or any other two people. 3182 03:37:58.875 --> 03:38:02.083 How would you go about doing this in code? 3183 03:38:02.083 --> 03:38:06.000 Well, if I wanted to combine the vaults for someone, I could do this. 3184 03:38:06.000 --> 03:38:08.333 Well, I could do galleons equals-- 3185 03:38:08.333 --> 03:38:13.958 let's do potter.galleons + weasley.galleons. 3186 03:38:13.958 --> 03:38:16.250 That gives me a variable called galleons that has 3187 03:38:16.250 --> 03:38:19.041 the sum of Harry and Ron's galleons. 3188 03:38:19.041 --> 03:38:25.333 Let's next do sickles = potter.sickles + weasley.sickles. 3189 03:38:25.333 --> 03:38:31.916 And then lastly, let's do knuts = potter.knuts + weasley.knuts. 3190 03:38:31.916 --> 03:38:32.958 I've got three variables. 3191 03:38:32.958 --> 03:38:34.666 What can I now do with these values? 3192 03:38:34.666 --> 03:38:37.833 Well, let's create a third-- a new vault. Total will 3193 03:38:37.833 --> 03:38:41.416 be the name of this variable equals a new vault, Capital V, notice. 3194 03:38:41.416 --> 03:38:43.916 And now, let's pass in those three new variables-- 3195 03:38:43.916 --> 03:38:47.000 galleons, sickles, and knuts. 3196 03:38:47.000 --> 03:38:49.625 And that's it, and let's print out this total vault. 3197 03:38:49.625 --> 03:38:52.750 So we should now see three vaults-- one for Harry, for Ron, 3198 03:38:52.750 --> 03:38:55.208 and the combination-- the addition of the two. 3199 03:38:55.208 --> 03:38:58.875 Let me go ahead and rerun Python of vault.py, and there we have it. 3200 03:38:58.875 --> 03:39:04.875 What was 100, 50, 25 and 25, 50, and 100, combined through addition now, 3201 03:39:04.875 --> 03:39:08.250 is 125, 100, 125. 3202 03:39:08.250 --> 03:39:10.541 So pretty straightforward, using techniques from weeks 3203 03:39:10.541 --> 03:39:14.541 ago, where we're just declaring a few new variables and doing some addition. 3204 03:39:14.541 --> 03:39:18.208 But wouldn't it be cool if I could do something like this? 3205 03:39:18.208 --> 03:39:20.541 Wouldn't it be cool if I could just somehow, 3206 03:39:20.541 --> 03:39:24.541 not manually create my own vault and do all of this annoying math up here-- 3207 03:39:24.541 --> 03:39:30.541 what if I could just do potter + weasley and get rid of all of this logic here? 3208 03:39:30.541 --> 03:39:34.750 Wouldn't it be nice if I overload the operator-- 3209 03:39:34.750 --> 03:39:37.666 we know as plus, just like str does, just 3210 03:39:37.666 --> 03:39:42.041 like list does-- to allow me to add two vaults together 3211 03:39:42.041 --> 03:39:43.875 on the left and the right. 3212 03:39:43.875 --> 03:39:48.250 Well, it turns out in Python and through operator overloading, 3213 03:39:48.250 --> 03:39:50.250 there is a way to do just this. 3214 03:39:50.250 --> 03:39:52.708 If you consult the documentation, there's 3215 03:39:52.708 --> 03:39:56.750 this and so many other special methods that come with classes. 3216 03:39:56.750 --> 03:39:59.916 The third one we'll see here is this one here-- 3217 03:39:59.916 --> 03:40:03.291 __add__. 3218 03:40:03.291 --> 03:40:05.458 And you'll see that it very generically is described 3219 03:40:05.458 --> 03:40:09.333 in the documentation is working for any object, be it a vault or str 3220 03:40:09.333 --> 03:40:10.791 or a list or something else. 3221 03:40:10.791 --> 03:40:13.458 By convention, it's going to take a first argument called self, 3222 03:40:13.458 --> 03:40:17.083 and then it's going to take some other argument, by convention, called other. 3223 03:40:17.083 --> 03:40:20.083 self, in effect, is going to be referring to whatever 3224 03:40:20.083 --> 03:40:22.250 object is on the left of a plus sign. 3225 03:40:22.250 --> 03:40:24.166 other is going to be referring to whatever 3226 03:40:24.166 --> 03:40:26.291 is on the right-hand side of a plus sign, 3227 03:40:26.291 --> 03:40:30.333 thereby giving us a way of describing, in code, the operand 3228 03:40:30.333 --> 03:40:35.416 on the left and the operand on the right of the operator, plus, in between. 3229 03:40:35.416 --> 03:40:37.958 That is to say, if I go back to VS Code here, 3230 03:40:37.958 --> 03:40:42.083 what I'm trying to do is implement support for this. 3231 03:40:42.083 --> 03:40:45.041 Well, let me try, without writing any other code just yet-- 3232 03:40:45.041 --> 03:40:47.541 Python of vault.py, Enter-- 3233 03:40:47.541 --> 03:40:53.541 TypeError: unsupported operand type(s) for +: 'Vault' and 'vault.' 3234 03:40:53.541 --> 03:40:56.750 That is to say Python, at this moment, does not know what 3235 03:40:56.750 --> 03:40:59.083 it means to add two vaults together. 3236 03:40:59.083 --> 03:41:00.500 You and I might have an instinct. 3237 03:41:00.500 --> 03:41:03.958 Probably want to combine the galleons and the sickles and the knuts 3238 03:41:03.958 --> 03:41:04.791 respectively. 3239 03:41:04.791 --> 03:41:05.958 Python doesn't know that. 3240 03:41:05.958 --> 03:41:08.291 It just knows that you have a new class called Vault. 3241 03:41:08.291 --> 03:41:10.250 But let's teach Python to do this. 3242 03:41:10.250 --> 03:41:12.166 Let me clear my terminal window. 3243 03:41:12.166 --> 03:41:15.208 Let me scroll back up to the class itself, where, at the moment, 3244 03:41:15.208 --> 03:41:18.250 I only have two special methods-- init and str. 3245 03:41:18.250 --> 03:41:20.458 But let's add this third. 3246 03:41:20.458 --> 03:41:26.625 Let me go into the class here and define __add__ and then specify its first 3247 03:41:26.625 --> 03:41:30.791 parameter as self, as before, and then a second parameter for this particular 3248 03:41:30.791 --> 03:41:32.583 method called, by convention, other. 3249 03:41:32.583 --> 03:41:34.833 Now, as always, I could name those parameters anything 3250 03:41:34.833 --> 03:41:37.000 I want, but I'm going to stick with convention here. 3251 03:41:37.000 --> 03:41:39.041 And now, inside of this method, am I going 3252 03:41:39.041 --> 03:41:42.375 to have to now add together the contents of two vaults? 3253 03:41:42.375 --> 03:41:43.333 Well, what two vaults? 3254 03:41:43.333 --> 03:41:46.291 Well, if we scroll down to our goal at hand, the goal, of course, 3255 03:41:46.291 --> 03:41:48.583 is to add this vault plus this other vault-- potter 3256 03:41:48.583 --> 03:41:50.208 plus weasley, respectively. 3257 03:41:50.208 --> 03:41:53.958 Well, it turns out, in Python, that, when you do overload an operator like 3258 03:41:53.958 --> 03:41:56.291 plus, what's going to happen automatically, 3259 03:41:56.291 --> 03:42:01.666 as soon as Python sees that, is it's going to call that __add__ method, 3260 03:42:01.666 --> 03:42:04.041 and it's going to pass into it to arguments-- 3261 03:42:04.041 --> 03:42:07.333 whatever the operand is on the left-- potter, in this case-- 3262 03:42:07.333 --> 03:42:10.666 and whatever the operand is on the right-- weasley, in this case. 3263 03:42:10.666 --> 03:42:15.500 And those values are going to get passed in as self and other, respectively. 3264 03:42:15.500 --> 03:42:18.375 What that means is that we can access their contents up here 3265 03:42:18.375 --> 03:42:20.458 in our implementation of add as follows. 3266 03:42:20.458 --> 03:42:24.125 Let me go ahead and define a local variable called galleons and set that 3267 03:42:24.125 --> 03:42:27.500 equal to, for instance, the sum of self.galleons-- 3268 03:42:27.500 --> 03:42:30.125 whatever's in Potter's vault in this case, 3269 03:42:30.125 --> 03:42:33.041 plus whatever is in Wesley's vault in this case, which 3270 03:42:33.041 --> 03:42:35.000 would be other.galleons. 3271 03:42:35.000 --> 03:42:36.708 Let me do the same for sickles. 3272 03:42:36.708 --> 03:42:40.125 self.sickles + other.sickles. 3273 03:42:40.125 --> 03:42:41.958 And let me lastly do that for knuts. 3274 03:42:41.958 --> 03:42:45.791 So self.knuts + other.knuts. 3275 03:42:45.791 --> 03:42:47.625 But at the end of the day, I'm going to need 3276 03:42:47.625 --> 03:42:51.500 to return a brand new bigger vault that contains all of those contents 3277 03:42:51.500 --> 03:42:52.250 together. 3278 03:42:52.250 --> 03:42:56.791 And if we ultimately want to assign that bigger vault to a variable like total 3279 03:42:56.791 --> 03:43:01.083 here, on the left, we'd better return a value from this add method. 3280 03:43:01.083 --> 03:43:04.791 So I'm going to go ahead and give myself a brand new vault, as by returning 3281 03:43:04.791 --> 03:43:08.916 capital Vault, which of course, is going to call my vault function into which 3282 03:43:08.916 --> 03:43:11.750 I can now pass some of those initialization arguments. 3283 03:43:11.750 --> 03:43:13.625 Well, how many galleon, sickles, and knuts do 3284 03:43:13.625 --> 03:43:15.541 I want this brand new vault to contain? 3285 03:43:15.541 --> 03:43:19.666 Well, I want it to contain this many galleons this many sickles, 3286 03:43:19.666 --> 03:43:21.291 and this many knuts. 3287 03:43:21.291 --> 03:43:23.708 So ultimately, what we're doing in this implementation 3288 03:43:23.708 --> 03:43:27.791 of add is adding together those galleons, sickles, and knuts, passing 3289 03:43:27.791 --> 03:43:30.958 them to the vault function so that we get a brand new bigger vault, 3290 03:43:30.958 --> 03:43:33.833 and return that altogether. 3291 03:43:33.833 --> 03:43:36.041 So now I've defined this new special method 3292 03:43:36.041 --> 03:43:42.250 called add that should now just make plus work for two vaults. 3293 03:43:42.250 --> 03:43:42.833 Let's see. 3294 03:43:42.833 --> 03:43:46.750 Let me run down to my terminal window, Python of vault.py and hit Enter. 3295 03:43:46.750 --> 03:43:51.958 And voila, and now we've implemented an overloaded operator, plus, 3296 03:43:51.958 --> 03:43:54.166 to do what you and I as humans would hope 3297 03:43:54.166 --> 03:43:56.458 would be the case when you add two vaults together. 3298 03:43:56.458 --> 03:43:58.916 But I've now written the code more specifically 3299 03:43:58.916 --> 03:44:04.500 to teach Python what it means concretely to add two vaults together. 3300 03:44:04.500 --> 03:44:06.708 And it's with very similar code in effect, 3301 03:44:06.708 --> 03:44:09.541 underneath the hood, that Python is doing this for two strings, 3302 03:44:09.541 --> 03:44:13.875 to concatenate them together, to joining two lists into a new list with list, 3303 03:44:13.875 --> 03:44:16.875 and so many other classes as well. 3304 03:44:16.875 --> 03:44:22.166 Any questions now on operator overloading or this example here. 3305 03:44:22.166 --> 03:44:23.875 AUDIENCE: How would you go about creating 3306 03:44:23.875 --> 03:44:31.833 a function for adding a student and a vault for two separate classes? 3307 03:44:31.833 --> 03:44:33.250 Would that be possible? 3308 03:44:33.250 --> 03:44:35.125 DAVID J. MALAN: Let me see what happens here. 3309 03:44:35.125 --> 03:44:36.041 I don't know offhand. 3310 03:44:36.041 --> 03:44:36.875 Let's do this. 3311 03:44:36.875 --> 03:44:38.541 Let's create a str and see what happens. 3312 03:44:38.541 --> 03:44:40.875 If I add Potter plus a str-- 3313 03:44:40.875 --> 03:44:41.500 str object. 3314 03:44:41.500 --> 03:44:42.666 Yeah, so it would work. 3315 03:44:42.666 --> 03:44:44.708 I'm just figuring this out as I go here, Eric. 3316 03:44:44.708 --> 03:44:47.000 So just to be clear, what I did was I just 3317 03:44:47.000 --> 03:44:48.958 changed weasley to str just to see what would 3318 03:44:48.958 --> 03:44:54.166 happen when I add a vault plus a str, and it will work, theoretically. 3319 03:44:54.166 --> 03:44:54.791 Why? 3320 03:44:54.791 --> 03:45:01.541 Because so long as the type of value on the left has an add method implemented, 3321 03:45:01.541 --> 03:45:04.291 other can be any type that you want. 3322 03:45:04.291 --> 03:45:06.583 You just have to decide and code what it's 3323 03:45:06.583 --> 03:45:09.625 going to mean conceptually to add a vault plus a string, which, 3324 03:45:09.625 --> 03:45:13.458 in this case, probably doesn't make any sense at all, but it's possible. 3325 03:45:13.458 --> 03:45:15.208 It's going to be the operand on the left. 3326 03:45:15.208 --> 03:45:16.250 And I'm inferring that. 3327 03:45:16.250 --> 03:45:17.958 I did not know the answer a moment ago. 3328 03:45:17.958 --> 03:45:22.916 I'm inferring that because what I got was an attribute error here on line 11 3329 03:45:22.916 --> 03:45:27.125 because Python did not like this. other.galleons didn't work, 3330 03:45:27.125 --> 03:45:29.666 but I could make it work by figuring something out. 3331 03:45:29.666 --> 03:45:30.791 Really good question. 3332 03:45:30.791 --> 03:45:32.291 Didn't know that one myself. 3333 03:45:32.291 --> 03:45:35.916 Other questions on operator overloading? 3334 03:45:35.916 --> 03:45:38.291 AUDIENCE: Can you define new operators in Python? 3335 03:45:38.291 --> 03:45:39.666 DAVID J. MALAN: I don't think so. 3336 03:45:39.666 --> 03:45:44.583 There is a very long but precise list of operators that you can overload. 3337 03:45:44.583 --> 03:45:48.500 I do not believe you can assign arbitrary characters 3338 03:45:48.500 --> 03:45:50.541 to be operators in Python. 3339 03:45:50.541 --> 03:45:52.458 Let me defer to Carter in the chat to-- 3340 03:45:52.458 --> 03:45:55.166 OK, I'm seeing two of my colleagues are saying, no, not possible. 3341 03:45:55.166 --> 03:45:57.166 So I'm going to go with my first instinct, no. 3342 03:45:57.166 --> 03:45:58.583 Otherwise, that'd be kind of cool. 3343 03:45:58.583 --> 03:46:00.875 You could make emoji do whatever you want to. 3344 03:46:00.875 --> 03:46:04.000 How about one final question on operator overloading? 3345 03:46:04.000 --> 03:46:08.416 AUDIENCE: Is that the only operation you can do as far as-- 3346 03:46:08.416 --> 03:46:10.041 can you do a subtraction as well? 3347 03:46:10.041 --> 03:46:11.041 DAVID J. MALAN: You can. 3348 03:46:11.041 --> 03:46:13.583 You can do so many others let me. 3349 03:46:13.583 --> 03:46:16.583 If, Carter, you don't mind pulling up this URL here-- 3350 03:46:16.583 --> 03:46:19.750 so this link here-- special method names and today's slides, 3351 03:46:19.750 --> 03:46:23.208 you'll see a long list of all of the operators that you can overload. 3352 03:46:23.208 --> 03:46:26.583 You can do less than, equals than, plus equals, minus equals. 3353 03:46:26.583 --> 03:46:29.291 Pretty much any symbol you've seen me type on the screen 3354 03:46:29.291 --> 03:46:33.166 can be overloaded in the context of classes. 3355 03:46:33.166 --> 03:46:35.541 So even though, today, we focused entirely 3356 03:46:35.541 --> 03:46:38.625 on object-oriented programming, this is a technique that we've been using, 3357 03:46:38.625 --> 03:46:41.791 really, since the first week of the class because those ints, 3358 03:46:41.791 --> 03:46:45.291 those strs, those floats, those lists, those dictionaries, 3359 03:46:45.291 --> 03:46:49.083 and so much more were already underneath the hood this whole time-- classes 3360 03:46:49.083 --> 03:46:50.125 and objects thereof. 3361 03:46:50.125 --> 03:46:52.041 But you now, as a programmer, have the ability 3362 03:46:52.041 --> 03:46:55.583 to create your own classes with your own instance or class variables, 3363 03:46:55.583 --> 03:46:58.875 with your own instance or class methods, with your own properties, 3364 03:46:58.875 --> 03:47:01.958 and even with your own custom behavior for operators. 3365 03:47:01.958 --> 03:47:04.625 So ultimately, you can absolutely continue 3366 03:47:04.625 --> 03:47:09.291 using those simple tuples or lists or those dictionaries or other structures 3367 03:47:09.291 --> 03:47:09.791 as well. 3368 03:47:09.791 --> 03:47:13.416 But object-oriented programming, and with it, classes and now these objects 3369 03:47:13.416 --> 03:47:15.208 is just another tool in your toolkit. 3370 03:47:15.208 --> 03:47:18.041 And daresay, as your code gets more sophisticated 3371 03:47:18.041 --> 03:47:20.041 and your problems get bigger, you'll find 3372 03:47:20.041 --> 03:47:23.708 that being able to model these real world or even fantasy world entities 3373 03:47:23.708 --> 03:47:26.458 with classes and related data and functionality 3374 03:47:26.458 --> 03:47:30.583 will ultimately just allow you to define code that's not just correct but ever 3375 03:47:30.583 --> 03:47:32.833 well-designed as well. 3376 03:47:32.833 --> 03:47:35.541 This was CS50. 3377 03:47:35.541 --> 03:47:41.000