From 7b2491ecd5b3370b7cc712fced32b948440ecb40 Mon Sep 17 00:00:00 2001 From: triumphantomato <91909240+triumphantomato@users.noreply.github.com> Date: Thu, 7 Sep 2023 22:29:58 -0700 Subject: [python/en] Updated Decorator and wrapping explanation (#4749) Now it includes motivation, an explanation of functools.wraps, and demonstrates the utility of wrapping. --- python.html.markdown | 70 +++++++++++++++++++++++++++++++++++++++------------- 1 file changed, 53 insertions(+), 17 deletions(-) (limited to 'python.html.markdown') diff --git a/python.html.markdown b/python.html.markdown index d9261ff2..91a53360 100644 --- a/python.html.markdown +++ b/python.html.markdown @@ -1016,31 +1016,67 @@ gen_to_list = list(values) print(gen_to_list) # => [-1, -2, -3, -4, -5] -# Decorators -# In this example `beg` wraps `say`. If say_please is True then it -# will change the returned message. -from functools import wraps +# Decorators are a form of syntactic sugar. +# They make code easier to read while accomplishing clunky syntax. +# Wrappers are one type of decorator. +# They're really useful for adding logging to existing functions without needing to modify them. -def beg(target_function): - @wraps(target_function) +def log_function(func): def wrapper(*args, **kwargs): - msg, say_please = target_function(*args, **kwargs) - if say_please: - return "{} {}".format(msg, "Please! I am poor :(") - return msg - + print("Entering function", func.__name__) + result = func(*args, **kwargs) + print("Exiting function", func.__name__) + return result return wrapper +@log_function # equivalent: +def my_function(x,y): # def my_function(x,y): + return x+y # return x+y + # my_function = log_function(my_function) +# The decorator @log_function tells us as we begin reading the function definition +# for my_function that this function will be wrapped with log_function. +# When function definitions are long, it can be hard to parse the non-decorated +# assignment at the end of the definition. + +my_function(1,2) # => "Entering function my_function" + # => "3" + # => "Exiting function my_function" + +# But there's a problem. +# What happens if we try to get some information about my_function? + +print(my_function.__name__) # => 'wrapper' +print(my_function.__code__.co_argcount) # => 0. The argcount is 0 because both arguments in wrapper()'s signature are optional. + +# Because our decorator is equivalent to my_function = log_function(my_function) +# we've replaced information about my_function with information from wrapper + +# Fix this using functools + +from functools import wraps + +def log_function(func): + @wraps(func) # this ensures docstring, function name, arguments list, etc. are all copied + # to the wrapped function - instead of being replaced with wrapper's info + def wrapper(*args, **kwargs): + print("Entering function", func.__name__) + result = func(*args, **kwargs) + print("Exiting function", func.__name__) + return result + return wrapper -@beg -def say(say_please=False): - msg = "Can you buy me a beer?" - return msg, say_please +@log_function +def my_function(x,y): + return x+y + +my_function(1,2) # => "Entering function my_function" + # => "3" + # => "Exiting function my_function" +print(my_function.__name__) # => 'my_function' +print(my_function.__code__.co_argcount) # => 2 -print(say()) # Can you buy me a beer? -print(say(say_please=True)) # Can you buy me a beer? Please! I am poor :( ``` ### Free Online -- cgit v1.2.3