High level function & Nested function:

What is function:

def name():
    print('I am Blake')
    return 2**2
name() 
# recall the function
print(name)                                           
 # show the memory address of the function(save the  content of the function)
print(name())
# show the return value of the function  
I am Blake       
# the result after the function is recalled                                          
<function name at 0x101f21e18>
# show the memory address of the function
I am Blake
4
recall the function and return the value of the function
‘name’ is the name of the function, it define what the function should do
‘()’ is used to recall the function
The name of function can be variables, and used in other function
>>> x = abs
>>> x(-10)
10
>>> abs(-10)
10
>>> x = abs(-10)
>>> x
10
>>> x = abs                          # the name of function can be variables, and recalled by other name
>>> x(-199)
199
>>> x
<built-in function abs>

High level function:use the name of the function to be recalled in other function
def code(func):   # receive the parameter (func = name)
    print("%s Let's write some codes!"%func())    # recall the function ‘func(name)’
def name():
    print('My name is Blake!')
code(name)  # Take the name of the function as variables, and transfer it to the function ‘coade’
My name is Blake!
None Let's write some codes!
Nested function: write other functions in the function
def code():
    print("Let's write some codes!")
    def name():
        print('My name is Blake!')
    name()
code()  execute the function ‘code’ the first, in the process of executing it, the function ‘name’ will be executed.

Decorator***
Sept one:

def outer():
    def inner():   # nested function
        print('I am handsome boy!!!')
    return inner   # inner = outer()
foo = outer()
# We can get: foo=inter, and foo()=inter()
foo() # use another name to recall inter,
# that means foo take the place of inter, and foo()=inter()
I am handsome boy!!!

Step two: to understand ‘closure’
def outer():
    x = 'Blake'
    def inner():
        print(x)
    return inner
foo = outer()
foo() # Infact, foo() is doing what the function inner does, but the value 'x' still can be remenbered, it's called closure
Blake

Step three:
def outer(x):
    def inner():
        print(x+' & Alice')
    return inner
foo = outer('Blake') # send parameter to  'outer', then to inner
foo()                           # foo()=inner()
# sept one: send parameter to outer
# sept two: execute the function inner, but borrow the name of 'foo'
# change another way to understand: foo() is a self-defined function
Blake & Alice

Step four:
def outer(x):
    def inner():
        print('We always the function')
        x()   # inner=outer(x)=decrator, decraotor()=inner()=x()
        print('Blake and Alice, haha...')
    return inner
def foo():      # This time, 'foo' is function
    print('We will see who is the famous men in the city!')
    return 1
decorator = outer(foo)  # take 'foo' as parameter, and send it to 'outer'
decorator()  # =inner()
We always the function
We will see who is the famous men in the city!
Blake and Alice, haha...

Step five:
def outer(x):
    def inner():
        print('We always the function')
        x()   # inner=outer(x)=decrator, decraotor()=inner()=x()
        print('Blake and Alice, haha...')
    return inner
def foo():      
    print('We will see who is the famous men in the city!')
    return 1
foo = outer(foo)  # we use 'foo' to take the place of 'decorator'
foo()   
# we just use foo to cheat, and make the function looks more simple
# infact the name 'foo' is not the name function 'foo'
get the same result

Step six:
def outer(x):
    def inner():
        print('We always the function')
        x()
        print('Blake and Alice, haha...')
    return inner
@outer  # @outer is foo=outer(foo)***
def foo():
    print('We will see who is the famous men in the city!')
    return 1
foo()
Now, if ‘foo’ have many parameters, how we send to the function ‘outer’

Step seven:
import time
def outer(func):
    def inner(*args):
        start_time = time.time()
        func(*args)
        end_time = time.time()
        print('Run time is %s'%(end_time-start_time))
    return inner
@outer  # @outer is foo=outer(foo)
def foo(x,y):
    result = x**y
    print('x**y = %s'%result)
    return 1
foo(2,3)
x**y = 8
Run time is 5.507469177246094e-05
Now, we use *args and **kargs  to send many parameters and dictionaries to function outer