A function wrapping a function
Adding timing, logging, or caching to an existing function without touching its body is exactly what decorators are for. The decorator takes the original function and returns a new one that does something extra before or after calling through.
The @decorator syntax above a def is just shorthand. @timer followed by def do_work(): is exactly the same as writing do_work = timer(do_work) after the definition. Python evaluates the decorator at definition time and replaces the name with whatever it returns.
Once you see decorators that way, the surprise wears off. They are not a special feature, they are an application of the rule that functions are first-class values. You can pass them around and wrap them in other functions, and decorators are the most common reason to do so.