On Tue, Jul 05, 2016 at 09:22:52AM -0400, Alex Hall wrote: > Hi list, > I've read three or four articles on creating decorators. I don't know why, > but the use of decorators makes sense, yet the creation of them isn't > clicking. I get the idea, but when it comes to precisely how to write > one--what goes where and gets returned/called when--it's just not making > complete sense.
*Technically* a decorator can return anything at all, but the most common use for them is to return a function. In this case, the decorator is a function that: (1) Takes as input a single function; (2) Processes or wraps that function in some way; and (3) Returns the original function, or the wrapped function, as output. Back before Python had the "@decorator" syntax, we used to use decorators like this: def func(arg): ... func = decorator(func) which has the disadvantage that we have to write the function name three times, and that the call to the decorator is kinda lost there at the end instead of near the function declaration, but it does have one advantage: it makes it clear that "func" gets set to whatever decorator() returns. Which may be None if you're not careful. > To simplify things, what might be an example of a decorator that, say, > prints "decorated" before whatever string the decorated function prints? import functools def prependDecorated(function): """Wrap function so that it prints "decorated" before running.""" # Here we define a new function that prints "decorated", # then calls the original function. # # We use functools.wraps as a bit of magic to make sure our new # function looks like the original (same name, docstring, etc). # @functools.wraps(function) def inner(*args, **kwargs): print("Decorated!") return function(*args, **kwargs) # # Now that we have our new "inner" function that calls the old # one, we have to return that inner function. return inner @prependDecorated # NO PARENTHESES!!! def lunch(): print("What's for lunch?") print("How about some wonderful SPAM!!!") That's what the great bulk of decorators look like: (1) declare a function that takes one function as argument; (2) define an inner function (usually called "inner"); (3) it will often take arbitrary *args, **kwargs parameters, since you don't normally know what arguments the WRAPPED (decorated) function will take; (3) use functools.wraps to disguise the inner function as the original function (this is important so that it keeps the docstring, the name, any other attached attributes etc. that the original had); (4) inside the "inner" function, do your pre-processing, then call the original function, do any post-processing, and then return the result; (5) finally return the "inner" function. Here's a decorator that makes sure the decorated function returns a string: def stringify(func): @functools.wraps(func) def inner(*args, **kwargs): result = func(*args, **kwargs) return "stringified result: " + str(result) return inner @stringify def add(a, b): """Return the stringified result of adding a and b.""" return a+b Here's a decorator that makes sure the first argument is greater than zero: def verify_arg0(func): @functools.wraps(func) def inner(x, *args, **kwargs): if x <= 0: raise ValueError('x must be greater than zero') return func(x, *args, **kwargs) return inner Here's a neat trick: here's a decorator that doesn't actually modify the function, but registers its name somewhere! THE_REGISTER = [] def register(func): THE_REGISTER.append(func.__name__) return func @register def plus(a, b): return a + b @register def minus(a, b): return a - b > That is: > > @prependDecorated() > def printSomething(str): > print str > > printSomething("Hello world.") > #result should be: > decoratedHello world. This one is trickier than you think, because it requires knowledge of what the original function will do. It *requires* that the original function MUST call print at least once, otherwise you'll have started to print something, but not completed the line. That may mess up your python prompt (in the interactive interpreter) or any other output you print. But here we go: def prepend_actual_print(func): @functools.wraps(func) def inner(*args, **kwargs): ## print("decorated", end='') # Python 3 syntax sys.stdout.write("decorated") # Python 2 return func(*args, **kwargs) return inner Hope this helps! -- Steve _______________________________________________ Tutor maillist - Tutor@python.org To unsubscribe or change subscription options: https://mail.python.org/mailman/listinfo/tutor