一、函数对象

    1、函数是第一类对象,即函数可以当作数据传递

        1)可以被引用

        2)可以当作参数传递

        3)返回值可以是函数

        4)可以当作容器类型的元素

    2、利用该特性,优雅的取代多分支的if

def foo():
    print('foo')
def bar():
    print('bar')

dic = {
    '1': foo,
    '2': bar,
}
while True:
    print('''
    请输入你的选择:
    1、foo
    2、bar
    ''')
    choice = input('>>: ').strip()
    if choice in dic:
        dic[choice]()

    

二、函数嵌套

    1、函数的嵌套调用

def max(x,y):
        return x if x > y else y
def max4(a,b,c,d):
        res1=max(a,b)
        res2=max(res1,c)
        res3=max(res2,d)
        return res3
print(max4(1,2,3,4))

    2、函数的嵌套定义

def f1():
    def f2():
        def f3():
            print('from f3')
        f3()
    f2()
f1()

f3()#报错,为何?请看下一小节


三、名称空间与作用域

    1、什么是名称空间?

        名称空间:存放名字的地方,三种名称空间,(x=1,1存放于内存中,那名字x存放在哪里呢?名称空间正是存放名字x与1绑定关系的地方)

    2、名称空间的加载顺序

        举个例子:python test.py

        1)python解释器先启动,因而首先加载的是:内置名称空间

        2)执行test.py文件,然后以文件为基础,加载全局名称空间

        3)在执行文件的过程中如果调用函数,则临时产生局部名称空间(这类名称空间会随着函数执行的结束而被释放)

        加载顺序总结:内置名称空间--->全局名称空间--->局部名称空间

    3、名字的查找顺序

        查找顺序总结:局部名称空间--->全局名称空间--->内置名称空间

        注意:在全局无法查看局部的,在局部可以查看全局的,如下示例:

# max=1
def f1():
    # max=2
    def f2():
        # max=3
        print(max)
    f2()
f1()
print(max)

    4、作用域

        1)作用域即范围

        - 全局范围(内置名称空间与全局名称空间属于该范围):全局存活,全局有效

        - 局部范围(局部名称空间属于该范围):临时存活,局部有效

        2)作用域关系是在函数定义阶段就已经固定的,与函数的调用位置无关,如下

x = 1
def f1():
    def f2():
        print(x)
    return f2
    
x = 100
def f3(func):
    x = 2
    func()
    
x = 10000
f3(f1())

        3)查看作用域:globals(),locals()

            LEGB 代表名字查找顺序: locals -> enclosing function -> globals -> __builtins__

            locals 是函数内的名字空间,包括局部变量和形参

            enclosing 外部嵌套函数的名字空间(闭包中常见)

            globals 全局变量,函数定义所在模块的名字空间

            builtins 内置模块的名字空间

    5、global与nonlocal关键字

        1)global

        2)nonlocal:当内部函数需要修改外部函数的变量

        两者的区别:

        第一:两者的功能不同。

            global关键字修饰变量后标识该变量是全局变量,对该变量进行修改就是修改全局变量;

            nonlocal关键字修饰变量后标识该变量是上一级函数中的局部变量,如果上一级函数中不存在该局部变量,nonlocal位置会发生错误(最上层的函数使用nonlocal修饰变量必定会报错)。

        第二:两者使用的范围不同。

            global关键字可以用在任何地方,包括最上层函数中和嵌套函数中,即使之前未定义该变量,global修饰后也可以直接使用,

            nonlocal关键字只能用于嵌套函数中,并且外层函数中定义了相应的局部变量,否则会发生错误(见第一)。

     

四、闭包函数

    1、什么是闭包?

        闭包函数的定义,需要满足一下三个条件

        1、函数嵌套

        2、内部函数引用(或者改变)外部函数的变量(非全局变量)(内部函数包含对外部作用域而非全局作用域的引用  )

        3、外部函数将内部函数名作为返回值

#非闭包函数,临时空间随着函数的结束而关闭,每一次调用都是一个新的临时空间
def func1(s1):
    n = 1
    n += s1
    print(n)

func1(5)
func1(5)
func1(5)
func1(5)
#打印出来的都是6

#闭包函数,临时空间不会随着函数的结束而关闭,每一次调用都是一个在已有的临时空间操作,回收由垃圾回收机制
def outer(s1):
    n = 1
    def inner():
        nonlocal n  #要对变量修改需要引用
        n += s1
        print(n)
    return inner

res=outer(5)
res()
res()
res()
res()
#打印出来的是6789

    2、闭包的意义与应用

        闭包的意义:返回的函数对象,不仅仅是一个函数对象,在该函数外还包裹了一层作用域,这使得,该函数无论在何处调用,优先使用自己外层包裹的作用域

        应用:装饰器(最典型的影响)
                延迟计算(原来我们是传参,现在我们是包起来)

from urllib.request import urlopen
def index(url):
        def get():
                return urlopen(url).read()
        return get
baidu=index('http://www.baidu.com')
print(baidu().decode('utf-8'))

五、装饰器

    装饰器就是闭包函数的一种应用场景

    1、为何要用装饰器

        开放封闭原则:对修改封闭,对扩展开放

    2、什么是装饰器

        装饰他人的器具,本身可以是任意可调用对象,被装饰者也可以是任意可调用对象。

        强调装饰器的原则:

            a、不修改被装饰对象的源代码 

            b、不修改被装饰对象的调用方式

        装饰器的目标:在遵循1和2的前提下,为被装饰对象添加上新功能

    3、装饰器语法

        被装饰函数的正上方,单独一行

@deco1
@deco2
@deco3
def foo():
    pass

foo = deco1(deco2(deco3(foo)))

    4、装饰器的使用

        1)无参函数使用装饰器

import time
def timmer(f):
    def inner():
        start_time = time.time()
        f()
        end_time = time.time()
        print('此函数的执行时间是%s' % (end_time-start_time))
    return inner

@timmer #实质上func1 = timmer(func1)  给函数使用装饰器,会自动向下多读一行,使用函数名
def func1():
    time.sleep(0.5)
    print('函数function1')

# func1 = timmer(func1)
func1()

        以上函数的执行步骤:

            a、执行timmer(func1)函数,把func1传给f,执行结果返回inner

            b、将inner赋值给func1变量

            c、执行func1(),即inner()

        2)有参函数使用装饰器

import time
def timmer(f):
    def inner(*args,**kwargs):
        start_time = time.time()
        f(*args,**kwargs)
        end_time = time.time()
        print('此函数的执行时间是%s' % (end_time-start_time))
    return inner

@timmer #实质上func1 = timmer(func1)  给函数使用装饰器,会自动向下多读一行,使用函数名
def func1(a):
    time.sleep(0.5)
    print('函数function1,参数是%s' % (a) )

@timmer
def func2(x,y):
    time.sleep(0.7)
    print('x值是%s, y值是%s' % (x,y))

func1(9)
func2(10,5)

        3)给有返回值的函数使用装饰器

import time
def timmer(f):
    def inner(*args,**kwargs):
        start_time = time.time()
        res=f(*args,**kwargs)
        end_time = time.time()
        print('此函数的执行时间是%s' % (end_time-start_time))
        return res
    return inner

@timmer #实质上func1 = timmer(func1)  给函数使用装饰器,会自动向下多读一行,使用函数名
def func1(a):
    time.sleep(0.5)
    print('函数function1,参数是%s' % (a) )
    return a

print(func1(9))

        4)给函数使用带有参数的装饰

import time
def outter(a,b):
    print(a,b)
    def timmer(f):
        def inner(*args,**kwargs):
            start_time = time.time()
            res=f(*args,**kwargs)
            end_time = time.time()
            print('此函数的执行时间是%s' % (end_time-start_time))
            return res
        return inner
    return timmer

@outter(1,2) #实质上func1 = timmer(func1)  给函数使用装饰器,会自动向下多读一行,使用函数名
def func1(a):
    time.sleep(0.5)
    print('函数function1,参数是%s' % (a) )
    return a

print(func1(9))

        5)有参函数使用有参装饰器

def auth(driver='file'):
    def auth2(func):
        def wrapper(*args, **kwargs):
            name = input("user: ")
            pwd = input("pwd: ")
            if driver == 'file':
                if name == 'egon' and pwd == '123':
                    print('login successful')
                    res = func(*args, **kwargs)
                    return res
            elif driver == 'ldap':
                print('ldap')
        return wrapper
    return auth2

@auth(driver='file')
def foo(name):
    print(name)

foo('egon')

        6)给一个函数使用多个装饰器

i

          7)总结:!!!

                a、装饰器本质上就是闭包函数

                b、装饰器是在不改变函数的函数体和函数的调用方式的情况下为已有函数增加函数功能的      

#装饰器的模板
def outter(f):
    def inner(*args,**kwargs):
            该部分可以添加代码块
        res=f(*args,**kwargs)
            该部分可以添加代码块
        return res
    return inner