Sorry for the delay in responding to this thread! On Wed, Jul 26, 2017 at 09:22:59PM -0500, boB Stepp wrote:
> I am having a recurring problem with Python: I can work out the > mechanics of how various Python constructs work, such as decorators, > but then I find myself scratching my head as to why would I want to > use them. The idea of replacing a function with its decorated version > sounds cool, but what types of problems would I want to use this > approach on? Any time you have a series of functions (or classes) which include the same subsection of code, repeated (often word-for-word!), a decorator is a possible alternative. Here's a simple example. You have a bunch of functions which all follow the same pattern when called: - print the function name - print their arguments - do some work - print their return result - return it Only the "work" differs. So we could write: def func1(a, b, c): print("func1") print(a, b, c) result = a + b + c print("result", result) return result def func2(a, b, c): print("func1") # Oops, copy-and-paste error! print(a, b, c) result = a - b - c print("result", result) return result and so on, but if you have a dozen such functions, that's pretty tedious, and the chances of copy-and-paste errors approaches certainty. A decorator comes to the rescue! def decorate(func): @functools.wraps(func) def inner(a, b, c): print(func.__name__) print(a, b, c) result = func(a, b, c) print(result) return result return inner @decorate def func1(a, b, c): return a+b+c @decorate def func2(a, b, c): return a-b-c This ensures that all such functions are guaranteed to have the correct wrapper code. If you decide to add printing of the date, or change printing to logging to a file, you only need to change it in one place, not a dozen. Another use for decorators is to transform the functions in some way. That's especially useful for methods, where we have three different kinds of methods: - regular instance methods, which need no transformation because they are the default; - class methods; - static methods. Plus you can create your own, if needed. (A fairly advanced topic, but less mind-blowing than metaclasses.) They are all written the same way, with the difference being that class and static methods are decorated with the built-in - classmethod - staticmethod decorators, which handle transforming the function objects into classmethod or staticmethod descriptors. Don't worry if you don't know the technical details of what descriptors are or how they work. The point is you don't need to! You just need to call the decorator, and it does all the work. class X(object): @classmethod def thingy(cls, arg): ... Another example is to register a function with something else. If you have code where you create functions, then register them, you can simplify the process or at least move the registration to the top of the function where it is more obvious with a decorator: def register(func): MyRegistry.register(func) return func @register def spam(): ... If you need to change the registration details, you change it in one place. This is an example that shows that decorators don't need to transform their input. In this case, we return the function unchanged. A more complex example of this is singledispatch: https://docs.python.org/3/library/functools.html#functools.singledispatch Memoization (adding a cache) to a function is another good example. Instead of re-creating the cache logic in every single function, you write it once, as a decorator, and then just call the decorator. https://docs.python.org/3/library/functools.html#functools.lru_cache > One thing that bothers me, is that once I decorate the function, I no > longer have access to the original, un-decorated function. In the most general case, that is true: the decorator can do anything, including deleting the original and replacing it with a function that always returns "¿Qué?" no matter the arguments. Most of the time, this is not a problem. The function is *incomplete* until it has been decorated, so you often don't care about the original. Do you find yourself worrying that you don't have access to the middle one third of your existing functions, without the start and the end? Probably not. All that's happened here is that you've systematically moved the common bits of your functions into a decorator. But for those cases where you might care, and since Python 3.2, if you use the functools.wraps() helper function (itself a decorator!) to create your decorators, it will automatically create a __wrapped__ attribute that holds the original, unwrapped function. > But on the > other hand, if I had a decorator which I wanted to apply to multiple > functions, then I would be DRY-er by taking this approach -- I would > need only one decorator function and could then use it decorate as > many other functions as it made sense to do so. Precisely. -- Steve _______________________________________________ Tutor maillist - Tutor@python.org To unsubscribe or change subscription options: https://mail.python.org/mailman/listinfo/tutor