飞行的猪哼哼

  博客园  :: 首页  :: 新随笔  :: 联系 :: 订阅 订阅  :: 管理

一:闭包的定义:
目的:保证变量不会因为外部函数调用而销毁。
1:在函数嵌套的前提下,内部函数使用了外部函数的变量,外部函数返回了内部函数,我们把这个使用外部函数变量的内部函数称为闭包。
2:闭包的形成条件:
函数嵌套 //内部函数使用了外部函数的变量 // 外部函数返回了内部函数对象。
3:简单闭包:

def func_out(num1):
    def func_inner(num2):
        result = num1 + num2;
        print("结果是:",result)
    return func_inner

f = func_out(1)

f(2)
f(3)

4:闭包的作用:闭包可以保存外部函数内的变量,不会随着外部函数调用完而销毁。
5:为什么不会随着外部函数的调用而销毁?
答:变量当自己的引用计数等于0的时候就会被销毁。当将1赋值给num1的时候num1的引用计数是1,然后定义fun_inner,由于fun_inner里面用到了num1(result = num1 + num2;),所以num1的引用计数变成了2,最后函数调用完成时,销毁所有变量(本质是引用变量减一),所以num1引用计数又变成了1。所以外层函数调用完,变量是不会销毁的。
6:修改闭包内使用的外部变量。nonlocal关键字:
nonlocal是使用外层函数内的变量,如果不写,实际上是内层定义的自己新的变量与外层的变量是无关的。

# 定义一个外部函数
def func_out(num1):

    # 定义一个内部函数
    def func_inner(num2):
        # 这里本意想要修改外部num1的值,实际上是在内部函数定义了一个局部变量num1
        nonlocal num1  # 告诉解释器,此处使用的是 外部变量a
        # 修改外部变量num1
        num1 = 10
        # 内部函数使用了外部函数的变量(num1)
        result = num1 + num2
        print("结果是:", result)

    print(num1)
    func_inner(1)
    print(num1)

    # 外部函数返回了内部函数,这里返回的内部函数就是闭包
    return func_inner

# 创建闭包实例
f = func_out(1)
# 执行闭包
f(2)

二:装饰器:特殊的闭包。:区别是只有一个变量,并且变量是函数类型。
1:定义:给已有函数增加额外功能的函数。
2:功能特点:
不修改已有函数的源代码
不修改已有函数的调用方式
给已有函数增加额外的功能

3:简单代码:

def check(fn):
    def inner():
        print("请先登录")
        fn()
    return inner


def comment():
    print("发表评论")

comment = check(comment)
comment()

分析:开始comment 指向本函数,check(comment)让fn和comment 同时指向本函数,当check返回inner的时候,comment重新指向修饰函数的内层函数。此时(comment指向修饰函数内层函数,fn指向了本函数)。当我们调用comment()函数的时候是执行修饰函数的内层函数,而内层函数又可以调用本函数,所以上实现了在本函数上扩展功能,但是不修改本函数。
4:语法糖写法:@修饰函数

# 添加一个登录验证的功能
def check(fn):
    print("装饰器函数执行了")
    def inner():
        print("请先登录....")
        fn()
    return inner

# 使用语法糖方式来装饰函数
@check
def comment():
    print("发表评论")


comment()

5:利用修饰器计算函数的执行时间:

import time

def out_hello(fn):
    def inner_hello():
        before = time.time()
        fn()
        after = time.time()
        print("函数所用时间是:", after-before)
    return inner_hello

@out_hello
def print_hello():
    for i in range(100000):
        print("hello:%d" % i)

ph = print_hello()

6:装饰器能不能携带参数?可以返回值吗?

def out_count(fn):
    def inner_count(num1,num2):
        print("正在做加法其他的功能。。。")
        sum = fn(num1,num2)
        print("做完加法做其他的。。。。。")
        return sum

    return inner_count

@out_count
def count_num(num1,num2):
    sum = num1 + num2
    print("num1 + num2=",sum)
    return sum

sum = count_num(1,2)
print("最后结果: %d" % sum)

分析装饰过程:@out_count 本质是:count_num = out_count(count_num), 首先开始的时候,count_num指向本函数,传参过去后,fn和count_num同时指向本函数,当装饰函数返回inner_count的时候,count_num重新指向装饰内层函数。
调用过程:count_num(1,2),先调用装饰内层函数,将1,2传递给inner_count(num1,num2) ,当执行到 sum = fn(num1,num2)的时候,inner_count(num1,num2) 中的num1 和num2赋值给fn(num1,num2)中的num1和num2,而fn此时指向本函数,所以又将fn中的num1,num2传递给count_num(num1,num2)。当count_num执行完后将sum返回给装饰器函数内层函数中的sum,最后这个内层函数又把自己的sum,返回给主程序数中的sum。

注意:由于传参先传给装饰器内层函数,然后内存函数调用外层传进来的函数,最后调用本函数。所以参数个数,内层函数,内层中的外层传入的函数,都要和本函数保持一致。

7:装饰器可以携带不定长参数吗?


def loging(fn):
    def inner(*args,**kwargs):
        print("----输出执行函数前的日志信息")
        # 注意这里是用到拆包技术,这里是把inner(参数中的)变成1,2,3,x=100,y=200这种格式
        sum = fn(*args,**kwargs)
        print("-----输出函数执行后的日志信息")
        return sum

    return inner

@loging
def sum_num(*args ,**kwargs):

    sum = 0
    for i in args:
        sum = sum + i
    for i in kwargs.values():
        sum = sum + i
    return sum

sum = sum_num(1,2,3,x=100,y=200)
print("求和之后的结果是:", sum)

8:多个装饰器如何执行流程???


def add_div(fn2):
    def inner_div():
        return "<div>" + fn2() + "</div>"
    return inner_div


def add_p(fn1):
    def inner_p():
        return "<p>"+fn1()+ "</p>"
    return inner_p


@add_div
@add_p
def content():
    return "人生苦短,我用python!"

main_content = content()
print(main_content)



输出结果:

<div><p>人生苦短,我用python!</p></div>

分析:装饰过程:

**@add_p 相当于:content = add_p(content),开始时,content指向本函数,当调用add_p(content) 后,fn1也指向本函数,然后add_p返回给content自己的内层函数,所以此时content指向第一个修饰器的内层函数。@add_div 相当于 content = add_div(content),此时开始content 指向第一个修饰器的内层函数,通过传参将第一个修饰器的内层函数传递给fn2,然后add_div把自己的内层函数返回给content,所以content指向第二个修饰器的内层函数。**

总结:最终content指向第二个修饰器的内层函数,fn2指向第一个修饰器的内层函数,fn1指向本函数 content()。

调用过程:调用过程:content()实际上等于inner_div(),所以先调用inner_div,然后执行 fn2(),实际上是inner_p(),然后执行fn1实际上是content()函数。然后从content函数开始向上返回,content向上返回给inner_p:"人生苦短,我用python!",inner_p组合后向上返回给inner_div:<p>人生苦短,我用python!</p>,然后inner_div向上返回给主程序<div><p>人生苦短,我用python!</p></div>

9:带有参数的装饰器:


def logging(flag):

    def out_num(fn):

        def inner_num(x,y):
            if flag == "+":
                print("这是加法操作的准备工作")
                sum2 = fn(x,y)
                return sum2

            elif flag == "-":
                print("这是减法操作的准备工作")
                mul2 = fn(x,y)
                return mul2

        return inner_num
    return out_num



@logging("+")
def add_num(a,b):
    sum = a+b
    return sum


@logging("-")
def mul_num(a,b):
    mul = a-b
    return mul

sum = add_num(1,2)
print(sum)

mul = mul_num(2,1)
print(mul)

分析:@logging("+"):可以拆分成两部分:logging("+")传递给装饰器外层的函数,返回一个装饰器类型的对象out_num 然后@和out_num 又拼接成语法糖。
问题:为什么在修饰器外面增加一个函数,内部就能访问传入参数呢?
由于这个外层函数传入变量,返回内层函数,内层函数使用外层函数变量,所以修饰器实际上是一个闭包了,而披上的是外层函数,闭包内可以访问外层函数变量。
10:类装饰器:

class Check(object):
    def __init__(self ,fn):
        self.__fn = fn;
    def __call__(self, *args, **kwargs):
        print("发表评论前的准备工作。。。")
        self.__fn()
    
@Check
def comment():
    print("发表评论")

comment()

@Check 等价于 comment = Check(comment), 所以需要提供一个init方法,并多增加一个fn参数。
要想类的实例对象能够像函数一样调用,需要在类里面使用call方法,把类的实例变成可调用对象(callable),也就是说可以像调用函数一样进行调用。
在call方法里进行对fn函数的装饰,可以添加额外的功能。

posted on 2020-08-18 17:03  飞行的猪哼哼  阅读(38)  评论(0)    收藏  举报