# Take away reading Primer On Python Decorators
https://realpython.com/primer-on-python-decorators/
Real python is my favorite material learning python. Decorator is one of the mid-level to advance topic of python and used extensively in production.
## part one
Part one of python decorator, main takeaways is function in python is First class object. And hence you can pass function in parameter.
The inner function is called closure and have access to variable in the enclosing scope.
The functools.wraps update the wrapper function with passed in function's function name, docstring, arguments list, etc.
```python=
import functools
def some_decorator(func):
@functools.wraps(func)
def wrapper(*args,**kwargs):
print('do something before exec the func')
value = func(*args,*kwargs)
return value
return wrapper
def some_function():
print("I am a function")
return 1
some_function = some_decorator(some_function)
some_function()
```
Usually, people would use the syntax sugar
```python=
import functools
def some_decorator(func):
@functools.wraps(func)
def wrapper(*args,**kwargs):
print('do something before exec the func')
value = func(*args,**kwargs)
return value
return wrapper
@some_decorator
def some_function():
print("I am a function")
return 1
some_function()
```
# part two fancy decorator
### Nested decorator
```python=
import functools
def some_decorator(func):
@functools.wraps(func)
def wrapper(*args,**kwargs):
print('do something before exec the func')
value = func(*args,**kwargs)
return value
return wrapper
def do_twice(func):
def wrap(*args,**kwargs):
func()
func()
return wrap
@some_decorator
@do_twice
def some_function():
print("I am a function")
return 1
some_function()
```
This is equivalent to some_decorator(do_twice(some_function))
### decorator with parameters
This is an example taken from https://realpython.com/primer-on-python-decorators/
```python=
import functools
def repeat(num_times):
def decorator_repeat(func):
@functools.wraps(func)
def wrapper_repeat(*args, **kwargs):
for _ in range(num_times):
value = func(*args, **kwargs)
return value
return wrapper_repeat
return decorator_repeat
@repeat(num_times=4)
def greet(name):
print(f"Hello {name}")
greet("World")
```
Note that after @ we need a function that is a decorator (callable and return a function object), hence we have function repeat with parameter num_times, so @repeat(num_times=4) is equivalent to @decorator_repeat only that the latter's closure now has num_times, since the decorator_repeat returned by repeat has access to num_times.
## Definiton of closure
>The closure isn’t the inner function itself but the inner function along with its enclosing environment.
Since closure also include the enclosing environment, we can make factory decorator with state closure
```python=
def power(exponent):
def wrapper(base):
return base ** exponent
return wrapper
square = power(2)
cube = power(3)
print(square(5))
print(cube(5))
```
>For a class instance to be callable, you implement the special .__call__() method:
### class decorator
```python=
import functools
class CountCalls:
def __init__(self,func):
functools.update_wrapper(self,func)
self.func = func
self.num_calls = 0
def __call__(self,*args, **kwargs):
self.num_calls += 1
print(self.num_calls, self.func.__name__)
return self.func(*args,**kwargs)
@CountCalls
def say_whee():
print("whee!")
say_whee()
say_whee()
```
## Some main takeaway:
- You can make a class callable by having __call__ and use it as decorator. Use functools.update_wrapper(self,func) inside __init__.
- Use @functools.wraps(func) to update information on inner function.
- Use Closure to remember state
- repr(object) Return a string represetanta