A Complete Example: Putting It All Together
00:00 In the previous lesson, I explained the three different kinds of methods. In this lesson, I’ll show you a complete example using the concepts you’ve learned so far in the course. Time for a quick review.
00:12
So far, you’ve learned how to declare class; how to use attributes; how to create instances; how to write instance, static, and class methods; and how to use properties and the descriptor protocol to make methods look like attributes. Before diving into the sample code, I want to go over a couple of concepts in case they’re new to you. First, that cls
named class being passed into a class method?
00:39 Well keep in mind it’s actually a class. I know that statement seems obvious. I’ve gone over this already, but it means as a consequence, you can actually instantiate things off of it.
00:51
It can look a little weird, as cls
is all small case, and classes are usually in PascalCase, but a class is a class whether or not it fits the naming convention.
01:01
Whatever it is named, call it with parentheses, and it will return an object. In the previous lesson, I used *
(star) in a method signature to mean any number of arguments. There is a related concept using **
(two stars).
01:15 In this case, a dictionary is passed in, with key-value pairs being treated as the names and values of arguments in the signature. This is a programmatic way of specifying which named arguments you use in a function or method call. Don’t worry if that’s a little muddled.
01:32 You’ll see it in practice shortly.
01:35
And I’ve got two more dunder methods for you. .__str__()
gets called when you convert an object to a string, like printing it, and .__repr__()
gets called to represent an object, meaning viewing it in the REPL. By convention, .__repr__()
is supposed to return a string that, if it were pasted into the REPL, would create the same object it is representing. All right, that’s my tangent. Each of these concepts gets used in the example that follows.
02:05 Alright, there’s a little over forty lines of code to go through here, but it’s closer to an actual use case that you might find in the wild. I’m creating a class to represent an employee at a company. As all my employees work for the same place (this must be internal code to the company), I’ve created a class attribute with the company name inside.
02:27
And here is the ever-present .__init__()
. To construct my class, I want a name and a date of birth. I’ve learned my lesson, no more first name, last name, just name. The boilerplate inside stores the name and the birth date. But wait, I’ve been tricky. How?
02:46
Well, I’ll come back to that in a second. .birth_date
is a property. In an earlier lesson, I spoke about the pattern of using a property and a non-public attribute. This isn’t that pattern.
02:59
It’s subtly different. Note in .__init__()
, I didn’t set ._birth_date
. I set .birth_date
. Well, there is a property and a setter for that, which means the code in .__init__()
is actually using the descriptor protocol.
03:14
That’s the tricky bit. The .birth_date
property returns the non-public attribute. But wait, how’d it get set? Well, it gets set with the setter, which .__init__()
called. See, a little tricky.
03:29
Let me scroll down a bit so you can see the setter. Here’s the decorator naming the property that is being set, .birth_date
, and the method, whose name isn’t important because it’s wrapped in a decorator, and what it does is set the value.
03:46
Here, I’m using the datetime
library to parse a date string and store it as an actual datetime
object. So, .birth_date
in .__init__()
takes a string and assigns it to self.birth_date
, which is registered against a setter, which expects a string, converts it to a datetime
object, and then creates ._birth_date
, which the property uses. String goes in, datetime
object comes out.
04:15 A better programmer would include some docstrings on all this explaining what values are expected by these methods. If you happen to come across one, send them my way. I have some code that could do with cleaning up.
04:26 Let me scroll some more.
04:31
.compute_age()
is an instance method. This function returns the employee’s age based on their birth date and today’s date. First, it uses the .today()
method from the datetime
object to get today’s date. Yep, datetime
is a class even though it isn’t named like one.
04:50 Next, it calculates the difference between today’s year and the year of the employee’s birth. You can’t just do subtraction, though. If it’s June, people born in May are one year older than people born in July, as our birthday is the trigger of incrementing that horrible attribute we all carry known as age. Sorry, old man editorializing. I’m not bitter, though.
05:15
Grumpiness aside, to properly calculate the age, I create a new datetime
object based on the current year and the employee’s birth month and day.
05:24 This allows me to compare it to today and check if their birthday has happened this year yet, and if it hasn’t, I’m removing a year and returning that, and otherwise, I return the unmodified value. Okay, time for a class method.
05:42 This is a factory, one that takes a dictionary of key-value pairs and constructs an instance object based on its content. This kind of factory is kind of common in the wild.
05:55 As the constructor only takes a name and a birth date, I could access those values from the dictionary and pass them in directly, but then you wouldn’t get to see this neat little line of code.
06:04
Two things to remember here. First, cls
class, its lowercase stature notwithstanding, is the Employee
class, and like any other class, I can instantiate it using parentheses.
06:17
Normally, I do that with its arguments, name
and birth_date
. But here, I’m doing that other thing, **
. Using **
on a dictionary turns it into arguments for the class.
06:29 Each key-value pair in the dictionary gets used here as a named argument to the constructor. In this case, it makes the code a little harder to read, but if you’ve got a lot of arguments and the possibility of defaults that aren’t in the dictionary, this is the only way to go. All right, that’s enough trickiness. Now a little bit of housekeeping.
06:51
The .__str__()
method is what gets called when you convert an object to a string. It should return a string, and that string can contain whatever you want.
07:00
I’ve put a sentence about our employee. The .__repr__()
method is what gets called when an object is represented in the REPL. Convention is this should be a string that can be pasted in the REPL to create a copy of the object.
07:15
One pattern you’ll see sometimes in a method like this is to use self.__class__.__name__
instead of hard-coding the class name, Employee
.
07:25 The advantage of that is you can rename the class and this would still work, but I figured I threw enough tricky stuff at you already and just went with the hard-coded value instead. All right, that’s my class.
07:37 Let’s go make some employees.
07:47 and you’ll recall from way back at the top of the code, this is the class attribute, the company name. And
07:57
there’s an employee named geralt
.
08:01
And if I examine geralt
in the REPL, the .__repr__()
method gets called. There’s that string I could copy and paste to create a new copy of the object.
08:11
If instead I print it, it gets converted to a string, calling the .__str__()
method. That prints out the sentence. Remember the sentence calls the .compute_age()
instance method, so lots of code getting run here. When I call .compute_age()
directly,
08:29
you can see the 36
used inside of .__str__()
. Before leaving geralt
and moving on,
08:37 just a quick reminder that you can get at class variables through the object as well. But you can only read them. Don’t set them. And generally, as I said before, personally I try not to do this. All right, enough with Geralt. Let’s hire a sorceress.
08:59
I’ve created a data
dictionary containing "name"
and "birth_date"
strings. These correspond to the arguments in the Employee()
constructor.
09:11
I then pass this dictionary to the .from_dict()
class method, which is a factory. This uses the **
mechanism to map the contents of the dictionary to the arguments in the class constructor and returns us the instance that I’ve stored in yen
.
09:28
And there’s yen
, using .__repr__()
09:32 and printing her out. And there you go: all the class stuff you’ve learned so far in one place. This is actually a fairly realistic example. It’s quite common in software to have objects represent things in the real world—here, an employee of a company.
09:49
If an employee was stored in a database as a JSON blob, you would deserialize that JSON into a dictionary and construct an object using the .from_dict()
factory.
10:00
The .compute_age()
idea is a little toy-problem-esque, but it represents the idea of doing work on the data belonging to the employee to produce new information, and it’s quite likely you’re going to have something like that in your own code.
10:15 That’s almost it for part one of this course. Next up, I’ll summarize everything you’ve learned.
Become a Member to join the conversation.