day13 闭包及装饰器

"""
今日内容:
    1、函数的嵌套定义及必包
    2、global 与 nonlocal 关键字
    3、开放封闭原则及装饰器
"""

"""
一、函数的嵌套定义及闭包
    -- 在一个函数中定义另一个函数的过程叫做函数的嵌套定义
    
    -- 为什么要使用函数的嵌套定义?
        -- 在一个函数中要是用另一个函数中的变量,就在原函数中嵌套定义这个新函数
        -- 但是嵌套定义后函数内部的函数就只能在函数的原函数的内部使用,在函数外部不能直接访问该函数
        -- 如果想要访问新函数,可以将新函数作为返回值传递到函数的内部
        -- 这样,在函数的外部重新定义一个与新函数重名的变量就可以像内部函数一样进行加()使用
    
    -- 闭包(closure):在函数内部定义的,在函数外部也可以进行调用的函数就叫做闭包函数。
"""

"""
二、global 与 nonlocal 关键字
    -- global 关键字
        -- 当想要将函数内部的变量升级为全局变量时,就可以使用global关键字
        -- 使用方法为:在函数内部需要升级变量作用域的变量上方加入  global 变量名
            def fn1():
                num = 10 
        -- 如果想要将num的作用域提升为全局,那么就需要在函数的上方加入 global关键字
            def fn1():
                global num
                num = 10 
        -- 这样函数num的作用域就是全局了
        
    -- nonlocal 关键字
        -- 当只想要将嵌套函数内部的变量提升一个等级,那么就需要使用到nonlocal 关键字,但是使用nonlocal关键字使需要注意两个问题
            -- 使用nonlocal关键字提升变量等级时,此关键字必须在父类函数中存在,不存在就会报错
            -- 如果要提升变量等级,此变量所在的函数必须有父类函数,如果没有也会报错
    
    -- global 与 nonlocal关键字的使用注意事项:
    
        -- 如果想要统一 outer 及 inner中的 num ,那么就将两个 num 都是用global进行声明,而不能使用nonlocal进行链式升级。
        num = 0
        def outer():
            global num
            num = 1
            def inner():
                global num
                num = 10
                print(num)
            inner()
            print(num)
        outer()
        print(num)
        
"""

"""
三、开放封闭原则及装饰器
    -- 开放封闭原则
        -- 开放原则:可以为代码添加新的功能
        -- 封闭原则:
            -- 不能修改源代码
            -- 不能修改调用方式
    
    -- 装饰器
        装饰器是一个满足开放封闭原则的闭包的应用
        
    -- 多装饰器的加载顺序:
        -- 先进后出,后进先出
"""
# 装饰器的推导:
"""
想要给原函数添加新功能,但是不能修改原函数及函数的调用方式
原函数:
def fn1():
    print("插花")

需要添加的功能: 观赏

"""


# 闭包函数的推导

"""
def fn1():
    num = 10

def fn2():
    print(num)
"""

# 第一种方法:将fn1的num作为返回值传递给外部,再用名字为num的变量进行接收 --> 这样只是有一个找了一个重名的变量,并不是原来的变量
# 第二种方法:使用global关键字将num变为全局变量,这样,如果全局变量中有num这个变量,会重新赋值
# 第三种方法:将函数fn2定义到函数fn1的内部,这样fn2就可以使用fn1的变量了

# 综上使用第三种方法进行验证
"""第一步:想要在一个函数内部使用另一个函数的内部变量 --> 将新函数放入到原函数的内部
def fn1():
    num = 10
    def fn2():
        print(num)  
-- 使用这种方法时,弊端就是不能在全局中调用fn2函数,只能在fn1中调用  
"""

""" 第二步:想要在全部中访问函数fn2:可以将fn2作为fn1函数的返回值,在外界接收后记性调用

def fn1():
    num = 10
    def fn2():
        print(num)
    return fn2

# res = fn1()
fn2 = fn1()
fn2()

-- 使用这种方法可以将函数的返回值使用变量进行接收,变量可以使用任意合法的变量名接收,可以使用n,m等名字,也可以使用fn2进行接收
-- 当使用fn2进行接收时,此时使用fn2()就可以执行,跟直接调用原来的函数相同。这样fn2既可以使用num 又可以在外界进行调用。
-- 这个在函数内部定义的函数就叫做闭包函数(closure)
"""


# 装饰器推导
"""第一步:在函数的内部调用原来的函数

def fn2():
    print("观赏")
    fn1()
    
fn1 = fn2
fn1()  # RecursionError: maximum recursion depth exceeded while calling a Python object

-- 这样看似可以使用,但是在调用时会报错,原因是函数执行过程中,执行时先到 def fn2() -->  fn1 = fn2 此时 fn1中内存地址与fn2中相同 -->
fn1()实际上就是运行fn2() --> 进入到fn2函数内部 --> 打印 插花 --> 运行fn1(),但是此时fn1=fn2,还是会调用fn2函数 --> 死循环
    
"""

"""第二步:对第一步进行更新,将fn1首先赋值给第三方变量temp,此时就不会进入死循环
temp = fn1

def fn2():
    print("观赏")
    temp()
    
fn1 = fn2
fn1()

-- 此时就是装饰器
-- 但是此装饰器分为了三部分,temp = fn1 ,def fn2 ,fn1=fn2,如果在三部分中间不小心对temp进行了重新赋值,就会破坏这个装饰器
-- 为了时装饰器更加的统一,需进行进一步进化
"""

"""第三步:将装饰器的三部分进化为两部分
-- 由于fn2函数的内部需要使用temp,此时可以将这两部分封装为一个函数
def outer():
    temp = fn1
    def fn2():
        print("观赏")
        temp()
    return fn2
    
fn1 = outer()
fn1()

-- 此时,装饰器已经进一步完善,但是如果fn1是有参数及返回值的呢?需要将函数的参数及返回值传递到装饰器内部
"""

"""第四步:给原函数添加参数

def fn1(name):
    print("插花%s"%name)

def outer():
    temp = fn1

    def fn2(name):
        print("观赏")
        temp(name)

    return fn2


fn1 = outer()
name = input("花: ")
fn1(name)

-- 函数还是有可能也有返回值,需要将返回值传递到装饰器的内部
"""

"""第五步:给装饰器传递返回值

def fn1(name):
    print("插花%s"%name)
    return name

def outer():
    def fn2(name):
        print("观赏")
        res = temp(name)
        return res
    return fn2

fn1 = outer()
name = input("花: ")
print(fn1(name))

-- 此时,函数既有参数也有返回值了,已经无限接近于完美了,但是还有一个小问题,这个装饰器只能给固定的一个函数fn1使用(添加新功能),但是如果想给
其它的函数也使用这个装饰器呢?

"""

"""第六步:将装饰器变成可以通用的装饰器
    -- 如果想要将装饰器变为通用的装饰器,此时有两个方面需要修改,一个是被装饰的函数本身,另一个是函数的参数列表不固定
    
def fn1(name):
    print("插花%s"%name)
    return name

def outer(func):
    def fn2(*args,**kwargs):
        print("观赏")
        res = func(*args,**kwargs)
        return res
    return fn2

fn1 = outer(fn1)
name = input("花: ")
print(fn1(name))

-- 函数本身的变化可以将函数通过outer传入
-- 函数的参数列表的变化,可以使用可变长形参进行传递(*args,**kwargs)

"""

"""
根据上述步骤,可以总结一个装饰器的公式:

    def outer(func):
        def inner(*args,**kwargs):
            pass  # 要添加的代码块
            res = func(*args,**kwargs)
            pass # 需要添加的功能代码
            return res
        return inner

"""

"""第七步:在python中提供了一种语法糖,可以将整个装饰器与被装饰的函数统一为一个整体
    -- 这种语法就是使用 @装饰器名称 添加到被装饰函数的上方
    -- 使用这种语法糖需要将装饰器函数写到被装饰函数的上方

"""
原文地址:https://www.cnblogs.com/lice-blog/p/10787383.html