Skip to content

Instantly share code, notes, and snippets.

@lisp3r
Created February 26, 2020 06:14
Show Gist options
  • Save lisp3r/54e74f4c334f3cdd80c070ef985ceec7 to your computer and use it in GitHub Desktop.
Save lisp3r/54e74f4c334f3cdd80c070ef985ceec7 to your computer and use it in GitHub Desktop.
Notes about functions, closures and decorators in Python

Functional Python

Function in function

def inc(x):
    return x + 1

def dec(x):
    return x - 1

def operate(func, x):
    return func(x)
>>> operate(inc, 6)
7
>>> operate(dec, 6)
5

Nested functions

def is_called():
    def is_returned():
        print("Hello")
    return is_returned()
>>> is_called()
Hello

Also is_returned object can be returned from is_called instead of calling it:

def is_called():
    def is_returned():
        print("Hello")
    return is_returned # WITHOUT parenthesis
>>> new = is_called()
>>> new()
Hello

nonlocal

nonlocal is used to declare that a variable inside a nested function is not local to it, meaning it lies in the outer inclosing function. If we need to modify the value of a non-local variable inside a nested function, then we must declare it with nonlocal. Otherwise a local variable with that name is created inside the nested function.

def outer_function():
    a = 5
    def inner_function():
        nonlocal a
        a = 10
        print("Inner function: ",a)
    inner_function()
    print("Outer function: ",a)
>>> outer_function()
Inner function:  10
Outer function:  10

Closures

returning a nested fuction object
nonlocal variable

The method of binding data to a function without actually passing them as parameters is called closure. It is a function object that remembers values in enclosing scopes even if they are not present in memory.

def logger(func):
    def log_func(*args):
        print('Running "{}" with arguments {}'.format(func.__name__, args))
        print(func(*args))
    return log_func

def inc(x):
    return x + 1

def dec(x):
    return x - 1
>>> inc_logger = logger(inc)
>>> dec_logger = logger(dec)
>>> inc_logger(5)
Running "inc" with arguments (5,)
6
>>> dec_logger(5)
Running "dec" with arguments (5,)
4

Under the hood

Decorators

closures

Functions and methods are called callable as they can be called.
In fact, any object which implements the special method __call__() is termed callable. So, in the most basic sense, a decorator is a callable that returns a callable.
Basically, a decorator takes in a function, adds some functionality and returns it.

Add some syntax sugar in logger example above to complete its transformation into a decorator:

@logger
def inc(x):
    return x + 1

@logger
def dec(x):
    return x - 1
>>> inc(5)
Running "inc" with arguments (5,)
6
>>> dec(5)
Running "dec" with arguments (5,)
4

With defined parameters:

def smart_divide(func):
   def inner(a,b):
      print("I am going to divide {} and {}".format(a, b))
      if b == 0:
         print("Can't divide")
         return
      return func(a,b)
   return inner

@smart_divide
def divide(a,b):
    return a/b
>>> divide(2,5)
I am going to divide 2 and 5
0.4
>>> divide(2,0)
I am going to divide 2 and 0
Can't divide

Chaining Decorators

def star(func):
    def inner(*args, **kw):
        print("*" * 10)
        func*args, **kw()
        print("*" * 10)
    return inner

def percent(func):
    def inner(*args, **kw):
        print("%" * 10)
        func*args, **kw()
        print("%" * 10)
    return inner
@star
@percent
>>> def printer(msg):
...     print(msg)
>>> printer("Hello")
**********
%%%%%%%%%%
Hello
%%%%%%%%%%
**********
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment