About Closure and Decorator in Python with examples.

Closure

Closure means function wraps function. When you want to save the state of a function but don’t want to write a decent class, use closure.

1
2
3
4
5
6
7
def new_counter():
total = 0
def count():
nonlocal total
total += 1
return total
return count

then you can use it to count student amount of different classes:

1
2
3
4
5
math = new_counter()
tennis = new_counter()

math(), math(), math(), math(), math(), math()
tennis(), tennis()

When closure search a variables, Local -> enclosing -> global -> builtin.
In the example, total stores in enclosing level.

Decorator

Decorator wraps a closure with extra function, but won’t change the inner function, it returns a closure.

Example: Logger

1
2
3
4
5
6
7
8
9
10
11
12
13
14
def my_logger(func):
def wrapper(*args, **kwargs):
print(f"calling {func.__name__}")
return func(*args, **kwargs)
return wrapper

@my_logger
def get_total(ls):
return sum(ls)

ls = [1, 2, 3, 4, 5, 6, 7, 8, 9]
get_total(ls)
# calling get_total
# 45

But decorated function’s function will become pretty confused about its identity. get_total.__name__ will return 'wrapper'.
Use @functools.wraps to preserve original function info.

1
2
3
4
5
6
7
8
9
10
11
12
13
import functools
def my_logger(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
print(f"calling {func.__name__}")
return func(*args, **kwargs)
return wrapper

@my_logger
def get_total(ls):
return sum(ls)

get_total.__name__ # 'get_total'

Example: Fibonacci

1
2
3
4
5
6
7
8
9
10
11
12
13
class MyCache(object):
def __init__(self, func):
self.func = func
self.cache = {}

def __call__(self, *args):
if not args in self.cache:
self.cache[args] = self.func(*args)
return self.cache[args]

@MyClass
def fib(n):
return 1 if n <= 1 else fib(n - 1) + fib(n - 2)

Example: Singleton

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
def singleton(cls):
instances = {}
def wrapper(*args, **kwargs):
if cls not in instances:
instances[cls] = cls(*args, **kwargs)
return instances[cls]
return wrapper

@singleton
class MyClass:
def __init__(self, name):
self.name = name

def submit(self):
return f"{self.name} Submitted!!!"

c1 = MyClass('hello')
c2 = MyClass('world')

c1 == c2 # True
c1.name, c2.name # 'hello', 'hello'

So the next step, is refactoring my report & log modules in the next sprint.