# Basic hello message: def greeting1(): return 'hello world' print(greeting1()) # We want to underline messages automatically. We can use a decorator: def underline(func): def wrapper(): message = func() underline = '-' * len(message) return f'{message}\n{underline}' return wrapper # This function is typical for a decorator: It takes a function and # returns a new function wrapping the old function. # # It allows us to define a new greeting function: @underline def greeting2(): return 'hello world' hello = greeting2() print(hello) # Will be underlined # The decorator is just syntactic sugar. # It is equivalent to: def greeting2_equivalent(): return 'hello world' greeting2_equivalent = underline(greeting2_equivalent) assert greeting2_equivalent() == hello # Same as previous greeting # However the advantage of the decorator syntax is that it appears up # front where we can see it along with the other function arguments, # and also looks decorative. # Now we want the decorator to take an argument, so we can use other # characters for underlining. # # Previously what we wrote was just a decorator. # Now what we want to write is a function which returns a decorator. # Thus it'll necessarily be threefold nested: def underline_custom(char): # Returns the decorator def underline(func): # This is the actual decorator def wrapper(): # ...and finally the wrapper. # About wrapper functions: # * The wrapper often forwards args and kwargs # * We can decorate it with @functools.wraps(func) # to make it look more like the function it wraps # (inherit docstring, improve name in stacktrace, ...) message = func() underline = char * len(message) return f'{message}\n{underline}' return wrapper return underline # Now we can specify a particular underline character: @underline_custom(char='—') def greeting3(): return 'hello world' print(greeting3()) # Since the triple function returns a decorator, it is possible to # call it and use its return value directly to decorate functions: underline_fancy = underline_custom(char='—') @underline_fancy def greeting4(): return 'hello world' assert greeting4() == greeting3()