I figured as to how @Transform
works out for a function defined with this decorator. Let us take the example of the normalisation function that was used in code walkthrough 4.
def _norm(x,m,s): return (x-m)/s
norm_t = Transform(_norm)
I did a %%debug
on norm_t = Transform(_norm)
. I found that the first thing the code was doing was calling __call__
of _TfmMeta
as Transform
uses the meta class _TfmMeta
.
def __call__(cls, *args, **kwargs):
f = args[0] if args else None
n = getattr(f,'__name__',None)
if not hasattr(cls,'encodes'): cls.encodes=TypeDispatch()
if not hasattr(cls,'decodes'): cls.decodes=TypeDispatch()
if isinstance(f,Callable) and n in ('decodes','encodes','_'):
getattr(cls,'encodes' if n=='_' else n).add(f)
return f
return super().__call__(*args, **kwargs)
Here the cls is the Transform
class and the function _norm is in args. f becomes _norm as args[0] is _norm. There are no encodes and decodes set here and neither is n in _ or encodes or decodes. So the last line of the function super().call(*args, **kwargs) gets called.
I was surprised to see that super is Transform class and call is calling init of Transform. But here on the behaviour was predictable. If you follow the debugger snapshot pasted below it will be clear that the flow of events is as follows:
-
enc is set to _norm.
-
init_enc is set to enc (which is now _norm)
-
The existing encodes and decodes of the Transform are deleted
-
encodes and decodes now become TypeDispatch()
-
The enc is added to encodes. Therefore _norm is now a part of self.encodes which allows to use _norm as an encodes function call.
-
The final output is
Transform: False {'object': '_norm'} {}
ipdb> n
/Users/i077725/Documents/GitHub/fastai_dev/dev/local/data/transform.py(140)init()
138 filt,init_enc,as_item_force,as_item,order = None,False,None,True,0
139 def init(self, enc=None, dec=None, filt=None, as_item=False):
→ 140 self.filt,self.as_item = ifnone(filt, self.filt),as_item
141 self.init_enc = enc or dec
142 if not self.init_enc: returnipdb> enc
<function _norm at 0x12ceef830>
ipdb> dec
ipdb> filt
ipdb> as_item
False
ipdb> init_enc
*** NameError: name ‘init_enc’ is not defined
ipdb> n/Users/i077725/Documents/GitHub/fastai_dev/dev/local/data/transform.py(141)init()
139 def init(self, enc=None, dec=None, filt=None, as_item=False):
140 self.filt,self.as_item = ifnone(filt, self.filt),as_item
→ 141 self.init_enc = enc or dec
142 if not self.init_enc: return
143ipdb> n
/Users/i077725/Documents/GitHub/fastai_dev/dev/local/data/transform.py(142)init()
140 self.filt,self.as_item = ifnone(filt, self.filt),as_item
141 self.init_enc = enc or dec
→ 142 if not self.init_enc: return
143
144 # Passing enc/dec, so need to remove (base) class level enc/decipdb> n
/Users/i077725/Documents/GitHub/fastai_dev/dev/local/data/transform.py(145)init()
143
144 # Passing enc/dec, so need to remove (base) class level enc/dec
→ 145 del(self.class.encodes,self.class.decodes)
146 self.encodes,self.decodes = (TypeDispatch(),TypeDispatch())
147 if enc:ipdb> self.class
<class ‘local.data.transform.Transform’>
ipdb> self.class.encodes
{}
ipdb> n/Users/i077725/Documents/GitHub/fastai_dev/dev/local/data/transform.py(146)init()
144 # Passing enc/dec, so need to remove (base) class level enc/dec
145 del(self.class.encodes,self.class.decodes)
→ 146 self.encodes,self.decodes = (TypeDispatch(),TypeDispatch())
147 if enc:
148 self.encodes.add(enc)ipdb> n
/Users/i077725/Documents/GitHub/fastai_dev/dev/local/data/transform.py(147)init()
145 del(self.class.encodes,self.class.decodes)
146 self.encodes,self.decodes = (TypeDispatch(),TypeDispatch())
→ 147 if enc:
148 self.encodes.add(enc)
149 self.order = getattr(self.encodes,‘order’,self.order)ipdb> n
/Users/i077725/Documents/GitHub/fastai_dev/dev/local/data/transform.py(148)init()
146 self.encodes,self.decodes = (TypeDispatch(),TypeDispatch())
147 if enc:
→ 148 self.encodes.add(enc)
149 self.order = getattr(self.encodes,‘order’,self.order)
150 if dec: self.decodes.add(dec)ipdb> n
/Users/i077725/Documents/GitHub/fastai_dev/dev/local/data/transform.py(149)init()
147 if enc:
148 self.encodes.add(enc)
→ 149 self.order = getattr(self.encodes,‘order’,self.order)
150 if dec: self.decodes.add(dec)
151ipdb> n
/Users/i077725/Documents/GitHub/fastai_dev/dev/local/data/transform.py(150)init()
148 self.encodes.add(enc)
149 self.order = getattr(self.encodes,‘order’,self.order)
→ 150 if dec: self.decodes.add(dec)
151
152 @propertyipdb> n
–Return–
None/Users/i077725/Documents/GitHub/fastai_dev/dev/local/data/transform.py(150)init()
148 self.encodes.add(enc)
149 self.order = getattr(self.encodes,‘order’,self.order)
→ 150 if dec: self.decodes.add(dec)
151
152 @propertyipdb> n
–Return–
Transform: Fa…': ‘_norm’} {}
I am surprised as to how the super().call() delegates to init() of Transform and how the super() of a call inside _TfmMeta refers to Transform class. Any thoughts?