Python - Decorators Flashcards

1
Q

Can you put functions inside a list as values? How would you call them?

A

Yes,

ex)

my_list = [ function1, function2]

my_list [0] (arg_to_insert)

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
2
Q

Can a function be inserted as an argument into another function? How would you do that?

A

Yes,

ex)

def outer_function (inner_function):
    return inner_function("blah blah arg")
How well did you know this?
1
Not at all
2
3
4
5
Perfectly
3
Q

What is the general template illustrating the concept of a decorator?

A
def my_decorator(func):
    def wrapper():
        print("Something is happening before the function is 
                 called.")
        func()
        print("Something is happening after the function is 
                  called.")
    return wrapper
def say_whee():
    print("Whee!")

say_whee = my_decorator(say_whee)
______

> > > say_whee()
Something is happening before the function is called.
Whee!
Something is happening after the function is called.

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
4
Q

What is a decorator? Why is it useful?

A

Decorators wrap a function, modifying its behavior.

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
5
Q

Define a function parent which has two functions defined within first_child and second_child. Have it take an int parameter and depending upon this parameter, return a specific function. How would we use this parent function?

A
def parent(num):
    def first_child():
        return "Hi, I am Emma"
    def second_child():
        return "Call me Liam"
    if num == 1:
        return first_child
    else:
        return second_child
\_\_\_\_\_

> > > first = parent(1)
second = parent(2)

> > > first
.first_child at 0x7f599f1e2e18>

> > > second
.second_child at 0x7f599dad5268>

> > > first()
‘Hi, I am Emma’

> > > second()
‘Call me Liam’

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
6
Q

Write a (clunky non-pythonic) decorator that takes a function that prints “Whee!” if an hour is between 7 & 22, else it passes and doesn’t return a function.

A

from datetime import datetime

def not_during_the_night(func):
def wrapper():
if 7 <= datetime.now().hour < 22:
func()
else:
pass # Hush, the neighbors are asleep
return wrapper

def say_whee():
    print("Whee!")

say_whee = not_during_the_night(say_whee)
______

If you try to call say_whee() after bedtime, nothing will happen:

> > > say_whee()

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
7
Q

Write a (pythonic clean) decorator using the @ symbol that takes a function that prints “Whee!” if an hour is between 7 & 22, else it passes and doesn’t return a function.

*Hint - This is called the “Pie” syntax and is an example of “Syntactic Sugar” to make things easier to read/write

A
def my_decorator(func):
    def wrapper():
        print("Something is happening before the function 
                 is called.")
        func()
        print("Something is happening after the function is 
                  called.")
    return wrapper

@my_decorator
def say_whee():
print(“Whee!”)
_______

So, @my_decorator is just an easier way of saying say_whee = my_decorator(say_whee).

It’s how you apply a decorator to a function.

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
8
Q

How would you write a decorator for the say_whee function and package it as it’s own module?

A

Create a file called decorators.py with the following content:

def do_twice(func):
    def wrapper_do_twice():
        func()
        func()
    return wrapper_do_twice
\_\_\_\_\_\_\_\_\_

You can now use this new decorator in other files by doing a regular import:

from decorators import do_twice

@do_twice
def say_whee():
    print("Whee!")
\_\_\_\_\_\_\_\_\_

When you run this example, you should see that the original say_whee() is executed twice:

> > > say_whee()
Whee!
Whee!

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
9
Q

Say that you have a function that accepts some arguments. Can you still decorate it? Define a greet function that takes in a name as a string and decorate it with a do_twice function.

*Hint - You need to use *args & **kwargs in the inner wrapper function. Then it will accept an arbitrary number of positional and keyword arguments.

A

Create a file called decorators.py with the following content:

def do_twice(func):
    def wrapper_do_twice(*args, **kwargs):
        func(*args, **kwargs)
        func(*args, **kwargs)
    return wrapper_do_twice
\_\_\_\_\_\_\_

In another file import that function:

from decorators import do_twice

@do_twice
def greet(name):
    print(f"Hello {name}")
\_\_\_\_\_\_\_

The wrapper_do_twice() inner function now accepts any number of arguments and passes them on to the function it decorates. Now both your say_whee() and greet() examples works:

> > > say_whee()
Whee!
Whee!

> > > greet(“World”)
Hello World
Hello World

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
10
Q

What does *args do?

A

The *args and **kwargs is a common idiom to allow arbitrary number of arguments to functions. You can use both in the same function definition.

The *args will give you all function parameters as a tuple:

The single * means that there can be any number of extra positional arguments. foo() can be invoked like foo(1,2,3,4,5). In the body of foo() param2 is a sequence containing 2-5.

def foo(param1, *param2):
    print(param1)
    print(param2)
\_\_\_\_\_\_

foo(1,2,3,4,5)

> > > 1
(2, 3, 4, 5)

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
11
Q

What does **kwargs do?

A

The *args and **kwargs is a common idiom to allow arbitrary number of arguments to functions. You can use both in the same function definition.

The **kwargs will give you all keyword arguments except for those corresponding to a formal parameter as a dictionary.

The double ** means there can be any number of extra NAMED parameters. bar() can be invoked like bar(1, a=2, b=3). In the body of bar() param2 is a dictionary containing {‘a’:2, ‘b’:3 }

def bar(param1, **param2):
    print(param1)
    print(param2)
\_\_\_\_\_\_

bar(1,a=2,b=3)

> > > 1
{‘a’: 2, ‘b’: 3}

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
12
Q

What is introspection? How would this apply to a function?

A

A great convenience when working with Python, especially in the interactive shell, is its powerful introspection ability. Introspection is the ability of an object to know about its own attributes at runtime. For instance, a function knows its own name and documentation…

The introspection works for functions you define yourself as well:

> > > say_whee
.wrapper_do_twice at 0x7f43700e52f0>

> > > say_whee.__name__
‘wrapper_do_twice’

>>> help(say_whee)
Help on function wrapper_do_twice in module decorators:

wrapper_do_twice()

_____

However, after being decorated, say_whee() has gotten very confused about its identity. It now reports being the wrapper_do_twice() inner function inside the do_twice() decorator. Although technically true, this is not very useful information.

To fix this, decorators should use the @functools.wraps decorator, which will preserve information about the original function. Update decorators.py again:

import functools

def do_twice(func):
    @functools.wraps(func)
    def wrapper_do_twice(*args, **kwargs):
        func(*args, **kwargs)
        return func(*args, **kwargs)
    return wrapper_do_twice
\_\_\_\_\_\_

Much better! Now say_whee() is still itself after decoration…

> > > say_whee

> > > say_whee.__name__
‘say_whee’

>>> help(say_whee)
Help on function say_whee in module whee:

say_whee()

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
13
Q

What is the decorator template?

A

import functools

def decorator(func):
    @functools.wraps(func)
    def wrapper_decorator(*args, **kwargs):
        # Do something before
        value = func(*args, **kwargs)
        # Do something after
        return value
    return wrapper_decorator
How well did you know this?
1
Not at all
2
3
4
5
Perfectly