name: inverse layout: true class: center, middle, inverse --- # Decorators (Advanced topics) BB1000 Programming in Python --- layout: false ## Decorators in Python: basic concepts - Python function definition ~~~ def function_name(parameters): "function docstring" function body return [expression] ~~~ - Python class method definition ~~~ def methodname(self, parameters): "method docstring" method body return [expression] ~~~ * Functions are first-class objects: * implemented operators can be applied to function variable * functions can be passed as arguments to other functions * functions can be used as class/object variables in Class --- ## Decorators in Python: basic concepts * passing arguments to function via args-kwargs syntax ~~~ def function_name(*args, **kwargs): "function docstring" function body return[expression] ~~~ * args are positional parameters of function (tuple) * kwargs are key-valued parameters of the function (dict) The function call ~~~ >>> function_name('hello', 'world', when='now') ~~~ will map values to variables in the function ~~~ args -> ('hello', 'world') # tuple kwargs -> {'when': 'now'} # dict ~~~ --- * Definition: a decorator extends and modified the behaviour of a callable (functions, methods, and classes) without permantly modifying the callable itself * *A decorator is a function that takes a function as in put and returns a function as output* ~~~ def decorator(f): ... @decorator def function_name(...): ... ~~~ is equivalent to ~~~ def decorator(f): ... def function_name(...): ... function_name = decorator(function_name) ~~~ --- ~~~ def empty_decorator(func): return func def hello(): return "Hello world" simple_hello = empty_decorator(hello) >>> simple_hello() Hello world! ~~~ * Returning modified function from decorator ~~~ def uppercase(func): def wrapper(): original_result = func() modified_result = original_result.upper() return modified_result return wrapper @uppercase def hello(): return "Hello world!" >>> hello() HELLO WORLD! ~~~ --- * Multiple decorators applied to single function ~~~ def strong(func): def wrapper(): return '**' + func() + '**' return wrapper def emphasis(func): def wrapper(): return'!!' + func() + '!!' return wrapper @emphasis @strong def hello(): return'Hello World' >>> hello() !!**Hello World**!! ~~~ --- * Passing variables to decorator ~~~ def decorator_function(func): def wrapper(*args, **kwargs): return func(*args, **kwargs) return wrapper ~~~ --- * Example: inspect values passed to and from a function ~~~ from functools import wraps def trace(func): @wraps def wrapper(*args, **kwargs): print( f'TRACE: calling {func.__name__}() ' f'with {args}, {kwargs}' ) original_result = func(*args, **kwargs) print( f'TRACE: {func.__name__}() ' f'returned {original_result!r}' ) return original_results return wrapper ~~~ * Apply trace decorator to function ~~~ @trace def say(greet, name): return greet + ",' + name ~~~ ~~~ >>> say("Hello", "world") TRACE: calling say() with ('Hello', 'world'), {} TRACE: say() returned 'Hello, world' Hello, world ~~~ --- * Decorate a class method ~~~ def as_html(func): def func_wrapper(*args, **kwargs): return f"
{func(*args, **kwargs)}
return func_wrapper class Person: def__init__(self): self.name = "John" self.family = "Doe" @as_html def hello(self): return f"Hello, my name is {self.name} {self.family}" ~~~ ~~~ >>> my_person = Person("Anne", "Applebaum") >>> print(my_person.hello())
Hello, my name is Anne Applebaum
~~~ --- About decorators: * decorators is good way to avoid code duplication * decorators allows to write more "pythonic" and clean code * functools.wraps make your decorators debug friendly when using multiple decorators See also * https://docs.python.org/3/glossary.html#term-decorator * https://realpython.com/primer-on-python-decorators/ * https://www.geeksforgeeks.org/decorators-in-python/