#!/usr/bin/python3 # -*- coding: utf-8 -*- # TODO finish adding type hints to this file? from typing import Callable, TypeVar, Any, cast F = TypeVar("F", bound=Callable[..., Any]) # %% # Python has first-class functions def create_adder(x: int) -> Callable[[int], int]: def adder(y: int) -> int: return x + y return adder add_10 = create_adder(10) add_10(3) # => 13 # %% Decorators """ A decorator is a function that takes in a function as an argument, and returns a modified version of that function, as a new function. Remember: * Functions can define functions within them * Functions can be passed as arguments to functions * Functions can be returned from functions """ # Note: this does NOT use special decorator syntax, but IS a decorator! def my_decorator(func: Callable[[], None]) -> Callable[[], None]: def wrapper() -> None: print("Something is happening before the function is called.") func() print("Something is happening after the function is called.") return wrapper def say_whee() -> None: print("Whee!") # This is equivalent to below say_whee = my_decorator(say_whee) # Note in the above line, we garbage-collect the old say_whee object, # re-assigning the old say_whee label to the new function object # which is returned by my_decorator(say_whee) say_whee() # to this: say_whee is passed as an argument to my_decorator @my_decorator def say_whee2() -> None: print("Whee!") say_whee2() # Here's another decorator def do_twice(func: Callable[[], None]) -> Callable[[], None]: def wrapper_do_twice() -> None: func() func() return wrapper_do_twice def say_whee3() -> None: print("Whee!") # This is equivalent say_whee = do_twice(say_whee) # Note in the above line, we delete the old say_whee object, # re-assigning the say_whee label to the new function # returned by do_twice(say_whee) say_whee() # to this @do_twice def say_whee4() -> None: print("Whee!") say_whee4() # What about functions with arguments: # *args, **kwargs cover all cases (overkill here with no dict args) def do_twice2(func: F) -> F: def wrapper_do_twice(*args: int, **kwargs: int) -> None: func(*args, **kwargs) func(*args, **kwargs) return cast(F, wrapper_do_twice) def greet(name: str, phone: int) -> None: print(f"Hello {name}") print(f"Your number is {phone}") # This is equivalent greet = do_twice2(greet) greet("Bob", phone=123) # To this @do_twice2 def greet2(name: str, phone: int) -> None: print(f"Hello {name}") print(f"Your number is {phone}") greet2("Bob", phone=123) # Return values: You must return in the inside wrapper, # in addition to returning the function itself def plus_one_more(func: F) -> F: def wrapper(*args: int) -> int: return func(*args) + 1 return cast(F, wrapper) # This is equivalent to below def plus_one(x: int) -> int: return x + 1 plus_one = plus_one_more(plus_one) print(plus_one(1)) # To this @plus_one_more def plus_one2(x: int) -> int: return x + 1 print(plus_one2(1)) # ++++++++++++ Cahoot-20.3 # https://mst.instructure.com/courses/58101/quizzes/57317