1 00:00:00,240 --> 00:00:02,510 I think we're going to leave our role playing game characters behind. 2 00:00:02,510 --> 00:00:06,300 They were really handy for exploring brand new classes but a lot of the power in 3 00:00:06,300 --> 00:00:09,140 object oriented design comes from taking advantage of the classes and 4 00:00:09,140 --> 00:00:11,130 objects provided by your language. 5 00:00:11,130 --> 00:00:14,310 Python gives us tons of objects that we can use as the basis of our 6 00:00:14,310 --> 00:00:15,750 own custom ones. 7 00:00:15,750 --> 00:00:18,270 We've been really heavy into magic methods in this stage. 8 00:00:18,270 --> 00:00:19,800 That's not gonna change now. 9 00:00:19,800 --> 00:00:21,620 When you're dealing with custom versions of built-ins, 10 00:00:21,620 --> 00:00:25,250 there are two magic methods that you'll want to know about above all others. 11 00:00:25,250 --> 00:00:26,370 init and new. 12 00:00:27,480 --> 00:00:28,700 You've already seen init, 13 00:00:28,700 --> 00:00:32,350 we use it when we want to customize how a new instance of a class is created. 14 00:00:32,350 --> 00:00:34,000 new works very similarly but 15 00:00:34,000 --> 00:00:37,190 has some special magic that we're not quite ready for it just yet. 16 00:00:37,190 --> 00:00:39,850 The major difference between the two though is when you use them. 17 00:00:39,850 --> 00:00:44,130 If you're customizing a mutable data type like list, you override init. 18 00:00:44,130 --> 00:00:47,880 If you're customizing an immutable type, you use new. 19 00:00:47,880 --> 00:00:50,780 Let's go to work spaces to try these new methods out. 20 00:00:52,110 --> 00:00:54,980 All right, so let's start with an immutable class first. 21 00:00:54,980 --> 00:01:00,350 How about we make a version of string, a string that is always reversed. 22 00:01:00,350 --> 00:01:03,840 I know, I know, you just thought of a million uses for such a thing. 23 00:01:03,840 --> 00:01:05,320 How it's not already built into the library, 24 00:01:05,320 --> 00:01:07,615 I don't know, we'll just have to submit I guess. 25 00:01:07,615 --> 00:01:12,300 [LAUGH] First things first, I have a file named reversedstr.py, 26 00:01:12,300 --> 00:01:14,410 and we need to go ahead and create our class. 27 00:01:14,410 --> 00:01:19,640 So I'm gonna call it ReversedStr, because that's what it is, right? 28 00:01:19,640 --> 00:01:21,110 I don't wanna confuse anyone. 29 00:01:21,110 --> 00:01:22,790 I don't want them thinking this is a normal string. 30 00:01:23,890 --> 00:01:26,933 And instead of going straight to the colon, 31 00:01:26,933 --> 00:01:31,551 I'm going to extend it with str or I'm going to make it extend str. 32 00:01:31,551 --> 00:01:34,050 So then I'm just going to say pass. 33 00:01:34,050 --> 00:01:36,795 And if I use the class right now, it's a string. 34 00:01:36,795 --> 00:01:38,466 Now let's just go ahead and do it. 35 00:01:42,565 --> 00:01:48,187 So Python from reversesdtr import ReversedStr. 36 00:01:48,187 --> 00:01:52,915 rs = ReversedStr('hello'), 37 00:01:52,915 --> 00:01:56,227 and rs is hello, right? 38 00:01:56,227 --> 00:01:59,246 And len(rs) I get 5. 39 00:01:59,246 --> 00:02:02,080 So yep, that's a string. 40 00:02:02,080 --> 00:02:06,880 Okay, so we want to change what happens when the string is created. 41 00:02:06,880 --> 00:02:08,940 Since strings are immutable, 42 00:02:08,940 --> 00:02:13,320 the only place we should change them is at creation time. 43 00:02:13,320 --> 00:02:16,560 We do that in new for immutable objects. 44 00:02:16,560 --> 00:02:19,430 So we're gonna say def __new__() And 45 00:02:19,430 --> 00:02:25,050 this is going to take whatever args and kwargs come in, just in case. 46 00:02:25,050 --> 00:02:28,920 And then, we're gonna say, self =, and self here is just a variable name, 47 00:02:28,920 --> 00:02:33,652 we don't have to call it self, but self = str._ 48 00:02:33,652 --> 00:02:38,500 _new__(args, kwargs). 49 00:02:38,500 --> 00:02:43,689 And then we're gonna say self = self reversed, ::-1. 50 00:02:43,689 --> 00:02:46,000 And then we're going to return self. 51 00:02:46,000 --> 00:02:48,300 Unlike init, new does a return. 52 00:02:49,460 --> 00:02:51,870 Now did you notice that new doesn't take self? 53 00:02:51,870 --> 00:02:55,420 That's because new is a special kind of method known as a class method. 54 00:02:55,420 --> 00:02:57,520 We'll talk more about them in a later video, but for now, 55 00:02:57,520 --> 00:03:01,280 just now that they're methods that operate on a class, and not on an instance. 56 00:03:02,610 --> 00:03:05,190 And inside of here, why aren't we using super? 57 00:03:05,190 --> 00:03:06,850 Well, with immutable types like this, 58 00:03:06,850 --> 00:03:10,470 it can actually be unsafe to use super inside of new. 59 00:03:10,470 --> 00:03:14,520 It's safer and easier to explicitly use the new method of the class that 60 00:03:14,520 --> 00:03:19,690 you're extending directly, which is why we did the str.__new__. 61 00:03:19,690 --> 00:03:23,180 And then of course, we're gonna go ahead and reverse our string, and return it. 62 00:03:23,180 --> 00:03:24,886 So does this work? 63 00:03:24,886 --> 00:03:25,626 Let's find out. 64 00:03:28,766 --> 00:03:33,546 Python, let's just import reversedstr, that's a little bit easier. 65 00:03:33,546 --> 00:03:38,379 And then let's say rs = reversedstr.ReversedStr( And 66 00:03:38,379 --> 00:03:41,680 I'm gonna say hello again. 67 00:03:41,680 --> 00:03:42,740 And now let's look at rs. 68 00:03:44,190 --> 00:03:46,400 I got olleh, ole. 69 00:03:46,400 --> 00:03:48,280 It does, all right. 70 00:03:48,280 --> 00:03:50,680 Well, great. 71 00:03:50,680 --> 00:03:52,350 That was easier than I expected. 72 00:03:52,350 --> 00:03:55,250 What if we wanted to make a sub-class of list, though? 73 00:03:56,440 --> 00:04:01,633 Now, since list is mutable, we'll have to override init instead of new. 74 00:04:01,633 --> 00:04:04,730 We should come up with something to make first, though. 75 00:04:04,730 --> 00:04:08,450 Let's borrow something from the data science world, at least partially, very, 76 00:04:08,450 --> 00:04:09,310 very partially. 77 00:04:09,310 --> 00:04:14,520 And build a list that can be prefilled with a certain number of numbers. 78 00:04:14,520 --> 00:04:19,203 So I'm gonna make a New File for this, I'm gonna call it filledlist.py 79 00:04:20,941 --> 00:04:25,788 And then inside of here I'm actually going to import a library called copy, 80 00:04:25,788 --> 00:04:28,601 and then I'll make my class, FilledList. 81 00:04:28,601 --> 00:04:33,780 And this is going to extend list, and I need to override init. 82 00:04:33,780 --> 00:04:35,780 And I know init takes self. 83 00:04:35,780 --> 00:04:38,810 All right, so I'm gonna take args and 84 00:04:38,810 --> 00:04:43,100 kwargs because If anybody passes me an iterable or 85 00:04:43,100 --> 00:04:47,580 something like that that they want made into a list, I want to accept that. 86 00:04:47,580 --> 00:04:49,840 But basically I'm going to ignore it. 87 00:04:49,840 --> 00:04:51,750 I want two other arguments. 88 00:04:51,750 --> 00:04:53,300 I want a count argument, and 89 00:04:53,300 --> 00:04:57,910 a value argument, so how many do you want, and what do you want, right? 90 00:04:57,910 --> 00:04:59,054 So I want five zeros. 91 00:05:00,933 --> 00:05:07,850 So, first things first, let's go ahead and super, and then init. 92 00:05:07,850 --> 00:05:10,490 And we're going to ignore everything that was passed in. 93 00:05:10,490 --> 00:05:12,020 I don't care what they gave me. 94 00:05:12,020 --> 00:05:16,570 All I want is an empty list when I get to the end of this, okay? 95 00:05:16,570 --> 00:05:17,380 So at this point, 96 00:05:17,380 --> 00:05:22,130 right now, I have a brand new, empty list instance, stored inside of self. 97 00:05:22,130 --> 00:05:23,690 That I can just do whatever I want with. 98 00:05:24,870 --> 00:05:32,650 So now, I use a for loop that'll go through the count and we'll do something. 99 00:05:32,650 --> 00:05:35,040 Now this underscore here ignores 100 00:05:35,040 --> 00:05:37,640 the number that would come out of range, right? 101 00:05:37,640 --> 00:05:41,140 So, range would normally be zero, one, two, three, four, five, 102 00:05:41,140 --> 00:05:44,570 whatever I don't care about that, so I'm going to ignore it. 103 00:05:44,570 --> 00:05:46,120 And that's what underscore lets me do. 104 00:05:46,120 --> 00:05:49,260 I could just use like x and then never use x there. 105 00:05:49,260 --> 00:05:53,180 But underscore is cleaner and implies that you don't care about this value. 106 00:05:53,180 --> 00:05:56,430 All right, so then I'm going to append a copy 107 00:05:57,550 --> 00:06:01,070 of whatever it is that they gave me, whatever the value is that they gave me. 108 00:06:01,070 --> 00:06:03,560 So I'm gonna use copy.copy. 109 00:06:03,560 --> 00:06:05,110 And then value. 110 00:06:05,110 --> 00:06:08,580 So I will append five copies or ten copies or whatever. 111 00:06:08,580 --> 00:06:10,540 Now why am I using copy.copy? 112 00:06:10,540 --> 00:06:14,730 Well, if they send in something mutable, like say, another list, 113 00:06:14,730 --> 00:06:19,370 each member in that FilledList or each member in our FilledList 114 00:06:19,370 --> 00:06:24,340 that was a copy of that list would be the same member if we weren't using copy.copy. 115 00:06:24,340 --> 00:06:26,900 If you changed one of them it would change all of the others too 116 00:06:26,900 --> 00:06:29,050 because we're just putting in references to that list. 117 00:06:30,060 --> 00:06:31,820 Okay, let's try this one out. 118 00:06:33,160 --> 00:06:35,450 With copy.copy though we're not getting references. 119 00:06:35,450 --> 00:06:38,760 We're just getting brand new versions of those lists. 120 00:06:40,150 --> 00:06:47,216 So let's say import filledlist and we'll say fl = filledlist.FilledList(). 121 00:06:48,230 --> 00:06:51,324 And I want this to have 9 copies of the number 2. 122 00:06:51,324 --> 00:06:56,662 So let's do len(fl), it's 9, cool. 123 00:06:56,662 --> 00:07:00,470 Now let's do fl and there's a whole bunch of 2s, nice. 124 00:07:00,470 --> 00:07:04,510 Now let's make sure that the copy part is working, right? 125 00:07:04,510 --> 00:07:09,340 So let's do fl2 = filledlist.FilledList(). 126 00:07:09,340 --> 00:07:14,322 And I want two copies of the list 1, 2, 3. 127 00:07:14,322 --> 00:07:20,030 Okay, so if I look at fl, sorry, if I look at fl2, I get 1, 2, 3, 1, 2, 3, great. 128 00:07:21,650 --> 00:07:27,110 Let's change, on fl2, the first item's second item to be a 5. 129 00:07:27,110 --> 00:07:29,194 So we should have 1, 5, 3, 1, 2, 3. 130 00:07:30,520 --> 00:07:35,134 So if I look at fl2, that's what I have, 1, 5, 3, 1, 2, 3. 131 00:07:35,134 --> 00:07:37,230 So excellent, that's working too. 132 00:07:37,230 --> 00:07:42,290 Now we could go another way, too, and just change some of the abilities of a class. 133 00:07:42,290 --> 00:07:47,800 We don't have to totally change the class, and just destroy everything. 134 00:07:48,900 --> 00:07:50,490 So let's make a new file. 135 00:07:50,490 --> 00:07:55,480 And let's call this one javascriptobject.py. 136 00:07:55,480 --> 00:07:57,470 Maybe you can guess what I'm gonna do here. 137 00:07:57,470 --> 00:08:03,160 Let's make a dictionary that sorta kind acts like a JavaScript object. 138 00:08:03,160 --> 00:08:05,290 Or rather, it has one thing that we want, 139 00:08:05,290 --> 00:08:07,750 which is the ability to look up keys with a dot. 140 00:08:08,930 --> 00:08:12,340 So there's a matching method called getAttribute. 141 00:08:12,340 --> 00:08:16,790 And that method is used whenever we ask for an attribute using dot notation. 142 00:08:16,790 --> 00:08:19,900 So let's try to use that to give us a key. 143 00:08:19,900 --> 00:08:22,490 So we'll call our class JavaScriptObject. 144 00:08:23,570 --> 00:08:24,940 This is going to extend dict. 145 00:08:24,940 --> 00:08:30,930 And then we're gonna find __getattribute__(self, item). 146 00:08:32,000 --> 00:08:38,878 And we're gonna try, return self[item], except KeyError, so 147 00:08:38,878 --> 00:08:44,548 if that key doesn't exist, if item isn't a valid key, 148 00:08:44,548 --> 00:08:50,730 then return super().__getattribute__(item). 149 00:08:50,730 --> 00:08:55,080 So if the key does exist, then we'll get that off of the dict. 150 00:08:55,080 --> 00:09:00,190 If the key doesn't exist though, we fall back to the dict version 151 00:09:00,190 --> 00:09:04,700 of getattribute, which actually looks to see if there's an attribute, 152 00:09:04,700 --> 00:09:08,010 an actual real attribute with that name, okay. 153 00:09:08,010 --> 00:09:09,330 So let's give that one a try now. 154 00:09:10,710 --> 00:09:14,310 We're just making weirder and weirder things? 155 00:09:14,310 --> 00:09:20,160 Okay, so let's say import javascriptobject. 156 00:09:21,570 --> 00:09:28,470 Okay, and let's call this jso = javascriptobject.JavaScriptObject(), 157 00:09:28,470 --> 00:09:32,041 and we're gonna give this one key, which is a (name). 158 00:09:32,041 --> 00:09:34,830 And I always like to use my name, feel free to use your own. 159 00:09:36,490 --> 00:09:38,705 Let's set an actual attribute on this, so 160 00:09:38,705 --> 00:09:42,450 jso.language and will set that language to python, okay. 161 00:09:42,450 --> 00:09:48,380 So, jso.name is Kenneth it went, looked up the key as an attribute, cool. 162 00:09:48,380 --> 00:09:53,070 And if I do jso.language I get Python, and if I do jso.fake, so 163 00:09:53,070 --> 00:09:58,000 that means it doesn't exist at all, I get an AttributeError. 164 00:09:58,000 --> 00:10:02,550 And now an interesting thing here is, if we scroll up just a little bit, 165 00:10:03,580 --> 00:10:08,810 we see that our traceback, we got this KeyError, which that's fine, right? 166 00:10:08,810 --> 00:10:10,910 That's the KeyError that happened here. 167 00:10:10,910 --> 00:10:15,520 And then while that KeyError happened, it came in here into this attribute, and 168 00:10:15,520 --> 00:10:16,550 it couldn't find that attribute. 169 00:10:16,550 --> 00:10:19,680 So it threw a second error, which was this AttributeError. 170 00:10:19,680 --> 00:10:23,640 Hopefully this gives you some ideas, of how you can customize the building classes 171 00:10:23,640 --> 00:10:27,660 to do what you need, with writing iterable code of your own.