【Python安全攻防基础篇:闭包和解释器】看了无数篇博客,终于搞懂逻辑和应用场景了!


在看这部分内容之前,我需要大家了解函数的概念。这个应该不难吧,有一点点编程基础的都应该知道。


闭包
什么是闭包?

说白了就是函数嵌套的时候,内部函数用了外部函数的变量,那个内部函数就叫闭包

比如这样的一段代码中,那个fun2就是闭包。

def fun1(start=0):
    count = [start]
    def fun2():
        count[0] += 1
        return count[0]
    return fun2

quote = fun1()
print(quote())

因此可以知道,闭包需要满足的三个条件:

  1. 存在于嵌套关系的函数中
  2. 内部函数引用了外部函数的变量
  3. 外部函数将内部函数名作为返回值返回
闭包有什么用?

说白了就是让闭包活下来。

空说也没有用,看下面这一段代码:

def fun():
	a = 2
	
quote = fun()
print(quote)

运行肯定报错!

为什么?因为我没有返回那个变量a,那么变量a就会在fun()函数执行完被销毁。

再看这一段:

def fun():
    a = 2
    return a

quote = fun()	# quote = a
print(quote)

运行结果:

2

为什么?因为fun()的返回值是a,然后a又被赋值给quote,因此a被返回,也就被保留了下来。

闭包是同样的道理,看文章开头的那段代码:

def fun1(start=0):
    count = [start]
    def fun2():
        count[0] += 1
        return count[0]
    return fun2

quote = fun1()
# 执行完fun1()这个函数后,返回的是fun2这个函数名。
# 所以此时相当于`quote = fun2`

print(quote())	
# 这次再执行quote(),就相当于执行fun2()。	
# fun2就这样被保留了下来

但是,我现在理解了闭包的逻辑了,然后呢?闭包有什么用?

我上边那代码直接这样不就好了吗:

def fun1(start=0):
    return start + 1

print(fun1())

到这里不知道大家看没看出区别:

  • return 变量、return 函数,本质上是没有区别的
  • return 变量只能返回一个变量,return 函数可以返回的东西就多了,或者说可以留住的东西就多了
  • return 函数,这个函数将会一直存活,这个函数内部的变量也会一直存活,那么能做的事情就多了…

这就是闭包的意义所在!它把数据私有化,完成了一次封装,相当于面向对象

还是用开头的例子举例:

def fun1(start=0):
    count = [start]
    def fun2():
        count[0] += 1
        # 我们把代码做一些修改,直接在内部函数打印变量值
        print(count[0])
    return fun2

quote = fun1()	# quote = fun2
quote()			# fun2()
quote()
quote()

猜猜结果会是什么?这下是不是更加深刻地理解了闭包的作用了?

图解

  • 本来变量a的存活域只是在outer函数中
  • 然后inner函数将a私有化,然后通过outer返回inner的方式,整个inner被调到了外边
  • 就像函数返回一个数字或字符串一样,这个inner会一直存活在整个程序中,同时inner中的变量也都会在整个程序中存活
装饰器
什么是装饰器?

**在不影响原有函数功能的情况下,添加新的功能。**就是装饰器的作用,此话怎讲呢?这要从闭包说起!

因为,如果一个闭包函数,的外层函数,的参数是一个函数的话,这就构成了装饰器

一个最基本的闭包结构是这样的:

def outer():
    /* codes */
    def inner():
        /* codes */
    return inner

如果此时,我们外部有个函数test()需要扩展功能,就需要在test()前边使用装饰器,这里需要注意3点:

  1. 在test()前边加上@outer,这个的意思就是把test作为参数传入outer()中
  2. 外部函数outer需要接收一个函数作为参数
  3. 内部函数inner需要将fun()作为参数返回

所以代码实现应该是这样的:

def outer(fun):	# outer需要传入一个fun作为参数
    /* codes */
    def inner():
        /* codes */
        return fun()	# inner返回fun()
    return inner

@outer	# test前加上 @outer
def test():
    print('test!')

这就是装饰器!

那么,return funreturn fun()有什么区别呢?

  1. return fun返回的是函数对象,函数名
  2. return fun()返回的是一个函数调用,也就是说在返回的过程中fun()将被调用,其实真正意义上返回的是fun()执行之后的结果
装饰器是如何工作的?
def outer(fun):
    /* codes */
    def inner():
        /* codes */
        return fun()
    return inner

# @outer的意思就是,在执行test()函数前,先执行一遍outer()函数,并且把test作为参数传入outer
# 根据闭包的知识我们知道,outer的执行结果是inner,而此时inner的执行结果是fun(),所以
# @outer -> outer() -> inner() -> fun()
@outer
def test():
    print('test!')
    
test()

把代码补全一下:

def outer(fun):
    print('outer!')
    def inner():
        print('inner!')
        return fun()
    return inner

@outer
def test():
    print('test!')

test()

看下执行结果:

outer!
inner!
test!

是不是就像上边说的,执行过程就是这个outer() -> innner() -> fun(),先死记硬背下来!

装饰器工作原理

当我们执行test()时,会分为两个过程:

首先会执行@outer
	等价于 test = outer(test) = inner
然后会执行test()
	等价于 inner()
    而inner()的结果是返回fun(),fun()是函数调用的意思,所以会执行一下fun()

所以,根据这个过程,就可以知道函数具体的执行过程了:

先执行outer(),打印"outer!",然后返回inner函数对象
再执行inner(),打印"inner!",然后返回fun()函数调用
再执行fun(),也就是test(),打印"test!"

这就好了呀,你就记住执行过程就是outer() -> innner() -> fun()不就完了!

装饰器带参数

这个直接用一个例子来看一下,因为原理和不带参数的是一样的,这里我要求你们背下来两个块内容:语法格式、执行过程。

大家其实不用对“背”这个字那么敏感,我也没说真的是死记硬背对不对,原理上边已经说的很清楚了。

有时候之所以掌握不好一部分内容,就是太忌讳背代码这件事了。背下来不代表不理解它的逻辑,反而还会帮助你理解。

会背了用起来就熟练了,用着熟练了理解就更透彻了,真的是这样。

题目

  1. 写两个函数,功能就是打印一句话“不管你几岁,赶紧关注我!”;
  2. 在不改变原有函数的基础上,根据年龄再打印一句话,年龄小于14岁打印“未满14岁,不能谈恋爱!”,另一个打印“已满14岁,可以谈恋爱了”;

解答

题目的意思就是有两个函数,一个函数代表小于14岁,另一个代表大于14岁,首先这两个函数都是有原有功能的,但是博主非常不要脸,让它们原有的功能全都是打印一句话“不管你几岁,都快点关注我,顺便再点个赞,或者关注下同名公众号「白帽子续命指南」!”,真是,太不要脸了,但也没办法。

先把这两个函数写出来:

def young():
    print('不管你几岁,都快点关注我!')

def old():
    print('不管你几岁,都快点关注我!')
    
young()
old()

第二个要求就是判断年龄,然后各打印一句话,还是不难,但需要用装饰器完成。

根据装饰器的原理,我们知道,它会先执行外层函数,再执行内层函数,最后执行被装饰函数。而且被返回到整个程序作用域的肯定是内层函数,所以这个功能应该在内层函数中实现。

据此,写出闭包函数(注意装饰器的格式):

def outer(fun):
    def inner():
        if age < 14:
            print('不满14岁,不能谈恋爱!')
        if age > 14:
            print('已满14岁,建议好好学习!')
        return fun()
    return inner

age = 10	# 不知道在哪传age,用不能没有age,就随便设一个age

@outer
def young():
    print('不管你几岁,都快点关注我!')

@outer
def old():
    print('不管你几岁,都快点关注我!')
    
young()
old()

差不多就是这样,但是那个age怎么传进去呢?这里就要科普一个知识点了:

装饰器是可以传进去参数的,它默认会把被装饰函数作为参数传进去,那如果我们再手动传一个参数进去,应该也是可以的,试试:

def outer(fun,age):	# 这里把默认的参数,还有手动上传的参数全都写上
    def inner():
        if age < 14:
            print('不满14岁,不能谈恋爱!')
        if age >= 14:
            print('已满14岁,建议好好学习!')
        return fun()
    return inner

@outer(age=10)	# 手动传第一个参数进去
def young():
    print('不管你几岁,都快点关注我!')

@outer(age=18)
def old():
    print('不管你几岁,都快点关注我!')
    
young()
old()

看起来应该没问题,运行一下:

Traceback (most recent call last):
  File "/Users/c0ny100/Documents/python_codes/my_python/闭包和解释器.py", line 12, in <module>
    @outer(age)
TypeError: outer() missing 1 required positional argument: 'age'

报错了!

说明不可以这样传参,那怎么传?

**再套一层!**在outer外边再套一层,那一层就专门用来接收参数,然后outer和inner的逻辑不变,这样就可以了!

def parm(age):	# 再来一层,专门用来接收参数
    
    # 这部分逻辑都是不变的
    def outer(fun):
        def inner():
            if age < 14:
                print('不满14岁,不能谈恋爱!')
            if age >= 14:
                print('已满14岁,建议好好学习!')
            return fun()
        return inner
    
    return outer	# 别忘了,因为又加了一层,所以得返回一下outer
					# 而且除了inner返回的是函数调用以外,其余返回的都是函数名

@parm(age=10)
def young():
    print('不管你几岁,都快点关注我!')

@parm(age=18)
def old():
    print('不管你几岁,都快点关注我!')

young()
old()

运行一下,完美!OK,把上边这个成品背下来!

不满14岁,不能谈恋爱!
不管你几岁,都快点关注我!
已满14岁,建议好好学习!
不管你几岁,都快点关注我!
被装饰函数带参数

这个更简单,把参数传到inner里就完了

举个例子,本来test函数是计算a+b的值,但现在我们想知道(a+1)+(b+1)的值,于是使用装饰器来实现:

def outer(fun):
    def inner(a, b):
        a += 1
        b += 1
        return fun(a, b)
    return inner

@outer
def test(a, b):
    print(a + b)

test(1, 1)

运行结果:

4

有没有大牛给我解释一下,为什么执行的是test(1, 1),结果却是4?也就是为什么1+1=4?[doge]

这个看起来没什么用,但当我们调用某些数学模块,你想改变某些值,又不想改变他本身的算法的时候,还是可以用到的。比如有一个骚操作时,在inner里把a、b的类型改了:

def inner(a, b):
    a = 'a'
    b = 'b'
    return fun(a, b)

这都是可以的。

除此之外还有调用多个装饰器以及被装饰函数有返回值的情况,原理都一样,就不细说了。遇到了自己百度吧,应该可以看懂了。

总结

闭 包:把内层函数整个保留下来

装饰器:从最外层函数开始执行,直到被装饰函数执行完

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值