15 hours 57 minutes 47 seconds
🇬🇧 English
Speaker 1
11:00:00
What I do like to be clear about using a dictionary is that it's allowing me just better semantics. Again, I don't have to remember, memorize, document that 0 is name, 1 is house. Instead, name is name, and house is house. It's just a little clearer, a little more expressive.
Speaker 1
11:00:17
So that's generally a good thing, especially if we stored more data about students than just their name and their house. If you had 3 fields, 4, 5, 10 different fields, no one's going to want to remember or be able to remember forever which is 0, which is 1, which is 2, and so forth, better to introduce names, like name and house in this case. But let me tighten this up further. Indeed, I'm typically in the habit of not introducing variables unnecessarily unless they make the code more readable.
Speaker 1
11:00:44
And an alternative way to format the same code would be this. Strictly speaking, I don't need to create an empty dictionary, then add 1 key to it, then add a second key to it, and then return that dictionary. I can actually consolidate this all into 1 statement, if you will. Let me go ahead and do this.
Speaker 1
11:01:02
Let me go ahead and say, name equals inputs return value, house equals inputs return value. And then instead of returning any variable named student, which I'm going to propose doesn't need to exist anymore, let me just create and return the dictionary all at once. Let me do quote, unquote, name in lowercase here. And then the variable, it's storing the user's name.
Speaker 1
11:01:23
Then quote, unquote, house as my second key, the value of which is going to be house the variable. Now, is this better? Maybe, maybe not. Maybe the first way was a little more readable, and that's totally fine to create variables if they improve the readability of your code.
Speaker 1
11:01:37
But just know that you can also create and return a dictionary on the fly like this, so to speak, all in 1 line. And I think it's arguably pretty reasonable in this case. Why? It's just pretty short.
Speaker 1
11:01:47
I probably wouldn't do this if it got longer and longer and longer. I might minimally then start moving my key value pairs to separate lines. But this would just be a slightly more compact way of doing this as well. But let me propose we do 1 more change.
Speaker 1
11:02:01
Let's go ahead and introduce that same special casing of Padma to fix her house, from Gryffindor, for instance, to Ravenclaw. How do we do this with dictionaries? Well, dictionaries, like lists, are mutable. You can change what is in them just like you can lists.
Speaker 1
11:02:17
How do you do that is just a little different syntactically. So let's go back into main and do this fix. If the student variable has a name key that equals equals Padma, then indented, go ahead and change the value of the house key inside of that student dictionary to be, quote unquote, Ravenclaw instead. So very similar in spirit to what we did with a list, but instead of using location 0 and 1, we're much more clearly, explicitly, semantically using, quote unquote, name and quote unquote, house.
Speaker 1
11:02:51
Because you index into lists and tuples using numbers, but you index into dictionaries using strings, as I've done here. All right, Let me go ahead and run Python of student.py. We'll again do Harry from Gryffindor, and I think all is well. Let me run it 1 more time, this time with Padma, who in the movies is from Gryffindor, but should really be from Ravenclaw.
Speaker 1
11:03:14
All right, Any questions, then, on this progression from tuples to lists to dictionaries? We haven't necessarily introduced anything new other than those tuples, which have been available to us all this time. But the goal at the moment is just to demonstrate this distinction among these different data types and how they each work a little bit differently.
Speaker 2
11:03:35
What if a combination of lists is there in a tuple? So is the list like we can change the list because tuple are immutable, but lists are mutable?
Speaker 1
11:03:48
Correct. You can change the contents of lists, and you can put most anything you want in them, other lists or strings, as I've done, integers or anything else. Tuples, you can do the exact same thing, but you cannot change them once you've created them. A dictionary is more like a list in that it is mutable.
Speaker 1
11:04:04
You can change it. But the way you index into a dictionary is by way of these keys, these strings, as we keep seeing, rather than by numbers, those numeric indices. All right. Well, let me propose that there is yet another way of solving this problem.
Speaker 1
11:04:22
And indeed, I would argue that there's now an opportunity to hand. Even though this program isn't particularly complicated, all I'm doing is collecting a name from the user and a house from the user, you could imagine wanting longer term to collect even more information, like the student's patronus or magical spell or a whole bunch of other information that might belong in a student. And right now, we're just kind of using these very general purpose data types in Python, a tuple to combine some values together, a list to do the same, but let us change it later, a dictionary, which is more powerful because it's a little more structured. It does have keys, and it has values, not just values.
Speaker 1
11:05:00
But you know what? We wouldn't have to be having this conversation if the authors of Python had just given us a data type called student. Wouldn't it have been nice if there were just a type of variable I could create in my code called student? Then we wouldn't have to figure out, well, do we use a tuple or a list or a dictionary?
Speaker 1
11:05:18
But that's pretty reasonable, right? You can imagine just how slippery of a slope that is, so to speak, if the creators of a language had to anticipate all the possible types of data that programmers like you and me want to store in your programs. So they just gave us these general purpose tools. But they gave us another general purpose tool that's going to allow us to create our own data types as well and actually give them names.
Speaker 1
11:05:42
And that terminology is a class. A class is kind of like a blueprint for pieces of data, objects, so to speak. A class is kind of like a mold that you can define and give a name. And when you use that mold or you use that blueprint, you get types of data that are designed exactly as you want.
Speaker 1
11:06:02
So in short, classes allow you to invent your own data types in Python and give them a name. And this is a primary feature of object-oriented programming to be able to create your own objects in this way, and in the case of Python and classes, even give them some custom names. So what does this mean in real terms? Well, let me go ahead and come back to VS Code here.
Speaker 1
11:06:24
And let me propose that we introduce a little bit of new syntax. I'm going to go ahead and clear my terminal window first. I'm going to go to the top of my file, and I'm just going to start a thought but not finish it yet. I'm going to use this new keyword for classes called literally class.
Speaker 1
11:06:40
So indeed, the new keyword we're going to have here, and if I go back to our slides here, this would be the official URL where you can read up more on this particular feature of Python. In the official tutorial, class is a new keyword we can use. Now, this is coincidentally related to students, because students take classes. But it has nothing to do with the fact that we're dealing with students.
Speaker 1
11:07:00
Class is a general purpose term in a lot of languages, Python among them, that allow you to define these custom containers with custom names for pieces of data. So let's go back to VS Code. Let's use this new keyword. And let me propose that we create a class called Students.
Speaker 1
11:07:16
And by convention, I'm going to use a capital S here. And I'm going to go ahead and, with a colon, get to later the implementation of this class. So I'm just going to use dot, dot, dot, which is a valid placeholder for now. That just indicates to me that I'm going to come back to implementing this later.
Speaker 1
11:07:30
But as of now, it does, in fact, exist. I now have a student class defined for me that I can now use in my code here. How am I going to use it? Well, first of all, let me go down to get student.
Speaker 1
11:07:45
And let me change this code to no longer use a dictionary, but to use this class. I'm going to do this. I'm going to give myself a variable called student, as I've done before. But I'm going to set it equal to capital student, open parenthesis, close parenthesis.
Speaker 1
11:08:00
So I'm going to do what appears to be calling a function. And that function, student with a capital S, notice, matches the name that I gave this class at the top of my file. All right, what do I next want to do? I'm going to go ahead and give this student a name.
Speaker 1
11:08:15
Now, if I were still using a dictionary, I would say student quote unquote name using square brackets. But this is not a dictionary. It turns out classes have what, for now, we'll call attributes, properties of sorts that allow you to specify values inside of them. And the syntax for that happens to be a dot.
Speaker 1
11:08:33
We've seen dots before. We've used it in the context of modules and libraries more generally. This is another similar in spirit use of a dot that allows you to get at something inside of something else. So student.name is going to be the syntax I use for giving this student a name.
Speaker 1
11:08:49
And that name is going to be whatever the return value of name is. And then I'm going to go ahead and say student.house to give another attribute called house and give that the return value of input here, prompting the user for house. And then as before, I'm just going to return student. But now what's really powerful about class and object-oriented programming more generally is that I've created this custom data type called literally student, capital S.
Speaker 1
11:09:16
I've stored 1 such student in a variable, like I can always do in a variable called student, lowercase s. But I could call it anything I want. It just makes sense to call it student as well, but lowercase for clarity. And then I'm returning that variable.
Speaker 1
11:09:30
And because of my syntax in lines 14 and 15, that has the result of putting inside of that class a name attribute and a house attribute. I just need to make 1 more change up here. I'm going to go ahead and remove our Padma code, just so we can focus only on what's new, rather than fixing her house. And I'm going to go in here and change the syntax that previously was for dictionaries.
Speaker 1
11:09:53
Again, dictionaries use square brackets and then strings in quotes, either single quotes or double quotes, depending on the context. Here, though, I'm going to change this to be student.name. And over here, I'm going to change it to be student.house. And that's just going to be my new syntax for getting the contents of what appears to be a class called student.
Speaker 1
11:10:15
Let me go ahead and rerun Python of student.py, enter. Let's type in Harry's name as before. Let's put him in Gryffindor, crossing our fingers as we often do. And Harry is indeed from Gryffindor.
Speaker 1
11:10:28
What, though, have I done? Let's introduce 1 other bit of terminology here. It turns out that I can create a class using that class keyword. But any time you use a class, you're creating what are called objects.
Speaker 1
11:10:41
And here is the word objects as in object-oriented programming or OOP. Let me go back to my code here. And even though I haven't really implemented much of it at all, I literally just left it with a dot, dot, dot, that's enough code, lines 1 and 2, to just invent a new data type called student, capital S, that may or may not have some future functionality as well. That's enough to create a class.
Speaker 1
11:11:03
What, though, am I doing on line 11? On line 11, what I'm technically doing is creating an object of that class. So this, too, is another term of art. You create objects from classes.
Speaker 1
11:11:16
So if we go back to that metaphor that a class is like a blueprint for a house, or a class is like a mold, an object is when you use that blueprint to build a specific house, or something that comes out of in plaster, the mold, when you actually use that mold to create such an object. So a class is, again, the definition of a new data type. The object is the incarnation of, or technically, instantiation of. And another term for objects would actually be an instance.
Speaker 1
11:11:45
You have instances of classes as well. So that's a lot of vocabulary. But at the end of the day, it just boils down to this. You can define your own class, which is really your own data type.
Speaker 1
11:11:54
You can then store attributes inside of it using this dot notation here. And then you can access those same attributes using code like this here. And now I have a proper student data type. And I don't have to kind of hack something together using a tuple, or a list, or even a dictionary.
Speaker 1
11:12:10
I now have a proper data type called student that the authors of Python didn't give me. I gave myself. Any questions now on classes, this new keyword class, or this idea of these objects or instances thereof?
Speaker 2
11:12:25
Is the class object mutable or immutable?
Speaker 1
11:12:30
A good question. And we've clearly laid the stage for having that conversation about every data type now. We will see that they are mutable.
Speaker 1
11:12:37
But you can make them immutable. So you can get the best of both worlds now by writing some actual code. And we'll write more code than the dot, dot, dot in just a bit. Other questions on classes or these objects thereof?
Speaker 2
11:12:49
Then what would be the properties of those classes?
Speaker 1
11:12:53
So at the moment, the properties of or the attributes of, as I've been calling them thus far, would just be name and house. It turns out that there may very well be other attributes built into classes that we may see before long. But for now, the only 2 attributes that I care about are the ones that I myself created, namely, name and house, or again, what I would call attributes.
Speaker 1
11:13:14
And in a little bit, we're going to start calling those same attributes, more technically, instance variables. Name and house, as I presented them here in VS Code, are really just variables called name and called house inside of an object whose type is student. All right, so what more can we do with these classes? Well, again, on line 11 is where we're instantiating an object of the student class and assigning it to a student variable.
Speaker 1
11:13:42
We're then adding attributes name and house, respectively, on lines 12 and 13 currently. Both of those have values that are technically strings or strs, because that's what the return value of input is. But those attributes values could actually be any data type. We're just keeping things simple and focusing on defining students in terms of 2 strings, name and house.
Speaker 1
11:14:01
And then on line 14, we're returning that variable. We're returning that object to main so that we can actually print out who is from what house. Well, let's go ahead and add a bit more functionality here, because right now on lines 12 and 13, this is a little manual. And it's a little reckless of me to just be putting anything I want inside of this student object.
Speaker 1
11:14:22
It turns out with classes, unlike with dictionaries, we can actually standardize all the more what those attributes can be and what kinds of values you can set them to. So let me go ahead and do this. Let me propose that it would actually be really nice if instead of doing this here, let me go ahead and simplify my code as follows. Let me go ahead and give myself a local variable called name and set it equal to the return value of input, like we've done many times now already.
Speaker 1
11:14:49
Let me give myself 1 other variable for now called house and set it equal to the return value of input as well, prompting the user for their house. And now, instead of creating a student object from my student class and then manually putting the name attribute inside of it and the house attribute inside of it, let me actually do something more powerful. Let me do this. Let me call that student function, which is identical to the class name.
Speaker 1
11:15:17
Just by defining a class, you get a function whose name is identical to the class name with the capital letter included. But instead of just doing open parenthesis, close parenthesis, let me pass in the name that I want to fill this object with and the house that I want to put in that object as well. And now let me set the return value as before to be student equals like this. So what have I done that's different?
Speaker 1
11:15:42
Fundamentally, I'm still getting user input in the same way. I'm using input on line 11 and input on line 12. And I just so happen to be storing those return values in local variables. But now, and now we're setting the stage for the more powerful features of classes and object-oriented programming more generally, notice that I'm deliberately passing to this capital S student function, name comma house.
Speaker 1
11:16:05
I'm passing in arguments to the function. Now, the student class is not going to know what to do with those yet. But now I'm sort of standardizing how I'm passing data into this student class. And ultimately, it's going to give me an opportunity to error check those inputs, to make sure that the name is valid, that it has a value, and it's not just the user hitting Enter.
Speaker 1
11:16:24
It's going to allow me to ensure that it's a valid house, that it's Gryffindor or Hufflepuff or Ravenclaw or Slytherin, or not just hitting Enter or some random value that the user types in, because I'm passing name and house to the student class, this particular function, I'm going to have more control over the correctness of my data. So let's now go up to the student class, which up until now I left as just dot, dot, dot. It turns out that in the context of classes, there are a number of not just attributes or instance variables that you can put inside, but also methods. Classes come with certain methods or functions inside of them that you can define, and they just behave in a special way by nature of how Python works.
Speaker 1
11:17:09
These functions allow you to determine behavior in a standard way. They are indeed special methods in that sense. Now, what do I mean by this? Well, let me go back to VS Code here.
Speaker 1
11:17:20
And let me propose that I start to define a standard function called underscore, underscore, or dunder, as it's abbreviated, init, underscore, underscore. And then I'm going to go ahead and do open parenthesis. And then I'm going to put in here literally the word self. More on that in just a moment.
Speaker 1
11:17:40
But now inside of this function, I'm going to have an opportunity to customize this class's objects. That is to say, this underscore underscore init method, or dunder init method, is specifically known as an instance method. And it's called exactly this. This is designed by the authors of Python.
Speaker 1
11:18:00
And if you want to initialize the contents of an object from a class, you define this method. And we'll see what it's about to do here. Let me go back to VS Code, and let me do something like this. Self.name equals name, and self.house equals house.
Speaker 1
11:18:19
But I don't want to just init this object very generically. I want this method called init to take in not just self, but name, house as well. Now, what in the world is going on? Because there's a lot of weird syntax here.
Speaker 1
11:18:33
There is this dunder init method, double underscore, init, double underscore. There's all of a sudden this parameter called self. And then there's this new syntax, self.name and self.house. Now you're seeing really a manifestation of object-oriented programming.
Speaker 1
11:18:48
It's not all that different fundamentally from what we've been doing for weeks with dictionaries by adding keys to dictionaries. But in this case, we're adding variables to objects, a.k.a. Instance variables to objects. Now, what's going on?
Speaker 1
11:19:03
Let's do this in reverse. Let's go back to the line of code we wrote earlier. On line 15, I am treating the name of this class, Student, with a capital S, as a function. And I am passing in 2 values, name and house.
Speaker 1
11:19:17
What I've highlighted here on the screen on line 15 is generally known as a constructor call. This is a line of code that is going to construct a student object for me. Using synonyms, it is going to instantiate a student object for me. And again, how is it going to create that object?
Speaker 1
11:19:37
It's going to use the student class as a template, as a mold of sorts, so that every student is structured the same. Every student is going to have a name. Every student is going to have a house. But because I can pass in arguments to this student function, capital S, I'm going to be able to customize the contents of that object.
Speaker 1
11:19:58
So if you think about the real world, if you've ever been on a street or a neighborhood, where all of the houses kind of look the same, but they might be painted differently. They might be decorated a little bit differently on the outside. All of those houses might have been built using the exact same blueprint, sort of a mold, if you will. But then you can specialize exactly the finer points of those houses by painting the outside a different color or planting different trees.
Speaker 1
11:20:22
You can style them differently. Similar in spirit here, we have a student blueprint that's always going to have now a name and a house, but it's up to you and me to pass in any name and any house that we want. Now, where is this function? The fact that I'm calling student, capital S, and then a parenthesis and a close parenthesis with arguments inside suggests that there's a function somewhere in the world that has been defined with def that's going to be called?
Speaker 1
11:20:49
Well, as you might have guessed by now, the function that will always be called by definition of how Python classes work is a function called double underscore init double underscore. Why? It's a crazy name, but it's what the authors of Python chose to just implement the initialization of an object in Python. Now, the only weird thing, especially weird thing, I will admit, is this.
Speaker 1
11:21:15
It would be way clearer to me, too, if the only 2 parameters for init were just name, comma, house. That's how we've defined every function thus far in the class. You just specify the parameters that you want the function to accept. And indeed, that lines up with what I'm doing on line 15.
Speaker 1
11:21:32
I am only passing in 2 things to the student function. But it turns out that the authors of Python need to give us a little bit of help here. Because suppose that you pass in name and house to this init method, and a method is just a function inside of a class, what are you going to do with the name and the house? Like, literally, where are you going to put them?
Speaker 1
11:21:54
If you want to remember the name and the house for this student, you've got to be able to store those values somewhere. And how do you store them in the current object that has just been instantiated? Well, the authors of Python decided that the convention is going to be that this init method also semi-secretly takes a third argument that has to come first. By convention, it's called self.
Speaker 1
11:22:17
But you could call it technically anything you want. But the convention is to always call it self. And self, as its name implies, gives you access to the current object that was just created. What does that mean?
Speaker 1
11:22:30
Again, now on line 14, now that it's moved down a little bit, this line here is a constructor. It constructs a student object. But there's nothing in that object initially. There's no name.
Speaker 1
11:22:40
There's no house. But the object exists in the computer's memory. It's up to now you to store the name and the house inside of that object. How do you do that?
Speaker 1
11:22:49
Well, Python will just automatically call this init method for you. And it's going to automatically pass in a reference to an argument that represents the current object that it just constructed in memory for you. And it's up to you to populate it with values. And what this means is that inside of your init method, you can literally do self.name to create a new attribute, a.k.a.
Speaker 1
11:23:14
An instance variable, inside of that otherwise empty object and put this name inside of it. It allows you to do self.house and store that value of house. Now, you could call these things anything you want. They could be n.
Speaker 1
11:23:26
They could be h as before. But that's really not very self-explanatory. Much better to do this kind of convention, self.name equals name, self.house equals house. And this is like installing into the otherwise empty object the value name and house and storing them in really identically named instance variables in the object.
Speaker 1
11:23:49
And again, an object is just an instance of a class. Now, I know that was a lot of vocabulary. That's a lot of weird syntax. So any questions on this init method, whose purpose in life, again, is to initialize an otherwise empty object when you first create it.
Speaker 2
11:24:04
AUDIENCE 2 What is the difference between the init method and the pod constructor? DAVID MALAN
Speaker 1
11:24:09
A good question. So in other languages, if you program before, for instance, Java, there are functions that are explicitly called constructors that, indeed, construct an object. They initialize it with values.
Speaker 1
11:24:21
Python technically calls this init method the initialization method. It initializes the value. It's on line 15 now of my code. If I scroll back down, that I'm technically constructing the object.
Speaker 1
11:24:34
It turns out there's another special method in Python that we won't talk about in detail today called underscore, underscore, new, underscore, underscore, that actually handles the process of creating an empty object in memory for us. But generally speaking, you, the programmer, don't need to manipulate the new function. It just works for you. Instead, you define your own init method here, an init function inside of your class.
Speaker 1
11:24:57
And that method initializes the contents of the object. So there's technically a distinction between constructing the object with new and initializing it within it. But in the world of Python, you pretty much only worry about the init method. Python generally does the other part for you.
Speaker 1
11:25:13
A good question. Others?
Speaker 2
11:25:15
AUDIENCE 2 What about if you want to store more than 1 name or more than 1 house?
Speaker 1
11:25:20
A good question. If you want to store more than 1 name or more than 1 house, you can do this in different ways. You could create other attributes, technically called instance variables, like self dot name 1, self dot name 2.
Speaker 1
11:25:33
But we've seen in the past that that is not a very good design, just to have multiple variables to store multiple things. Maybe instead, you have an instance variable called self.names, plural, and you set it equal to a list of names or a list of houses. Now, in this case, I don't think that really solves a problem, because I'm trying to implement a student singular. So it doesn't really make sense to have multiple first names, maybe a nickname, maybe a last name.
Speaker 1
11:25:57
So we could add those 2. But I don't think we need multiple names per se, and in this case, multiple houses. But absolutely, you could do that using some of our familiar building blocks, like lists. Other questions?
Speaker 2
11:26:08
AUDIENCE 1 How are classes or objects represented in memory? DAVID MALAN
Speaker 1
11:26:11
How are classes and objects represented in memory? So the class is technically just code. It is the code on the top of my file, lines 1 through 4, that defines that blueprint, that template, if you will.
Speaker 1
11:26:24
Objects are stored in the computer's memory by taking up some number of bytes. So you're probably familiar with bytes or kilobytes or megabytes. There's some chunk of bytes, probably all in the same location in the computer's memory or RAM, where those objects are stored. But that's what Python the program handles for you.
Speaker 1
11:26:43
Python the interpreter figures out where in the computer's memory to put it. You and I, the programmers, get to think and solve problems at this level. Python, the interpreter, handles those lower level details for you. How about 1 final question on classes and objects?
Speaker 2
11:26:56
Now, my question is, if we can do the same thing with the dictionary, so I have to use classes. DAVID MALAN
Speaker 1
11:27:03
ANDERSON Good question. If you can do the same things as you can with dictionaries, why should you use classes? Because we are just scratching the surface now of what you can do with classes.
Speaker 1
11:27:11
Allow me to go back now to my keyboard and show you more of what you can do with classes. But in short, you can do much more with classes. You can ensure the correctness of your data much more with classes. You can error check things.
Speaker 1
11:27:24
And generally, you can design more complicated software more effectively. And we'll continue to see today features of Python and object-oriented programming more generally that allows us to do just that. So let me propose, in fact, that first, let's just tighten up this current implementation, which again, has us with an init method that just declares 2 instance variables, self.name and self.house, which, again, just creates those variables inside of the otherwise empty object and assigns them values, name and house, respectively. Let me go ahead and just do 1 little thing here.
Speaker 1
11:27:57
I don't really need this student variable. Let me just tighten this up so that each time we improve or change the code, we're focusing really on just the minimal changes alone. So I've not fundamentally done anything different. I just got rid of the variable name, and I'm just returning the return value of this student function that's constructing my new object for me.
Speaker 1
11:28:16
So I'm just tightening things up, as we've done many times in the past. Well, what if something goes wrong when creating this student? For instance, what if the user does not give us a name, and they just hit Enter when prompted for name? Like, I don't want to put in my computer's memory a sort of bogus student object that has no name.
Speaker 1
11:28:36
I'd ideally like to check for errors before I even create it so I don't create a nameless student. It would just be weird and probably a bug to have an object that has no name. Similarly, I don't want the user to be able to type in something random as their house. At least in the world of Harry Potter, there's really only 4 houses, at Hogwarts at least.
Speaker 1
11:28:55
Or there's, again, Gryffindor and Hufflepuff and Ravenclaw and Slytherin, a list of 4 valid houses. It would be nice if I somehow validated that the user's input is indeed in that list. Now, I could do all of that validation in my getStudent function. I could check, is the name empty?
Speaker 1
11:29:13
If so, don't create the student object. Is the house 1 of those 4 houses? If not, don't create the student object. But that would be rather decoupled from the student itself, right?
Speaker 1
11:29:24
GetStudent currently exists as just my own function in my student.py file. But classes, and really object-oriented programming more generally, encourages you to encapsulate inside of a class all functionality related to that class. So if you want to validate that a name exists, if you want to validate that a house is correct, that belongs just fundamentally in the class called student itself, not in some random function that you wrote elsewhere. Again, this is just methodology.
Speaker 1
11:29:55
Because again, if we think about writing code that gets longer and longer, more and more complicated, it should make just intuitive sense that if you keep all of the name and all of the house-related code in the student, it's just better organization. Keep all of the related code together, and that's probably going to set you up for more success. And Indeed, that's part of this methodology of object-oriented programming. Let me go ahead now and change my students classes init method to do this.
Speaker 1
11:30:25
If the name is blank, so if not name. And we've seen this kind of syntax before. If you say in Python, Pythonically, if not name, that's like doing something like this. If name equals equals quote unquote.
Speaker 1
11:30:39
But I can do this a little more elegantly. Just say if not name would be the more Pythonic way to do it. Well, I want to return an error. Like I might want to do something like this, print missing name.
Speaker 1
11:30:50
But this is not good enough. It does not suffice to just print out missing name and then let the rest of the code go through. All right, well, what could I do instead? In the past, We've seen another technique.
Speaker 1
11:31:01
I could do sys.exit, and I could say something like missing name. And I could go up here, and I could import sys. But this is a really obnoxious solution to the problem. Just because you or maybe a colleague messed up and called a function with an invalid name, you're going to quit my whole program.
Speaker 1
11:31:17
Like, that's really, really extreme of a response. And you probably don't want to do that if your program's in the middle of running. You might want to clean some stuff up. You might want to save files.
Speaker 1
11:31:27
You don't want to just exit a program sometimes in some arbitrary line just because input was invalid. So I don't think we want to do that either. But we do now have a mechanism for signaling errors. Unfortunately, I can't do something like this.
Speaker 1
11:31:41
I could try returning none and say, uh-uh, this student does not exist. I'm going to hand you back none instead. But it's too late. If we scroll back down to where I'm creating the student, it's on line 17 now, where I've highlighted this code.
Speaker 1
11:31:54
The student has already been created. There is an object somewhere in the computer's memory that's structured as a student. It just doesn't have any values inside of it. But it's too late, therefore, to return none.
Speaker 1
11:32:07
That ship has sailed. The object exists. You can't just suddenly say, nope, nope, there is no object. There is an object.
Speaker 1
11:32:13
It's up to you to signal an error. And how do you signal an error? Well, we've actually seen this before, but we haven't had occasion to create our own errors. It turns out in Python, there's another keyword related to exceptions that Python itself uses to raise all of those exceptions we've talked about in the past when you've caught things like value errors or other such exceptions that come with Python, well, it turns out you, the programmer, can raise, that is, create your own exceptions when something just really goes wrong.
Speaker 1
11:32:44
Not wrong enough that you want to quit and exit the whole program, but enough that you need to somehow alert the programmer that there has been an error, something exceptional in a very bad way. Something exceptional has happened. And let them try to catch that exception as needed. So let me go back to VS Code here and propose that if the user passes in an invalid name, it's just empty.
Speaker 1
11:33:08
So there's not a name. Well, what I really want to do is this. I want to raise a value error. And we've seen the value errors before.
Speaker 1
11:33:16
We've created value errors accidentally before. And generally, you and I have tried to catch them if they happen. Well, the flip side of this feature of exceptions in a language like Python is that you, the programmer, can also raise exceptions when something exceptional happens. And you can even be more precise.
Speaker 1
11:33:33
You don't have to raise a generic value error and let the programmer figure out what went wrong. You can treat value error and all exceptions in Python like functions and actually pass to them an explanatory message like, quote unquote, missing name. So at least the programmer, when they encounter this error, knows, oh, I messed up. I didn't make sure that the user has a name.
Speaker 1
11:33:54
And now, what do you want to do instead? Well, now, if you're the programmer, you could do something like this. You could try to create a student, except if there's a value error, then you could handle it in some way. And I'm going to wave my hand with a dot, dot, dot at how you would handle it.
Speaker 1
11:34:12
But you would handle it using try and accept, just like we have in the past. And that would allow you, the programmer, to try to create the student. But if something goes wrong, OK, OK, I'll handle it nonetheless. So what's new here, again, is this raise keyword that just lets you and I actually raise our own exceptions to signal these errors.
Speaker 1
11:34:32
Well, let me go back to my code here. And I'm just going to go ahead and not bother trying or catching this error. For now, we'll just focus on raising it and assume that from our week on exceptions, you could add try and accept as needed in places. Let me go back to the code here and propose that something else could go wrong with house.
Speaker 1
11:34:50
If there is a name, we're good. But if we're given a house but it's invalid, we should probably raise an exception for that too. So what if we do this? If house is not in the list containing Gryffindor, quote unquote, Hufflepuff, quote unquote, let's see, Ravenclaw, quote unquote, or Slytherin, quote unquote, then with my colon, let's raise another type of value error.
Speaker 1
11:35:15
But rather than raise a generic value error, let's pass in an argument, quote unquote, invalid house. And so here we now see a capability that we can do with classes that we can't with dictionaries. If you add an attribute to a dictionary, a key to a dictionary, it's going in no matter what. Even if the name is empty, even if the house is a completely random string of text that's not 1 of these 4 houses, it's going into that dictionary.
Speaker 1
11:35:41
But with a class and by way of this init method, you and I can now control exactly what's going to be installed, if you will, inside of this object. You have a little more control now over correctness. And so now, let me go ahead and scroll back down to my terminal window and clear it. Let me run python of student.py.
Speaker 1
11:36:01
Let me type in something like Harry, let me type in Gryffindor, Enter, and we see that indeed Harry is from Gryffindor. What if I made a mistake, though? What if I ran Python of student.py and typed Harry as the name, but this time typed in number 4 Privet drive, which is where he grew up. Instead of his proper Hogwarts house, let me hit Enter now.
Speaker 1
11:36:20
And now you see a value error. But this isn't 1 that Python generated for us, per se. I raised this error. And therefore, if I went in and wrote more code in my get student function, I could also catch this error with our usual try, except syntax.
Speaker 1
11:36:36
So all we have now is not just classes in our toolkit, but even more powers when it comes to exceptions, and not just catching them ourselves, but raising them ourselves too. Any questions now on this use of classes and init, and now this ability to raise exceptions when something goes wrong inside of the initialization?
Speaker 2
11:36:57
So what if the user has a middle name, name, middle name, and last name. How would you fix that? DAVID MALAN ANDERSON
Speaker 1
11:37:04
Oh, good question. If you wanted the student to have a first name, middle name, and last name, we could do this in a bunch of different ways. The simplest, though, if let me clear my screen here, and let me just temporarily do this.
Speaker 1
11:37:17
Let me propose that the in it method taken a first argument, a middle argument and a last argument. And then what I think I would do down here is ultimately have first equals first and then I would do the same thing for middle and last. So middle and middle, and then last and last. And then what I would have to do here is when I actually ask the user for their name, I might need to really go all out.
Speaker 1
11:37:44
I might need to ask them first for their first name and store that in a variable called first and therefore pass in first. I might similarly need to ask them for their middle name and store that in a variable and then pass in a second argument middle. And then lastly, if you will, let me go ahead and create a third variable called last, get the input for their last name, and pass that in as well. I could instead just use 1 input and just ask them for their whole name.
Speaker 1
11:38:09
So type in David Malan, Enter, or David J. Malan, all 3 of them. And maybe I could use Python's split function, maybe a regular expression to tease it apart. That's probably going to be messy, because there's going to be people who don't have just 2 or 3 names.
Speaker 1
11:38:23
They might have 4 or 5. So maybe sometimes it's better to have multiple prompts. But that's not a problem, because with a class, We have the expressiveness to take in more arguments if we want. We could even take a list if we wanted.
Speaker 1
11:38:35
But I think we probably want to have even more error checking then not just for name, but for first and then maybe for middle and then maybe for last. So it just is more and more code, though there would be ways to perhaps consolidate that, too. Let me undo all of that and see if there are other questions now on classes.
Speaker 2
11:38:54
I assume this is classes or something I might do at the beginning of a project. Can I just put them in a different file and import them into my project or my main code as needed?
Speaker 1
11:39:04
Absolutely. A really good question. You could imagine wanting to use this student class, not just in student.py, but in other files or other projects of yours. And absolutely, you can create your own library of classes by putting the student class in your own module or package, per our discussion in the past about libraries more generally.
Speaker 1
11:39:22
And absolutely, you can do that. And later today, well, we see that we've actually been using classes, you and I, before in third party libraries. So you, too, can absolutely do the same. How about 1 more question on classes?
Speaker 3
11:39:35
Can you have optional variables in classes? And 2, can you have your own error names? Like, let's be egotistical and say, I want to raise Eric error.
Speaker 1
11:39:45
Short answer, yes. So you can, these init functions are just like Python functions more generally, even though they're special in that they're going to get called automatically by Python for you. But if you wanted to make house optional, you could do something like this.
Speaker 1
11:39:58
You could give it a default value in the init functions signature, so to speak, in that first line of code on line 2. And that would allow me to not have to pass in house. In this case, I'm going to continue to always pass in name and house, but you could make things optional. And yes, to your second question, if you wanted to have your own error message, like an Eric error, you could actually create your own Eric error exception.
Speaker 1
11:40:22
And we'll see in a little bit that there's actually a whole suite of exceptions that exist. And you, too, can invent those as well. Let me propose, though, that we now introduce 1 other aspect of this whereby we try printing out what a student looks like. At the moment, if I scroll back down to my main function, I'm still printing the student's name and house very manually.
Speaker 1
11:40:43
I'm going inside of the object, doing student.name, And I'm going inside of the object again and getting student.house, just to see where the student is from. But wouldn't it be nice if I could just print the student, like I've been printing for weeks, any int or float or str or any other data type? Well, let's see what happens if I just try printing the student instead of manually going inside and trying to create that sentence myself. Well, in my terminal window, let me go ahead and run Python of student.py again.
Speaker 1
11:41:11
Let me type in Harry. Let me type in Gryffindor. And voila, Harry. Whoa.
Speaker 1
11:41:16
OK, main student object at 0x102733E80. Well, what is going on? Well, if you were to run the same code, you might actually see something different on your computer in terms of that number. But what you're really seeing is the underlying representation as a string of this specific object.
Speaker 1
11:41:35
In particular, you're seeing where in the computer's memory it is. This number 0x102733E80 refers to, essentially, a specific location in the computer's memory or RAM. That's not really that interesting for me or you or, generally speaking, programmers. But it's just the default way of describing, via print, what this thing is.
Speaker 1
11:41:57
But I can override this as well. It turns out that there are other special methods in Python when it comes to classes, not just underscore, underscore, init, underscore, underscore, but continuing in that same pattern, underscore, underscore, str, underscore, underscore. So this, too, is a special method that if you define it inside of your class, Python will just automatically call this function for you any time some other function wants to see your object as a string. Print wants to see your object as a string.
Speaker 1
11:42:31
But by default, if you don't have this method defined in your class, it's going to print out that very ugly esoteric incarnation thereof, where it says main dot student object at ox dot dot dot. Well, how can I then define my own str function? Well, here back in VS Code, let me propose that I go in and define not just underscore, underscore, init, but let me define a second function in this class here as follows. Def underscore, underscore, str, underscore, underscore.
Speaker 1
11:43:00
There are indeed 2. Even though the font in VS Code is putting the 2 underscore so close, it just looks like a longer underscore. There are indeed 2 there on the left and the right, just like for init. This 1 only takes 1 argument that by convention is always called self, so that you have access to it.
Speaker 1
11:43:16
And then indented below that after a colon, I'm going to go ahead and create a format string and return it. So let me go ahead and return. How about something generic first, like a student? So I'm not going to bother even trying to figure out what this student's name or house is, I'm just going to always return a student.
Speaker 1
11:43:34
Let me go back now to my earlier code, which has print student on line 16. Let me clear my terminal window and rerun Python of student.py, enter. Type in Harry, type in Gryffindor. Last time I saw that very cryptic output.
Speaker 1
11:43:49
This time I see more generically a student. More readable, but not very enlightening. Which student is this? Well, notice that the double underscore stir method takes in this self-argument by default.
Speaker 1
11:44:04
It's just the way the Python authors designed this method. It will always be passed a reference to the current student object. What do I mean by that? When This line of code on line 6 is called print, because it's hoping it's going to get a string, is going to trigger the underscore, underscore, stir, underscore, underscore method to be called.
Speaker 1
11:44:26
And Python for you automatically is going to pass into that method a reference to the object that's trying to be printed, so that you, the programmer, can do something like this. Here's an f string with double quotes as usual. I'm going to use some curly braces and say print out self.name from self.house. So there's nothing new in what I've just done.
Speaker 1
11:44:49
It's just an f string. An f on the beginning, 2 double quotes, a couple of pairs of curly braces. But because automatically this str method gets past self, so to speak, a reference to the current object, I can go inside of that object and grab the name. I can go inside that object again and grab the house.
Speaker 1
11:45:07
So now when I go back to my terminal window, previously it just printed out a student. But now if I run Python of student.py, enter, type in Harry, type in Gryffindor, and 1 more time, hit Enter, Harry is again from Gryffindor. But if I run this yet again, let's, for instance, do Draco is from Slytherin, Enter, Draco's from Slytherin. Now it's customized to the specific object that we're trying to print.
Speaker 1
11:45:36
Questions on this function here, this dunder stir method?
Speaker 2
11:45:43
AUDIENCE 2 Is there anything else that the underscore, underscore stir method can do? The other question is, what's the difference between str and repr? DAVID MALAN.
Speaker 1
11:45:51
A good question. So there are many other methods that come with Python classes that start with underscore, underscore. We're just scratching the surface, and we'll pretty much focus primarily on these.
Speaker 1
11:46:00
But yes, there are many others. And we'll see at least 1 other in just a little bit. Among the others is indeed 1 called repr, which is a representation of the Python object. Generally speaking, the underscore, underscore, repr, underscore, underscore method is meant for developers' eyes.
Speaker 1
11:46:17
It typically has more information than Harry from Gryffindor. It would also say what type of object it is, like a student, capital S. Whereas underscore underscore str, underscore underscore is generally meant for users, the users of the program. And it's meant to be even more user-friendly.
Speaker 1
11:46:33
But both of those can be overridden as you see fit. Well, let me propose now that we pick up where we've left off on student and just add even more functionality, but not just these special methods like double underscore init and double underscore str. Like, let's create our own methods, Because therein lies the real power and flexibility of classes if you and I, as the programmers, can invent new functionality that's specific to students. For instance, students at Hogwarts over the time in school learn how to cast a certain type of spell.
Speaker 1
11:47:03
So when they say, expecto patronum, something comes out of their wand that typically resembles an animal or something like that. It's a special spell that they have to practice and practice. So let's see if we can't store not just the student's name and their house, but also their patronus, what actually they conjure when using this spell. Well, let me go ahead and clear my terminal window.
Speaker 1
11:47:22
And in the top of my code here, in the init method of student, let me go ahead and start expecting a third argument in addition to self, which automatically gets passed in, called patronus. And I'm not going to worry for now on validating the patronus from an official list of valid patronuses or patroni. I'm instead going to go ahead and just blindly assign it to self.patronus equals patronus. And we're going to let the user type whatever they want for now.
Speaker 1
11:47:50
But I could certainly add more error checking if I wanted to limit the patronises to a specific list of them here. Let me go ahead now and prompt the user for this patronis, as by, in my getStudent function, defining a variable called patronus or anything else, prompting the user for input for their patronus. And now I'm going to go ahead and pass in that third variable here. So again, Similar in spirit to just adding more and more attributes to the class, I'm going to pass in all 3 of these values instead of just 2.
Speaker 1
11:48:21
I'm not going to do anything interesting with that value yet. But just to make sure I haven't made things worse by breaking my code, let me run Python of student.py. I'll type in Harry. I'll type in Gryffindor.
Speaker 1
11:48:31
And it turns out his Patronus was a stag, and hit Enter. I haven't seen what his Patronus is in my output because I didn't change my str method yet. But at least I don't have any syntax errors. So at least I've not made anything worse.
Speaker 1
11:48:45
But suppose now I want to have functionality, not just for initializing a student and printing out a student. If my class is really meant to be a student, what I can do is not just remember information about data about students. What's powerful about classes, unlike dictionaries alone, is that classes can have not just variables or instance variables, so to speak, those attributes we keep creating. They can also have functions built in, a.k.a.
Speaker 1
11:49:14
Methods. When a function is inside of a class, it's called a method, But it's still just a function. At this point, we've seen 2 functions already, 2 methods called double underscore init and double underscore str. But those are special methods in that they just work if you define them.
Speaker 1
11:49:30
Python calls them automatically for you. But what if you wanted to create more functionality for a student so that your class really represents this real world, or maybe fantasy world, notion of a student where students not only have names and houses and patronuses, they also have functionality. They have actions they can perform, like casting a charm, a spell, magically. Could we implement, therefore, a function called charm that actually uses their magical knowledge?
Speaker 1
11:50:01
Well, let's go ahead and define our very own function as follows. Let me clear my terminal window, scroll back up to my student class, and instead of creating yet another function that's special with double underscores, I'm going to invent my own function or method inside of this class. I want to give Harry and Hermione and all of the other students the ability to cast charms. So I'm going to define a function that I can completely on my own call charm.
Speaker 1
11:50:28
Though I could call this function anything I want. But because it's a method inside of a class, the convention is that it's always going to take at least 1 argument called self by convention so that you have access to the current object, even if you don't plan to use it per se. All right, let me go ahead and propose that we implement charm in such a way that the method returns an emoji that's appropriate for each student's patronus. All right, how to implement this?
Speaker 1
11:50:55
Well, inside of the charm method, let's go ahead and match on self.patronus, which is the instance variable containing a string that represents each student's Patronus. And in the case that it matches a stag, for instance, for Harry, let's go ahead and return maybe the closest emoji, this horse here. How about in the case of an otter? Well, in that case, let's go ahead and return, oh, maybe the closest match to the otter, which might be this emoji here.
Speaker 1
11:51:24
And let's see. In the case of a, for Ron rather than Hermione, a Jack Russell Terrier, let's go ahead and return, how about, don't have as many options here, why don't we go ahead and return the cutest available dog in that case. And in the case of no Patronus recognized, as might cover someone like Draco, let's go ahead and use a default case using the underscore as in the past. And let's go ahead and return for this.
Speaker 1
11:51:53
Oh, what should happen if someone doesn't have a patronus? Why don't we just see a magical wand that seems to fizzle out, as in this case? All right, well, now, rather than just print the student, let's go about printing their actual patronus. So I'm going to go down to my main function here.
Speaker 1
11:52:07
I'm going to still get a student using the getStudent function. But rather than print student, let's go ahead and declare expecto Patronum, printing out just that as pure text. And now, let's go ahead and print out not the student, but rather the return value of their own charm method. All right, so let me go back down to my terminal window and run python of student.py And enter.
Speaker 1
11:52:31
Name, let's start with Harry. He lives in Gryffindor. Patronus is a stag. And let's see.
Speaker 1
11:52:38
Expecto Patronum. And of course, we see the stag emoji. What about someone like Draco, who at least in the books doesn't have a known patronus? Well, let's go ahead and clear my terminal window, rerun Python of student.py.
Speaker 1
11:52:51
And this time, let's type in Draco for name, Slytherin for house, and patronus is unknown. So I'm just going to go ahead and hit Enter. And now, expecto patronum, and just kind of sizzles instead. Questions now on what I've done by implementing this charm method.
Speaker 2
11:53:11
Can we call a method outside the function?
Speaker 1
11:53:15
Absolutely. And we're seeing that already. If I scroll back down to my main function, which is outside of the class, because it's all the way down in the file, it's all left aligned just like the class. Notice on line 28, I'm calling student.charm.
Speaker 1
11:53:29
And I'm accessing a method that's inside of the student object, even though my main function is outside of the class. And what's different between our init function or method and our str method that we've seen before, Those are called automatically when you try to create a student or when you try to print a student. For the first time, we're now seeing a method that I invented inside of the student class that I can call anywhere I want. And if I'm calling it outside of the class, like I am Here, I just use the name of the variable containing that student object, and I just do dot charm, just like I could access the individual attributes or instance variables inside of that object as well.
Speaker 1
11:54:13
Other questions on classes and this new method charm?
Speaker 4
11:54:17
In parentheses, you put self. It's supposed to be like an initializer. But in the top part, you didn't define it the same way.
Speaker 4
11:54:28
Would you have to put the class on? Why you didn't put the class right there?
Speaker 1
11:54:32
DAVID MALAN So notice the indentation here. So on line 1, I've, of course, said class student colon. And then everything below that is indented at the moment.
Speaker 1
11:54:40
That means that my init method, my stir method, and this new charm method are all inside of part of that class. By convention, then, each of these methods inside of a class should expect that Python will automatically pass in at least 1 argument to every method in a class. And that argument will always be a reference to the current object, the hairy object or the Draco object that is currently trying to cast a charm or be printed. So whenever you create a method inside of a class, you should always have it take at least 1 argument called self and then 0 or more other arguments that are entirely up to you.
Speaker 1
11:55:20
In it, for instance, takes 3 more. Name, house, patronus. Stir takes 0 more. Charm takes 0 more.
Speaker 1
11:55:26
But the convention is to always call that default argument self. And Python just automatically gives you access to the current object. Other questions? How about 1 more on classes and charms?
Speaker 2
11:55:39
AUDIENCE 2 Sir, I just wanted to ask that you have put the emojis in double quotes. So does it take the emojis as a form of strings or something else?
Speaker 1
11:55:49
DAVID MALAN Yes, if unfamiliar, an emoji is just a character. It's part of a mapping of numbers to letters known as Unicode. And so whenever you see these emoji on the screen, like the horse or the otter or the dog here specifically, that's really just like a key from your keyboard.
Speaker 1
11:56:06
You and I on Macs and PCs can't typically type it because you see English characters or some other human language. But emoji are increasingly available via menus and dropdown, and in my case, copy-paste on Macs and PCs and Android devices and iPhones. They are just characters. Even though they look like pictures, you can think of it like a graphical font almost.
Speaker 1
11:56:26
So they're just text. And indeed, if you put them between double quotes or single quotes, you can print emoji just like you can any other human character as well. Well, let me propose now that we remove this patronus code just to simplify our world and focus on some of the other core capabilities of classes. So at the risk of disappointing, I'm going to get rid of all of these beautiful emoji and charms.
Speaker 1
11:56:50
And I'm going to go ahead and stop asking the user now for their Patronus. And I'm going to stop passing it into init here. And I'm going to stop doing this here. And I'm going to instead just go ahead and restore our use of print student here.
Speaker 1
11:57:06
And I'm going to go ahead and get rid of Patronus down here. So it's essentially undo all of the fun charms we just created. So we're now back at the point in the story where we have a student class with only 2 methods, init and str. The first of those takes, of course, self as the first argument, as it always will, plus 2 more now, name and house.
Speaker 1
11:57:28
No more patronus. We're validating name up here. We're validating house down here. And then we're assigning name and house, respectively, to 2 instance variables called name and house also.
Speaker 1
11:57:38
But we use self to get access to the current object to store those values therein. We then still have our stir method here, which takes 1 argument by default, self, and that's it. And that function is going to be called automatically any time you want to convert a student object to a string, just like print might want to do here. So let me go ahead and just make sure I haven't broken anything.
Speaker 1
11:58:00
Let me run Python of student.py. I'll type in Harry. I'll type in Gryffindor, Enter. OK, we're back in business.
Speaker 1
11:58:07
Gone are the charms and patronuses, but at least I'm back to a situation where I have names and houses. But it turns out, At the moment, our use of classes is not very robust, even though we have this mechanism, very cleverly, if I may, in our init method of making sure that we're validating name and house, making sure that name is not blank, and making sure that house is a valid house among those 4 Hogwarts houses, it turns out that classes will still let me get at those attributes, those so-called instance variables, using dot notation anyway. Let me scroll down then And try to do this a little adversarially. Suppose that on line 16, I go ahead and call getStudent, which exists as before.
Speaker 1
11:58:53
And then I store the return value in a student variable, again on line 16. That will ensure that getStudent gets called, which calls input and input. And then it calls the student constructor, which invokes automatically this init method. So by way of how we've laid out my code, we're going to ensure that name is not blank and house is definitely 1 of those 4 values.
Speaker 1
11:59:15
My error correction or error checking is in place. But if I'm a little adversarial, I can still circumvent it. Suppose that, fine, you're going to require me to type in Harry and Gryffindor. I'm going to go ahead and type in student.house equals, quote unquote, number 4, Privet Drive, and you're not going to be able to stop me.
Speaker 1
11:59:36
Why? Well, it turns out with classes and objects thereof, you and I can still access those instance variables using this familiar dot notation. That's how we began the story of classes, just setting these attributes ourselves. But you can also read these attributes themselves and change them later if you want.
Speaker 1
11:59:53
And this will effectively circumvent the if condition and the other if condition in our init method.
Omnivision Solutions Ltd