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 methodmult
, 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
**kwargs
which 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)>
NB: @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 @use_kwargs_dict
-
@func_kwargs: It is used to change the class methods in a class which are in the
_methods
class 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.@delegates
makes 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
Thanks to @msivanes , @kshitijpatil09 and everyone in my fastai study group for pushing and helping me to do this
CORRECTIONS AND CRITICISM IS HIGHLY WELCOME