Fastai2 source code was very scary to me(still is) when I started reading it. I kept on seeing some decorators everywhere and wondered what they were. I decided to make explanation of some of the decorators which you’ll see scattered everywhere in the fastai2 source code
@patch: This is for adding extra functionalities to any given python
Type. For example, let’s assume you want an L type to be given a new method
mult, here’s how you will go about it.
@patch def mult(x:L, a): '''we want an L object to have the method `mult`, we just have to patch it to the L NB: @patch does not work on default inbuilt python types ''' # a is the value we pass into the func when defining it #x is the value of the initial type which we want to operate on return x*a L(1, 2).mult(2) [OUTPUT] (#4) [1,2,1,2]
NB: When @patch is to be used on multiple Types, they are passed in as tuples and the new method we write will be patched to the two Types in the tuple
@use_kwargs_dict: It is used to set the
**kwargswhich are to be used in a method call. Think of it as a store for al the **kwargs in a functions signature. An example is shown below
@use_kwargs_dict(a=10, b=12, c=None) def rand_func(z=10, **kwargs): pass inspect.signature(rand_func) [OUTPUT] <Signature (z=10, *, a=10, b=12, c=None)>
@use_kwargs can be used instead of
@use_kwargs_dict when we want all the kwargs to have a
None values. Also note that a list of all the argument keys is passed into the
@use_kwargs instead of a key-value pair in
@func_kwargs: It is used to change the class methods in a class which are in the
_methodsclass attribute list. A demo can be seen in the example below
@funcs_kwargs class A(): _methods = ['method1'] def __init__(self, a=10, **kwargs): pass def method1(self): return print('From method 1') def method2(self): return print('From method 2') a_ = A() a_.method1() [OUTPUT] From method 1 #we can change any method in the class which is in the `_method` class attribute a_ = A(method1 = lambda: print(f'New method 1')) a_.method1() [OUTPUT] New method 1 #As we can see, the class method method1 has changed. This change can only occur # if the class method is in the _methods class attribute list
We can optionally use a @method decorator to define a new class method to replace one that exists in the _method class attribut list
@method def new(self): print(f'New method 1') a_ = A(method1=new) a_.method1() [OUTPUT] New method 1
@delegates: It is used to pass the keyword args of a function to it’s wrapper by simply calling **kwargs in the
__init__of the initial function.
@delegatesmakes the autocompletion(shift+tab) of the
__init__of the wrapper function show the necessary keyword args of the initial function. An example of this is shown below
def init_func(a=1, b=2): return a + b @delegates(init_func) def wrap_func(c=10, **kwargs): pass we can now inspect the function signature of this wrapper function inspect.signature(wrap_func) [OUTPUT] <Signature (c=10, a=1, b=2)> #from the result, we can see that it gives collates the kwargs from the initial # function and adds them to the signature of the wrapper function.
- @typedispatch is the magic that makes all the transforms in fastai work (especially the decodes). It is the heart of fastai2 because every operation in this new library is treated as a transform and the @typedispatch handles them all. You use it to specify the types that every given transform is allowed to operate on. Let’s say you have a Pipeline containing Pipeline(PILImage.create, ToTensor) which you want to use on either a TfmdLists or a Datasets. Type dispatch makes it possible to know the various types in the TfdLists or Datasets which each transform will be able to work on. PILImage.create can only be dispatched on PIL.Image types. ToTensor on torch.Tensor types.
# lets write a generic function that can do concatenation of its strings arguments. # We may want to modify this function to be able too perform a different operation # it's arguments are no longer strings. We can do this using a @typedispatch decorator @typedispatch def generic_func(a:str, b:str): return a + b @typedispatch def generic_func(a:int, b:int): '''function that does multiplication when we have int types''' return a * b @typedispatch def generic_func(a:int, b:float): '''function that does division when we have an int and float types''' return a / b @typedispatch def generic_func(a:int, b:str): '''function that does string multiplication when we have an int and str types''' return a * b # str, str type generic_func('Fastai is', ' awesome!') [OUTPUT] 'Fastai is awesome!' #int, int types generic_func(2, 3) [OUTPUT] 6 #int, float types generic_func(2, 3.0) [OUTPUT] 0.6666666666666666 #int, str types generic_func(2, 'fastai') [OUtPUT] 'fastaifastai' # A single function performing multiple operations
CORRECTIONS AND CRITICISM IS HIGHLY WELCOME