diff options
| author | triumphantomato <91909240+triumphantomato@users.noreply.github.com> | 2023-09-07 22:29:58 -0700 | 
|---|---|---|
| committer | GitHub <noreply@github.com> | 2023-09-08 13:29:58 +0800 | 
| commit | 7b2491ecd5b3370b7cc712fced32b948440ecb40 (patch) | |
| tree | 5e0b05ead564df8e13e42930965990b19f6a7b8f | |
| parent | 62b5f91d724a7d5cbcbe18086a9ecbc449128e4b (diff) | |
[python/en] Updated Decorator and wrapping explanation (#4749)
Now it includes motivation, an explanation of functools.wraps, and demonstrates the utility of wrapping.
| -rw-r--r-- | python.html.markdown | 70 | 
1 files changed, 53 insertions, 17 deletions
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  | 
