Package: black-magic
Metaprogramming modules that operate on black magic!Currently there is only one module available. However, I am all open for
cool ideas.black_magic.decoratorThis module allows to create wrapper functions that look and behave identical
to the original function. This is particularly useful for decorators.Part of the module replicates the functionality of the well-knowndecoratormodule, but is based on creating AST nodes directly rather than composing and
compiling strings.Furthermore, this module makes it possible to create wrappers with modified
signatures. Currently, the only specialized function that is explicitly
dedicated to this purpose ispartial. If you are interested in doing
more complex modifications you can pass a dynamically createdSignaturetowraps. If you make something useful, please consider contributing
your functionality to this module..wraps()wrapscan be used similarly to the standardfunctools.wrapsfunction. However, it returns a real function, i.e. something that will
have a useful signature when being inspected withhelp()or by other
metaprogramming tools. Furthermore, it knows how to copy the signature
exactly, even remembering object identity of default arguments and
annotations:>>>fromblack_magic.decoratorimportwraps>>>defreal(a=[]):...pass>>>@wraps(real)...deffake(*args,**kwargs):...returnargs>>>fake()([],)>>>fake(1)(1,)>>>fake(a=2)(2,)>>>fake()[0]isreal()TrueIf you want to get real crazy you can even useast.expr’s:>>>importast>>>fake=wraps(real)(ast.Num(n=1))>>>fake(0)1WARNING: before usingfunctools.partialwith any of the functions in
this module, make sure to read the warning below!.partial()This is similar to thefunctools.partialfunction.>>>fromblack_magic.decoratorimportpartial>>>defreal(arg):...print(arg)>>>partial(real,arg=0)()0There are some differences, though:this function returns a function object which looks like the input
function, except for the modified parameters.all overwritten parameters are completely removed from the signature. Infunctools.partialthis is true only for arguments bound by position.the**kwargsare stripped first, then*args>>>partial(lambdaa,b,c:(a,b,c),2,a=1)(3)(1,2,3)by leaving the first argument emptypartialcan act as decorator:>>>@partial(None,1,bar=0)...deffoo(bar,lum):...returnbar,lum>>>foo()(0,1)Note, that the function returned bypartial(None,...)is just likepartial: it can bind additional arguments and you can still leave the
first parameter unspecified. This has weird properties and should not be
used in production code, but I thought it would be great to add some
additional brainfuck, to see where it will go.CAUTION:Iterative invocation ofpartial(withNoneas first
argument) doesn’t hide parameters the same way thatpartialapplied to
a function does, i.e. you can move bound arguments to the right in later
calls. In code:>>>partial(None,1)(a=0)(lambdaa,b:(a,b))()(0,1).metapartial()The returned value can be called likepartialbind a function to the
parameters given here. In fact,partial = metapartial().Binding further keyword arguments via the returned function will overwrite
keyword parameters of previous bindings with the same name.>>>@metapartial(1,a=0,c=3)...deffunc(a,b,*args,**kwargs):...return(a,b,args,kwargs)>>>func(2)(0,1,(2,),{'c':3}).decorator()This is the canonic utility to create decorators:>>>fromblack_magic.decoratorimportdecorator>>>@decorator...defplus_one(fn):...deffake(*args,**kwargs):...return1+fn(*args,**kwargs)...returnfake>>>@plus_one...defmul_plus_one(a,b):...returna*b>>>mul_plus_one(2,3)7.flatorator()flatoratorimitates the functionality of the well knowndecoratormodule.>>>fromblack_magic.decoratorimportflatorator>>>@flatorator...deftimes_two(fn,*args,**kwargs):...return2*fn(*args,**kwargs)>>>@times_two...defadd_times_two(a,b):...returna+b>>>add_times_two(1,2)6Under the hoodQ: This uses uglystrconcat andevalcode, right?A: No, it uses uglyabstract syntax treecode to do its dynamic code generation.Q: But it’s still ugly code, right?A: Yes.WARNING: performance hits incomingDecorating a function with the tools in this module is a quite costy
operation, so don’t do it very often! Invoking the wrapper is no problem on
the other hand.WARNING: functools.partial is evilBe careful when passingfunctools.partialobjects into.wraps, or
any black magic functions more generally.functools.partialfeatures
very unsensible handling of arguments that are bound by keyword. These, and
all subsequent arguments, become keyword-only parameters. Consider the
following example:>>>importfunctools>>>deffunc(a,b,*args,**kwargs):...return(a,b,args,kwargs)>>>part=functools.partial(func,a=0)>>>part(1)Traceback(mostrecentcalllast):...TypeError:func()gotmultiplevaluesforargument'a'Furthermore, note that the*argsparameter becomes completely
inaccessible, forever!For compatibility between python versions and ease of use, I chose to handlefunctools.partialobjects as if you had actually usedblack_magic.decorator.partialwith the same arguments, i.e.:>>>wrap=wraps(part)(part)>>>wrap(1,2,c=3)(0,1,(2,),{'c':3})Note, the signature imposed by.wraps(functools.partial(f))is
equivalent to the signature of.wraps(.partial(f)), which might come
unexpected.TestsThis module has been tested to work on python{2.6, 2.7, 3.3, 3.4, 3.5}
and PyPy1.9 usingTravis CI, and tested with python 3.2 locally.Changelog0.0.12Date: 26.12.2015fix missing UNLICENSE file in source distributionsome internal maintenance0.0.11Date: 25.12.2015add support for python 3.50.0.10add compatibility with python3.40.0.9fix signature ofblack_magic.decorator.partial: remove named
parameterfuncthat can prevent binding of the a parameter with the
same name into**kwargsreturn value ofpartialcan now be used for further parameter
(re-)binding if the function was left open. CAREFUL: this might have
unexpected characteristics, such as moving positional parameters to the
right in later calls.addmetapartialfunction, that accepts only*args, **kwargsto be
bound, while the function to be used can only be specified in a second
step.slightly improve performance ofblack_magic.decorator. Is now
approximately the same as the performance of thedecoratormodule.0.0.8convert allfunctools.partialtoblack_magic.partialwith the same
parameters.0.0.7partial support forfunctools.partial0.0.6fixfunctools.update_wrapperemulation inblack_magic.decorator.wraps()0.0.5addblack_magic.decorator.partial0.0.4support any callable to be passed toASTorator.decorateconvert README to .rst