
I am really sorry for splitting the text. My first post was in one piece but got blocked by the moderators. I didn't want to create some ridiculous suspense at the end of the first part. Here is the second part... --- Part 2 - The new-style syntax for decorators: Here is the code of the same decorators as in Part 1 above, but the proposed syntax. The first decorator ('new_style_repeat_fix') is created without parameters, while the second one ('new_style_repeat_var') uses arguments 'n' and 'trace' with the same role as previously: #--- @decorator def new_style_repeat_fix(self, *args, **keys): """docstring for decorating function""" print "apply %r on %r" % (self.deco.__name__, self.func.__name__) for n in range(3): self.func(*args, **keys) @decorator(n=3, trace=True) def new_style_repeat_var(self, *args, **keys): """docstring for decorating function""" if self.trace: print "apply %r on %r" % (self.deco.__name__, self.func.__name__) for n in range(self.n): self.func(*args, **keys) #--- When examining the new-style syntax, one can notice that there are basically four characteristics that distinguish NSD from OSD: * Each NSD is decorated by a newly-introduced callable class called 'decorator' (code provided in Part 3) by using one of two possible decorating forms. The first form '@decorator' is employed for decorators that do not need any parameter. The second form '@decorator(arg1=val1, arg2=val2...)' is employed to specify a sequence of named arguments (combined with their default values) that are passed to the decorator using the standard notation for keyword arguments. * The role of the 'decorator' class is to generate the decorated function (i.e. the inner nested function with the classic OSD syntax) and to broadcast it to the decorating function as its first argument 'self'. When the second decorating form is used, all keyword arguments used in '@decorator(...)' are automatically injected as meta-attributes in this decorated function 'self'. In the example above, the two decorator arguments 'n' and 'trace' are available within the code of 'new_style_repeat_var' as 'self.n' and 'self.trace' respectively. This mechanism avoids one level of nested functions used in standard OSD. * In addition to these decorator arguments, the decorating process also injects two other meta-attributes in the decorated function: 'self.deco' represents a reference to the decorating function, while 'self.func' represents a reference to the undecorated function. If there are several chained decorators, the mechanism is made recursive (more on this later). Note that this implies a slight name restriction: neither 'deco' nor 'func' can be used as the name of a parameter passed to the decorator, as this would generate collision in the corresponding namespace. An alternative might be to consider the two references as "special" attributes and rename them as 'self.__deco__' and 'self.__func__' respectively. I have no clear opinion about the pros/cons of the two alternatives. * Finally, note that each NSD has the same 3-argument signature: (self, *args, **keys). The first argument 'self' has been explained above. The two others 'args' and 'keys' respectively represent the set of positional and keyword arguments, as usual. However, all the values in either 'args' or 'keys' are not meant to be used by the decorating function, but always directly passed to the undecorated function. This means that the statement 'self.func(*args, **keys)' will always appear somewhere in the code of an NSD. Following this mechanism in the decorating function avoids the other level of nested functions used in standard OSD, and guarantees that flat functions are always sufficient. Once the NSD have been defined with the new syntax, they can be used to decorate functions using the standard @-notation, either for single or multiple decoration. For instance: #--- @new_style_repeat_fix def testA(first=0, last=0): """docstring for undecorated function""" print "testA: first=%s last=%s" % (first, last) @new_style_repeat_var(n=5) # 'n' is changed, 'trace' keeps default value def testB(first=0, last=0): """docstring for undecorated function""" print "testB: first=%s last=%s" % (first, last) @new_style_repeat_var # both 'n' and 'trace' keep their default values @new_style_repeat_fix @new_style_repeat_var(n=5, trace=False) # both 'n' and 'trace' are changed def testC(first=0, last=0): """docstring for undecorated function""" print "testC: first=%s last=%s" % (first, last) #--- When applying a decorator without arguments, or when *all* its arguments use their default values, the parenthesis after the decorator name may be dropped. In other words, '@mydeco' and '@mydeco()' are totally equivalent, whether 'mydeco' takes arguments or not. This solves a non-symmetric behavior of standard OSD that has always bothered me: '@old_style_repeat_fix' works but '@old_style_repeat_fix()' does not, and inversely '@old_style_repeat_var()' works but '@old_style_repeat_var' does not. Note also that in the case of chained decorators, each decoration level stores its own set of parameters, so there is no conflict when applying the same decorator several times on the same function, as done with 'new_style_repeat_var' on 'testC'. Now let's play a bit with some introspection tools: #---
testA <function <deco>testA...>
testB <function <deco>testB...>
testC <function <deco><deco><deco>testC...> #---
To explicitely expose the decoration process, a '<deco>' substring is added as a prefix to the '__name__' attribute for each decorated function (more precisely, there is one '<deco>' for each level of decoration, as can be seen with 'testC'). So, each time a '<deco>' prefix is encountered, the user knows that the reference to the corresponding undecorated function (resp. decorating function) is available through the meta-attribute '.func' (resp. '.deco'). When calling 'help' on a decorated function, this principle is clearly displayed, and the user can thus easily obtain useful information, including correct name/signature/docstring: #---
help(testA) <deco>testA(*args, **keys) use help(testA.func) to get genuine help
testA.func, testA.deco (<function testA...>, <function new_style_repeat_fix...>)
help(testA.func) testA(first=0, last=0) docstring for undecorated function
help(testA.deco) new_style_repeat_fix(self, *args, **keys) docstring for decorating function #---
In the case of chained decorators, the same principle holds recursively. As can be seen in the example below, all information relative to a multi-decorated function (including all decorator arguments used at any decoration level) can be easily fetched by successive applications of the '.func' suffix: #---
help(testC) <deco><deco><deco>testC(*args, **keys) use help(testC.func.func.func) to get genuine help
testC.func, testC.deco, testC.n, testC.trace (<function <deco><deco>testC...>, <function new_style_repeat_var...>, 3, True)
testC.func.func, testC.func.deco (<function <deco>testC...>, <function new_style_repeat_fix...>)
testC.func.func.func, testC.func.func.deco, testC.func.func.n, testC.func.func.trace (<function testC...>, <function new_style_repeat_var...>, 5, False)
help(testC.func.func.func) testC(first=0, last=0) docstring for undecorated function #---
------ to be continued in Part 3... CS