Package: biwrap
Yet simple util to make wrapper with optional argumentsInstallationMaster branchpipinstallgit+https://github.com/ferrine/biwrapLatest releasepipinstallbiwrapProblem outlinedReadability and transparent implementation is important.Wrappers is an advanced topic in programming and making them readable in some cases is difficult. The particular case is parametrizable wrapper. It can either be called with or without arguments. Implementation to handle this case is often tricky and looks weird (seeSO thread). This package solves the problem and provides with simple and generic solution.APIA minimal snippet to apply it to your problem.importbiwrap@biwrap.biwrapdefwrapper(fn,arg1='default',arg2='default'):defwrapped(*fn_args,**fn_kwargs):...# depends on `arg1`, `arg2`returnwrapped@wrapper# use defaultsdeffunc1(a,b):...@wrapper(arg1='non default')# change defaultsdeffunc2(a,b):...ExampleLet’s discuss a case when it is needed to put functions into a registry. Some functions can have alias names.Naive Solutiondefregister(alias=None):definner(fn):iffn.__name__notinregister.storage:register.storage[fn.__name__]=fnelifregister.storage[fn.__name__]isnotfn:raiseKeyError('{}is already in storage'.format(fn.__name__))ifaliasisnotNoneandaliasnotinregister.storage:register.storage[alias]=fnelifaliasisnotNone:raiseKeyError('{}is already in storage'.format(alias))returnfnreturninnerregister.storage={}@register()deff1(a):returnaprint(register.storage)#> {'f1': <function f1 at 0x11ff519d8>}@register(alias='fn3')deff2(a):returnaprint(register.storage)#> {'f1': <function f1 at 0x11ff519d8>, 'f2': <function f2 at 0x10a87d0d0>, 'fn3': <function f2 at 0x10a87d0d0>}AnalysisThe above example shows redundancy indecorator definition has double nesting (doubledef)usage requires trailing parenthesis@register()even in case we do not use optional argumentMore readable code should avoid these two points and look like:defregister(fn,alias=None):...@registerdeff1(a):returna@register(alias='fn3')# <- (1)deff2(a):returnaNaive implementation of the above API won’t work. Line marked above as(1)will fail as first argumentfnis not passed. But we want the output to be the same.Better solutionimportbiwrap@biwrap.biwrapdefregister(fn,alias=None):iffn.__name__notinregister.storage:register.storage[fn.__name__]=fnelifregister.storage[fn.__name__]isnotfn:raiseKeyError('{}is already in storage'.format(fn.__name__))ifaliasisnotNoneandaliasnotinregister.storage:register.storage[alias]=fnelifaliasisnotNone:raiseKeyError('{}is already in storage'.format(alias))returnfnregister.storage={}@registerdeff1(a):returnaprint(register.storage)#> {'f1': <function f1 at 0x10f45a048>}@register(alias='fn3')deff2(a):returnaprint(register.storage)#> {'f1': <function f1 at 0x10f45a048>, 'f2': <function f2 at 0x10f45a488>, 'fn3': <function f2 at 0x10f45a488>}Functionality OverviewSome corner cases may exist and custom coding can create a boilerplate for each usecase (see thisSO thread). This package takes the best and implements yet simple but generic solution to resolve them all(?).SetupLet’s take a simple wrapper as an example. It will printhiorbyedepending on parametrization, default ishi.importbiwrap@biwrap.biwrapdefhiwrap(fn,hi=True):defnew(*args,**kwargs):ifhi:print('hi')else:print('bye')returnfn(*args,**kwargs)returnnewCasesFunction wrappingDefined wrapper can be used in both ways@hiwrapdeffn(n):print(n)fn(1)#> hi#> 1@hiwrap(hi=False)deffn(n):print(n)fn(1)#> bye#> 1Bound method wrapperbiwrapalso works for bound methods. As seen inSO threadthis can be a problem as first positional argument isselfinstead of a function.classW:def__init__(self,n):self.n=n@biwrap.biwrapdefwrap(self,fn,text='hi'):defwrapped(*args,**kwargs):for_inrange(self.n):print(text)returnfn(*args,**kwargs)returnwrappedwr=W(3)@wr.wrapdeffn(n):print(n)fn(1)#> hi#> hi#> hi#> 1@wr.wrap(text='bye')deffn(n):print(n)wr.n=2fn(2)#> bye#> bye#> 2Class methods / properties wrappingImplementation deals with these cases as wellclassO:def__init__(self,n):self.n=n@classmethod@hiwrapdeffn(cls,n):print(n)@property@hiwrap(hi=False)defnum(self):returnself.no=O(2)o.fn(1)#> hi#> 1print(o.num)#> bye#> 2Wrapper as a functionFunction like call is OK toodeffn(n):print(n)fn=hiwrap(fn,hi=False)fn(1)#> bye#> 1