Package: compose
The classiccompose, with all the Pythonic features.Thiscomposefollows the lead offunctools.partialand returns callablecomposeobjects which:have a regular and unambiguousrepr,retain correct signature introspection,allow introspection of the composed callables,can be type-checked,can be weakly referenced,can have attributes,will merge when nested, andcan be pickled (if all composed callables can be pickled).Forasync/awaitsupport, different variants ofcomposeare included.VersioningThis library’s version numbers follow theSemVer 2.0.0
specification.Installationpip install composeFor static type checking, also installthe type hint
stubs:pip install compose-stubsUsageBasicsImportcompose:>>>fromcomposeimportcomposeAll the usual function composition you know and love:>>>defdouble(x):...returnx*2...>>>defincrement(x):...returnx+1...>>>double_then_increment=compose(increment,double)>>>double_then_increment(1)3Of course any number of functions can be composed:>>>defdouble(x):...returnx*2...>>>times_eight=compose(double,double,double)>>>times_16=compose(double,double,double,double)We still get the correct signature introspection:>>>deff(a,b,c=0,**kwargs):...pass...>>>defg(x):...pass...>>>g_of_f=compose(g,f)>>>importinspect>>>inspect.signature(g_of_f)<Signature(a,b,c=0,**kwargs)>And we can inspect all the composed callables:>>>g_of_f.functions# in order of execution:(<functionfat0x...>,<functiongat0x...>)composeinstances flatten when nested:>>>times_eight_times_two=compose(double,times_eight)>>>times_eight_times_two.functions==times_16.functionsTrueWhen programmatically inspecting arbitrary callables, we
can check if we are looking at acomposeinstance:>>>isinstance(g_of_f,compose)Truecomposeraises aTypeErrorwhen called with
no arguments or with any non-callable arguments:>>>compose()Traceback(mostrecentcalllast):...TypeError:compose()needsatleastoneargument>>>compose(increment,'oops',increment)Traceback(mostrecentcalllast):...TypeError:compose()argumentsmustbecallableasync/awaitWe can composeasynccode by usingacompose:>>>importasyncio>>>fromcomposeimportacompose>>>>>>asyncdefget_data():...# pretend this data is fetched from some async API...awaitasyncio.sleep(0)...return42...>>>get_and_double_data=acompose(double,get_data)>>>asyncio.run(get_and_double_data())84acomposecan compose any number ofasyncand regular functions, in any order:>>>asyncdefasync_double(x):...awaitasyncio.sleep(0)...returnx*2...>>>async_times_16=acompose(async_double,double,async_double,double)>>>asyncio.run(async_times_16(1))16acomposeinstances always return awaitable values,
even if none of the composed functions areasync:>>>awaitable_times_16=acompose(double,double,double,double)>>>asyncio.run(async_times_16(1))16sacomposeis likeacompose, butsacomposeinstances return an awaitable value only if any of
the composed functions return an awaitable value:>>>fromcomposeimportsacompose>>>>>>regular_times_4=sacompose(double,double)>>>awaitable_times_4=sacompose(double,async_double)>>>>>>regular_times_4(1)4>>>asyncio.run(awaitable_times_4(1))4acomposeandsacomposeinstances flatten when nested:>>>acompose(f,acompose(f,f)).functions==(f,f,f)True>>>acompose(sacompose(f,f),f).functions==(f,f,f)True>>>sacompose(acompose(f,f),f).functions==(f,f,f)True>>>sacompose(f,sacompose(f,f)).functions==(f,f,f)TrueButcomposeinstancesdon’tflatten when nested
intoacomposeandsacompose, and vice versa:>>>acompose(g_of_f).functions(compose(<functiongat0x...>,<functionfat0x...>),)>>>sacompose(g_of_f).functions(compose(<functiongat0x...>,<functionfat0x...>),)>>>compose(acompose(g,f)).functions(acompose(<functiongat0x...>,<functionfat0x...>),)>>>compose(sacompose(g,f)).functions(sacompose(<functiongat0x...>,<functionfat0x...>),)compose,acompose, andsacomposeinstances are all distinct types:>>>isinstance(g_of_f,compose)True>>>isinstance(g_of_f,(acompose,sacompose))False>>>isinstance(async_times_16,acompose)True>>>isinstance(async_times_16,(compose,sacompose))False>>>isinstance(awaitable_times_4,sacompose)True>>>isinstance(awaitable_times_4,(compose,acompose))False