back to indexfastai v2 walk-thru #5
Chapters
0:0
5:48 Transforms
13:20 Metaclass
17:41 Why Are We Using Metaclass
22:9 The Python Data Model
22:14 The Python Data Model
22:27 Customizing Class Creation
23:0 Metaclasses
29:52 Type Dispatch Class
44:36 Treat a Transform Subclass as a Decorator
00:00:58.080 |
I just had to restart Chrome for some reason. 00:01:18.460 |
So there's a few possible directions we can go, but maybe we could just dive into transforms. 00:01:26.960 |
Not because particularly it's code that many people need to understand, but I think it's 00:01:38.720 |
So it's fun to see how the basics, the lowest level stuff is put together. 00:01:43.840 |
And then we can kind of build back from there, look at data source and look at data blocks. 00:01:53.120 |
Maybe first of all, though, we'll have a quick look at data blocks. 00:02:01.120 |
Because you can kind of see where we're heading with this, which we can simplify. 00:02:18.800 |
So quickly, where we're getting to is here are some examples. 00:02:27.360 |
So here's an example of MNIST using the new data blocks API. 00:02:33.180 |
And as you can see, it's not a fluent API anymore. 00:02:55.600 |
But now it's more of a, I don't know how you describe it. 00:02:59.480 |
It's kind of similar to these other callbacks/subclass APIs like the data loader one. 00:03:06.680 |
So you can now subclass from a data block and define the normal things that we're used 00:03:14.360 |
to from version one, or you can construct it and pass in exactly the same things as functions. 00:03:25.560 |
So the data box API ends up looking kind of similar. 00:03:30.280 |
But in some ways it's kind of easier to use because you can see easily what things you 00:03:40.480 |
So there's MNIST, here's pets, as you can see, looks pretty familiar. 00:04:03.240 |
One is, again, bypassing things in, actually, here's a super, super simple way to do it. 00:04:30.320 |
So you can see we're going to end up with a nice simple data block API. 00:04:35.680 |
Here is the entire definition of the data block class. 00:04:41.800 |
As you can see, it fits on less than one screen of code. 00:04:47.120 |
And the reason for that is all that intermediate API stuff we built makes creating stuff like 00:04:57.000 |
For questions about any of the data sets I just showed, have a look at the most recent 00:05:01.360 |
courses, because they're all described there. 00:05:05.600 |
And yes, David, this new API does present accidental wrong ordering, because there's 00:05:10.600 |
no order to these things, they're just either subclasses, they're either subclasses or parameters. 00:05:37.840 |
It's just so easy to create these kinds of things. 00:05:41.840 |
So yeah, let's start pretty close to the bottom of the layers now. 00:05:50.460 |
So before we do, I will answer this question from Gokor, which is my thought process when 00:06:01.080 |
I just throw code out there, and I then refactor it again and again and again. 00:06:07.640 |
So I try to make sure I've got pretty good tests. 00:06:10.960 |
And I keep refactoring it until it gets to the point where I feel like it's so easy that 00:06:14.880 |
even I will understand it in six months' time. 00:06:19.680 |
With the previous versions of fast.ai, I haven't had the time to fully do that. 00:06:27.840 |
So as you can see, every function is super short and easy, which is really nice. 00:06:41.400 |
So transforms, the basic transform class is here, again, less than a line of code-- sorry, 00:07:00.920 |
But the work's really happening in the meta class, because what happens is when we call 00:07:09.080 |
done to call or decode, it's going to call end codes and decodes by this little intermediate 00:07:20.720 |
thing, underscore call, which we haven't really looked at filters much yet. 00:07:27.240 |
But once we get to data source, you'll see this is how we do things on just the training 00:07:37.280 |
Basically, we check whether we're doing as item as true or false. 00:07:42.760 |
If we're doing as item, then we just call the function, decodes or end codes. 00:07:50.480 |
If it's tuple behavior rather than item behavior, then we call it for each element of the list 00:08:04.000 |
So that's all pretty simple, but the trick is what are end codes and decodes, because 00:08:15.640 |
we know these are things which have a few cool things we can do. 00:08:35.120 |
Most interestingly, we can have a type annotation and we can have multiple end codes/decodes 00:09:05.920 |
So let's look at how we-- OK, so the first two are pretty straightforward. 00:09:18.240 |
Now in it, you can pass in an encoder and/or a decoder. 00:09:26.720 |
And if you pass either of those in, then it will create the encoder and decoder-- encodes 00:09:40.440 |
But as you can see, they are not just methods. 00:09:49.720 |
So that's what we're going to have to look at. 00:09:51.680 |
If you don't pass in an enc or dec, which will be-- that means this will be false, then 00:09:59.440 |
somehow encodes and decodes have already been created for us. 00:10:12.000 |
So answering questions, mixing, tabular and text, for example, yes, we certainly want 00:10:24.980 |
Not sure what you're asking exactly, AJ, about post-student projects at USF, but maybe ask 00:10:30.720 |
Do I have to subclass something? Simply means to type class something and then put something 00:10:39.040 |
So for example, there is a subclass of transform. 00:10:48.200 |
OK, so if we look underneath here, we can see some of the kind of behaviors being used. 00:10:59.120 |
So here's another interesting behavior, which is that we can use a class of transform as 00:11:14.600 |
a decorator to add a encodes or decodes to an existing class. 00:11:30.040 |
OK, yeah, Max, we're going to be talking about filters later when we look at data source, 00:11:39.040 |
but basically just it's something that we're going to have zero generally as a training 00:11:44.520 |
set, filter zero as a training set, filter one is generally the validation set, you can 00:11:51.960 |
Transforms know basically which data set they're being applied to so they can have different 00:11:55.680 |
behavior for training versus validation optionally, but we'll look at that later. 00:12:03.120 |
OK, so here's an example of a class transform a, which is getting an encodes, which is just 00:12:11.720 |
So we can check that that works, we should be able to subclass that transform, so that 00:12:17.880 |
checks that that works, and here's an empty transform, so it should do nothing at all. 00:12:24.360 |
So that checks that that works, but then later on, we'll get to the tests where, for example, 00:12:32.020 |
we are making sure things only work on a particular subclass, in this case, tensor image. 00:12:40.000 |
So here's the test, we're going to create one of these a objects, and we will call it, we 00:12:48.880 |
should end up with a negative number, assuming that this was a something of type and tensor 00:12:54.320 |
image, or else if we pass something which is not a type tensor image, in this case an 00:13:02.960 |
Also, when we do this, we should not be changing the type, so we check that the type is still 00:13:18.520 |
So the trick is that all the stuff is happening in the meta class. 00:13:27.240 |
Redex given some good examples and code walkthroughs of some meta class stuff in the forums, so 00:13:32.120 |
we check that out, but basically, when you go class something like that, that's creating 00:13:45.640 |
By the way, if you don't know, dot dot dot is basically the same as typing pass in Python. 00:13:50.800 |
So that's created a new class, as you can see. 00:13:59.920 |
Well class foo, writing this, is basically syntax sugar for calling type and passing in 00:14:10.560 |
these three things, a name, so foo, let's call this one foo2, some bases, so the base 00:14:23.080 |
class is always implicitly, unless you write otherwise, object, so it's object. 00:14:30.680 |
And a dictionary of stuff to put in the class, which in this case is empty, and so let's 00:14:46.960 |
call that foo2, you can see they're very similar looking things. 00:15:02.640 |
So in other words, this is just syntax sugar for calling this thing called type. 00:15:12.040 |
You can find out the type of something by using the single argument version of type, 00:15:17.480 |
and the type of type is type, in other words, type is a class, and so this is a constructor, 00:15:28.400 |
and it constructs something, so if I go type foo2, it constructs something of type type, 00:15:41.440 |
and so there are all kinds of attributes that a type has. 00:16:04.240 |
So in particular, one of the important ones is I can put here a colon one. 00:16:11.760 |
The most important is dundadict, and dundadict is basically a dictionary, it's a slightly 00:16:21.960 |
special kind of dictionary, but it's a dictionary which contains a mapping from names to values, 00:16:41.200 |
and in particular anything that I passed in here ends up in that dictionary. 00:16:46.160 |
There's another way to put things in the dictionary, which is to use this special syntax sugar 00:16:56.920 |
And so now, if I say foo.dict, you can see it's got the same thing, a equals one. 00:17:10.640 |
So when you type that into a class, it's actually just a shortcut for creating an A attribute 00:17:29.040 |
Okay, a bunch of questions, what's for recommended systems, no plans yet. 00:17:46.720 |
Basically among the reason I started using meta classes in version 2 is because I wanted 00:17:56.080 |
to change the things about how Python worked that I didn't really have the time to change 00:18:02.760 |
So things like this, all the stuff we're going to look at doing with transforms are impossible 00:18:12.160 |
So yes, max type creates class objects and then class objects create instances, exactly 00:18:20.480 |
So, it also means in Python, it's worth knowing how these syntax sugar things work. 00:18:28.140 |
When you go like this, if you go like that, for example, then that's the same as saying 00:18:44.680 |
And so if you look at the dict, you'll see you end up with f and then some function. 00:18:54.040 |
So really, there's not that much - there's a small, concise, elegant set of foundations 00:19:04.480 |
in Python and the kind of stuff that we type day to day is a bunch of little bits of syntax 00:19:20.880 |
And specifically, it's syntax sugar for dunder dict a. 00:19:34.080 |
So this is all important to understand when we look at meta classes. 00:19:41.380 |
And the reason why is because a meta class is something where we're going to replace 00:19:46.200 |
We're going to say I want to create a class that does not use a type constructor but uses 00:19:54.560 |
And the way you do that in Python is you type meta class equals and then you type the name 00:20:01.680 |
of the class you want to construct this class. 00:20:05.720 |
You can create a class from scratch, but normally you wouldn't. 00:20:18.640 |
And if you remember, type takes three things, object or name, basis, dict. 00:20:29.040 |
So dunder new always requires class first and then here it is, name, basis, dict. 00:20:35.220 |
So if I wanted to create a super simple meta class, then I could just... 00:20:56.280 |
So here's something which just returns super. 00:20:59.640 |
So it's not going to change anything at all, but it will work, right? 00:21:17.600 |
So you can now start inserting things in here. 00:21:26.200 |
And see how this printed as soon as I typed class t pass, right? 00:21:29.960 |
It didn't print after I created an object of type t, it appeared as soon as I created 00:21:37.680 |
So Python is going to call this code any time I try to actually create a class, not when 00:21:51.280 |
So in this case, we are placing three things, new, call and prepare. 00:22:07.600 |
So there is a really cool piece of documentation called the Python data model. 00:22:13.360 |
And the Python data model describes how all this works. 00:22:18.480 |
And not just all this, but everything, it describes how Python is, how everything happens. 00:22:26.900 |
So there's a section called customizing class creation. 00:22:33.000 |
Where you can see all of the stuff that happens, including three, three, three, one meta classes. 00:22:45.060 |
And we can see in three arguments, it returns a new type object. 00:22:57.460 |
So meta classes, as it says, you say meta class equals blah. 00:23:06.960 |
And the first thing that happens is it has to prepare the class namespace. 00:23:14.400 |
And the class namespace is the dunderdict, is the dunderdict object. 00:23:25.760 |
So if we were to keep creating our underscore m, we could just return an empty dictionary. 00:23:49.240 |
And I guess we should be able to put something in it, even. 00:24:02.300 |
So you can see dunderdict is created by calling your dunderprepare. 00:24:06.920 |
And this is actually a way you can insert something into every single class. 00:24:17.080 |
So to train the different arguments to the meta class constructor, you would want to 00:24:27.040 |
read 3.3.3 of the data model reference in the Python docs. 00:24:44.080 |
So it's not returning a dictionary, but instead it's returning some special kind of dictionary 00:24:51.480 |
So a tuffundict is a dictionary that overrides dunder set item. 00:25:07.680 |
So I had things set up so you could use either underscore or encodes. 00:25:11.660 |
I'm actually going to get rid of the thing that lets you use underscore. 00:25:16.400 |
So basically, if you're calling something that isn't encodes or decodes, then it's just 00:25:27.160 |
But if it is encodes or decodes, then what I do is I check whether or not I already have 00:25:40.760 |
And if I don't, then I create one using dicts set item. 00:25:51.720 |
And what I set it to is I set it to a type dispatch object. 00:25:57.940 |
So in other words, this tuffundict is something which behaves exactly like a normal dictionary 00:26:06.520 |
and so it's going to be inside my dunder dict for anything that uses this meta class and 00:26:11.560 |
it's not going to work differently at all except for two special things called encodes 00:26:16.480 |
And in those cases, it's going to use something called type dispatch. 00:26:25.080 |
So if I go plus a meta class equals to for meta, well, let's and then let's say def 00:26:54.440 |
encodes self, turn X, so a dot encodes, oh, that's not a normal function, what type is 00:27:14.400 |
So do I create a meta class instead of inheriting from the class? 00:27:20.280 |
By inheriting from a class, you can't change the behavior of type creation. 00:27:25.760 |
So we're trying to create something where anything that is inheriting from transform 00:27:37.200 |
And so it's impossible to do the things we're describing by inheriting. 00:27:41.720 |
There's no way, for example, normally to be able to say, I want to have two different 00:27:50.920 |
encodes for two different types, for instance. 00:27:59.120 |
But thanks to meta classes and to done to prepare, we know that each time it sees this, 00:28:05.180 |
it's going to call our replacement dicts under set item with a key of encodes and the value 00:28:11.520 |
of the function, and we can do whatever we like. 00:28:16.720 |
And so what we're going to do is we're going to create an encodes, decodes type dispatch 00:28:21.360 |
object if we don't already have one, and then we're going to add this function to that type 00:28:33.520 |
If I add this twice with two different things, you can see now float has one thing and int, 00:28:56.480 |
It's basically how do you make Python do whatever you wanted to do. 00:29:02.560 |
Python is a dynamic language, and so they created this amazing data model to allow us 00:29:15.300 |
to customize anything we like in Python, but it takes some time to get used to. 00:29:20.680 |
So it taught me a lot of reading and studying and looking at lots of different places to 00:29:27.720 |
get the hang of all this, and so I don't expect to understand it all the first time around. 00:29:32.600 |
I'm not sure what built-in functionality you're referring to, Max. 00:29:37.960 |
I don't think Python has anything which does what I just described, which is why I'm doing 00:29:48.560 |
Okay, so here is the type dispatch class, and basically it's going to be something which 00:29:57.640 |
we know it's going to have to work a lot like a dictionary because we're going to be adding 00:30:02.920 |
things to it, and what's going to happen--actually, I haven't quite shown you all this yet--what's 00:30:11.380 |
going to happen is we're going to--let's see, we're going to grab encodes, we're going to 00:30:21.840 |
call--oh, yes, so we're going to call the function, and when we call the function--well, Max, 00:30:31.480 |
we're not just checking types, we're dispatching on types. 00:30:33.760 |
There's a big difference between checking types and dispatching on types, so we're actually 00:30:36.680 |
building something where we're able to call different code depending on types, so yeah. 00:30:45.680 |
So there isn't a way to do that in Python, so as we've discussed in previous walkthroughs, 00:30:54.400 |
when you look at, for example, data augmentation, we're going to have class rotate, which when 00:31:00.520 |
we first define it might be empty, and then later on when you say, "Oh, I've got something 00:31:05.080 |
for a tensor image," then we'll say @rotate, and it'll say def encodes, x colon tensor image, 00:31:18.960 |
and then degrees, and then there'll be some functionality for that, and then in some other 00:31:23.960 |
place there's going to be x colon bounding box, and it's going to be different also whether 00:31:31.800 |
So it's a--it's quite different functionality. 00:31:42.920 |
Dispatch is referring to how do you--how does a programming language decide what piece of 00:31:52.480 |
So for example, there's all kinds of different ways of doing dispatch in Python. 00:32:01.540 |
The main one that is used for methods, for example, is something called MRO, which is 00:32:11.720 |
Yeah, so it's like--it's basically all the rules in a language about how do you decide 00:32:18.860 |
which piece of code to call when you call some function, and different languages do 00:32:25.280 |
it all kinds of different ways, and yeah, check out some of the earlier walkthroughs 00:32:30.560 |
if you want more information about why we're doing this dispatch. 00:32:35.000 |
Yes, thank you, I mentioned that should have been transformed if we want that to work. 00:32:47.720 |
So, what we want is we want something which works--looks like a function, so that means 00:33:01.280 |
it has to have it done to call, but when you call it with some argument, we're not just 00:33:10.240 |
going to call a function, but we're going to look at the type of that argument and we're 00:33:14.800 |
going to try to find the appropriate function or method to call based on the type of that 00:33:21.360 |
argument, and based on which methods have been created so far. 00:33:33.400 |
And so, what's going to happen is that inside our type dispatch object, there will be a 00:33:40.560 |
dictionary called "funx" and that's going to contain a dictionary where the key is the 00:33:47.240 |
So, for example, in this case, we're going to have keys "tensor image" and "bbox" and 00:34:06.480 |
So then, there was an "add" method and that is what FIFM dict calls. 00:34:17.560 |
It adds this function, and the "add" method is going to find out the type annotation for 00:34:27.480 |
the first parameter, "p1-anno" is parameter number one annotation, so it will grab the 00:34:33.320 |
type. If there is none, then it's assuming that it's object because that's the highest 00:34:37.560 |
level of the type hierarchy, and it's going to pop that into our functions dictionary. 00:34:44.480 |
So then, later on, when you call "dumda call", it's going to look up the type of the parameter 00:34:53.560 |
that you're calling this function on, and it's going to look it up in this object. 00:35:00.080 |
If it doesn't find it, then it does nothing at all, so that's kind of the rule, right? 00:35:05.800 |
If you only have like a "rotate" defined for "tensor image" and "bbox" and then you call 00:35:12.400 |
it with "int", then nothing happens because that function isn't defined. 00:35:21.920 |
If we did find it, then we just call it with that argument and anything else that you passed 00:35:30.040 |
along. And you can actually tag things to say, "I want you to turn it into a method", 00:35:36.920 |
which is something we might talk about later if people are interested, but basically it's 00:35:42.440 |
just going to create a normal function unless you ask it to be a method. 00:35:48.360 |
So the key thing, then, is how does this line of code work? How does it look up the type? 00:35:54.800 |
So as you can see, it's calling "dunda get item", and basically what we're going to do 00:36:00.600 |
is we're going to keep a cache, which is a dictionary mapping from types to functions. 00:36:10.480 |
And we need a special cache dictionary because the way type dispatch works is it doesn't 00:36:15.480 |
just look up, say, "tensor image" or "bbox", but it also looks really subclasses of those 00:36:22.160 |
things. So, for example, we could have also, as we discussed in an earlier walkthrough, 00:36:29.200 |
"tensor". And in this case, then it's going to, if you pass a tensor image, it'll grab 00:36:35.160 |
the most specific version it can, which is a tensor image version. 00:36:47.480 |
So I think I just answered your question, which is the opposite of that. If you call 00:36:50.440 |
it on something which is a subclass of "tensor image", it will be invoked. And the reason 00:36:55.360 |
why is because of how we create this cache. And so if we don't find it in the cache, then 00:37:05.760 |
we're going to add it to the cache. And what we do is we create a list of all of the types 00:37:15.040 |
that are registered, for which there is the appropriate subclass relationship. And we, 00:37:28.760 |
how do we do this? And notice that the functions have been ordered by a comparator which checks 00:37:43.880 |
for subclass relationships. And so we grab the first one. And so the first one is the 00:37:49.680 |
most specific type of the ones that it's a subclass of. This takes a little bit of time. 00:37:58.080 |
We don't want to have to do this every time we call this function. So once we find the 00:38:01.400 |
right one, we pop it in the cache. So next time around, we can just grab it. Okay. So 00:38:16.000 |
that is type dispatch. And so the key way to understand it really is to look at the tests, 00:38:22.120 |
right? So you can see here we've got things at lots of different levels of hierarchy. 00:38:26.600 |
There's a parameter that's a collection, some kind of integer, tensor, mask or image, or 00:38:36.800 |
some kind of number, right? So these are all functions. So we can create a types dispatch 00:38:44.580 |
object with all of those different functions in. And so we should try, if we look up int 00:38:55.720 |
for instance, then we should get back this one because none of them were defined with 00:39:05.400 |
int specifically, but it's going to match number and integral. Integral is more specific 00:39:14.760 |
type than number. So that's why it matches that. String doesn't match any of them. So 00:39:23.880 |
it's a none. So here's the same kind of thing, but this time after creating the object, we'll 00:39:34.040 |
actually start calling some functions and make sure that they do the right thing. Okay. 00:39:47.560 |
So that is type dispatch. So yeah, to get the Python data model in your head requires kind 00:39:56.960 |
of putting all these things together. But the nice thing is once it is all in your head, 00:40:02.320 |
you can put it together the way we have here. So we were able to replace the normal dictionary 00:40:11.760 |
by replacing prepare with to firm dict. The firm dict is something which instead of a 00:40:19.640 |
normal dictionary, when you set the item as encodes or decodes, it actually creates a type 00:40:25.620 |
dispatch object and adds to it. And that means that now when we inherit from transform and 00:40:39.440 |
we create an encodes or decodes, um, attribute, uh, it will actually add it to the encodes 00:40:46.360 |
or decodes type dispatch. And so when we call encodes or decodes, we get this behavior, which 00:40:55.400 |
is to get a negative version of this because it's a tensor image and a non-negative version 00:41:01.600 |
of this because it's not a tensor image. Um, so, um, this specifically is single dispatch, 00:41:13.080 |
not multiple dispatch. So, um, it only looks up. Let's find it. Um, when you add it, it 00:41:24.980 |
only adds the, so P one ano is a function, any tiny little function here, which just 00:41:35.240 |
grabs the, um, added the first parameter annotation. So this will only work. Uh, so this will only 00:41:40.720 |
do type dispatch based on the first, um, non-self parameter. So for me, that's a very good question. 00:41:50.060 |
Why not throw an error? Um, basically because of what I was describing before, if you are 00:41:56.680 |
defining say, uh, rotate data augmentation for various types, um, then generally speaking, 00:42:06.860 |
if you don't have it defined for your type, then probably what we want to do is nothing 00:42:14.160 |
at all. Um, so the basic idea is that these kind of transforms are things you can opt 00:42:21.040 |
into. Um, if you do want to create a transform, um, um, if you do want to create a transform 00:42:37.440 |
which, uh, throws an error, uh, if you, um, if you call it with something that doesn't 00:42:45.060 |
exist, you certainly can. Let's do it. So here's a transform. And so if we say def encodes, 00:42:57.000 |
and we'll say here, raise not implemented, and then we'll go def encodes self comma x 00:43:09.600 |
colon int return x plus one. So let's go a equals a, and then we can go a one that returns 00:43:22.640 |
two a high, and that's going to return. Oh, is that not the right one? There we go. Not 00:43:37.280 |
implemented error. So you can certainly, um, um, add that. And so the reason this works 00:43:43.600 |
is because to remember that if you don't have an annotation, it's the same as saying object. 00:43:48.440 |
It's kind of the way Python normally does things. That's the way we do things too. And 00:43:52.760 |
since that's the highest thing up in the inheritance, in the inheritance hierarchy, that's the one 00:43:57.360 |
that it would end up calling if you don't provide some other behavior. Uh, so thank 00:44:05.440 |
you for that. Excellent question. Um, okay, so that is the way that we handle dispatch. 00:44:31.840 |
So the next thing is what about the way we can treat a transform sub class as a decorator? 00:44:44.680 |
So if you remember a decorator, um, uh, when you see something like this, um, we'll actually 00:44:52.840 |
call this as a callable and pass this function to it. So that is basically identical to saying 00:45:04.600 |
something like def underscore encodes equals that. And then encodes equals, um, a parenthesis. 00:45:16.520 |
And actually what I should say is no, I just say, yeah, a underscore encodes. It's basically 00:45:28.800 |
the same as doing that. Um, and we should find that the same test then passes. Um, but 00:45:37.400 |
we have to say a equals that. Um, let's see. Ah, yes, it's slightly different because it 00:45:52.680 |
thinks of it as not as a method. Okay. So it's not exactly the same. It's nearly the same, 00:46:02.720 |
well because of Python, because of stuff we do. So we'll come back to that later. Um, okay. 00:46:09.000 |
So the reason we can use this, um, as a decorator is because that means that our class, um, 00:46:22.640 |
is going to have to support, um, the callable basically. Um, now classes are normally callables 00:46:29.360 |
because you can, um, instantiate a class, obviously. So if you go class B like this, 00:46:37.800 |
then you go B equals B, then you're calling B as a callable or B's type, B's metatype 00:46:46.200 |
as a callable. Uh, so that means if we go to our meta class, we can redefine under call. 00:46:54.080 |
And that means we can redefine what happens when we instantiate this class. Um, so if we 00:47:04.760 |
instantiate this class or if we, if we done the call this class, um, and we pass in some 00:47:12.920 |
argument, um, and if that argument is callable, um, and it's not decodes or encodes, then we 00:47:28.840 |
are going to, um, add that function, uh, to our type dispatch. Um, and so that's exactly 00:47:38.800 |
what happens when we say at a def encodes, blah, blah, blah. It's just going to add that 00:47:46.040 |
function to this class's type dispatch. So you can see here it calls dot add, which is 00:47:58.280 |
the dot add that we had here in type dispatch. So this is the thing that basically registers 00:48:05.800 |
another type. Um, okay. So, uh, that's the hat. Uh, there's one last piece, which is done 00:48:23.160 |
to new. So this is the thing that first gets called when you're creating a new type. Um, 00:48:32.240 |
and one of the things I discovered, which is super annoying, is that if I create a new 00:48:37.240 |
type, like so any title type, even without being a meta type, and I define a done to 00:48:44.240 |
new, like so, so here's some class and then I define some sub class and I say in it, like 00:49:13.080 |
so. And then so I want to instantiate that and then I shift tab. Um, oh, that was not 00:49:23.200 |
what I expected to happen. Oh, sorry. And plus B inherits from a shift tab. You can 00:49:33.920 |
see the signature is star, star, star quags, or else I would have expected the signature 00:49:39.200 |
to be a, as it would be if I removed my base class. Um, so I found that super annoying because 00:49:49.080 |
like you want to customize new all the time. Not all the time, but very frequently. Um, 00:49:54.480 |
and pretty much most of the time when you customize it, you're going to use star, star, 00:49:58.280 |
star quags because you don't want to define what base classes can do. Um, but as soon 00:50:03.480 |
as you do that, you, you kill the signature, like so. So, um, I don't know why it works 00:50:20.240 |
that way and maybe I'm missing something, but what I did here was I replaced the signature 00:50:29.640 |
for the class with a signature for the dunder init so that we get the right nice signature. 00:50:37.080 |
So if we try that, here we have a look at transform. You can see we get the correct 00:50:45.240 |
signature as we would want. Um, because otherwise, uh, it's not just under new, it would also 00:50:55.120 |
be done to call from the meta class, uh, would also replace the signature otherwise. Um, so 00:51:02.680 |
that's why that's there. Um, so believe it or not, that's actually all the pieces. Um, 00:51:12.580 |
and so if you want to study this, like, so the first thing to point out is none of this 00:51:17.120 |
is at all necessary to understand fast AI version two. It's no more important than understanding 00:51:24.720 |
the meta object data model is to, um, use Python day to day. It's an advanced technique which 00:51:33.600 |
you can learn about if you're interested. And if you're interested in learning more 00:51:38.340 |
about how Python works behind the scenes, so you can try doing stuff like this yourself 00:51:42.600 |
if you want to create, um, change how Python works and fully use its dynamic features. 00:51:49.840 |
So if you want to fully understand what transform does, just check out all the tests. Um, we've 00:51:55.840 |
tried to make it so that each test does one thing, you know, one shows one clear type 00:52:03.540 |
of behavior. We try to add comments explaining what each behavior is that it's showing. Um, 00:52:13.720 |
so yeah, hopefully that all is useful for those of you that are interested. So then you can 00:52:20.440 |
see tuple transform and item transform just force an item to be true or false and lots 00:52:28.880 |
more tests of that behavior, as you can see. Um, all right. Well, I think that's enough 00:52:38.240 |
for today because that was super dense. Um, and, uh, yeah, if you start looking through 00:52:43.040 |
this code and want to learn more about it, feel free to ask any questions you like. All