day12 函数高级

课程目标:掌握函数嵌套、闭包、装饰器等高级知识点。
今日概要:
- 函数的嵌套
- 闭包
- 装饰器
上述内容均属于函数部分必备知识,以后开发时直接和间接都会使用,请务必理解(重在理解,不要去死记硬背)。
1. 函数嵌套
Python中以函数为作用域,在作用域中定义的相关数据只能被当前作用域或子作用域使用。
1 | NAME = "武沛齐" |
1.1 函数在作用域中
其实,函数也是定义在作用域中的数据,在执行函数时候,也同样遵循:优先在自己作用域中寻找,没有则向上一接作用域寻找,例如:
1 | # 1. 在全局作用域定义了函数func |
此处,有一个易错点:作用域中的值在被调用时到底是啥?
情景1
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16def func():
print("你好")
func()
def execute():
print("开始")
func()
print("结束")
execute()
def func():
print(666)
func()情景2
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15def func():
print("你好")
func()
def execute():
print("开始")
func()
print("结束")
def func():
print(666)
func()
execute()
1.2 函数定义的位置
上述示例中的函数均定义在全局作用域,其实函数也可以定义在局部作用域,这样函数被局部作用域和其子作用于中调用(函数的嵌套)。
1 | def func(): |
到现在你会发现,只要理解数据定义时所存在的作用域,并根据从上到下代码执行过程进行分析,再怎么嵌套都可以搞定。
现在的你可能有疑问:为什么要这么嵌套定义?把函数都定义在全局不好吗?
其实,大多数情况下我们都会将函数定义在全局,不会嵌套着定义函数。不过,当我们定义一个函数去实现某功能,想要将内部功能拆分成N个函数,又担心这个N个函数放在全局会与其他函数名冲突时(尤其多人协同开发)可以选择使用函数的嵌套。
1 | def f1(): |
1 | def func(): |
1 | """ |
1.3 嵌套引发的作用域问题
基于内存和执行过程分析作用域。
1 | name = "武沛齐" |

1 | name = "武沛齐" |

1 | name = "武沛齐" |

三句话搞定作用域:
- 优先在自己的作用域找,自己没有就去上级作用域。
- 在作用域中寻找值时,要确保此次此刻值是什么。
- 分析函数的执行,并确定函数
作用域链。(函数嵌套)
练习题
分析代码,写结果
1
2
3
4
5
6
7
8
9
10
11
12
13name = '武沛齐'
def func():
def inner():
print(name)
res = inner()
return res
v = func()
print(v)
# 武沛齐
# None分析代码,写结果
1
2
3
4
5
6
7
8
9
10
11
12
13
14name = '武沛齐'
def func():
def inner():
print(name)
return "alex"
res = inner()
return res
v = func()
print(v)
# 武沛齐
# alex分析代码,写结果
1
2
3
4
5
6
7
8
9
10
11
12
13
14name = 'root'
def func():
def inner():
print(name)
return 'admin'
return inner
v = func()
result = v()
print(result)
# root
# admin分析代码,写结果
1
2
3
4
5
6
7
8
9
10
11
12
13
14def func():
name = '武沛齐'
def inner():
print(name)
return '路飞'
return inner
v11 = func()
data = v11()
print(data)
v2 = func()()
print(v2)分析代码,写结果
1
2
3
4
5
6
7
8
9
10
11
12def func(name):
# name="alex"
def inner():
print(name)
return 'luffy'
return inner
v1 = func('武沛齐')()
print(v1)
v2 = func('alex')()
print(v2)
1
2
3
4
5
6
7
8
9
10def func(name):
def inner():
print(name)
return 'luffy'
return inner
v1 = func('武沛齐')
v2 = func('alex')
v1()
v2()
分析代码,写结果
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15def func(name=None):
if not name:
name= '武沛齐'
def inner():
print(name)
return 'root'
return inner
v1 = func()()
v2 = func('alex')()
print(v1,v2)
# 武沛齐
# alex
# root root
2.闭包
闭包,简而言之就是将数据封装在一个包(区域)中,使用时再去里面取。(本质上 闭包是基于函数嵌套搞出来一个中特殊嵌套)
闭包应用场景1:封装数据防止污染全局。
1
2
3
4
5
6
7
8
9
10
11
12
13name = "武沛齐"
def f1():
print(name, age)
def f2():
print(name, age)
def f3():
print(name, age)
def f4():
pass1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17def func(age):
name = "武沛齐"
def f1():
print(name, age)
def f2():
print(name, age)
def f3():
print(name, age)
f1()
f2()
f3()
func(123)闭包应用场景2:封装数据封到一个包里,使用时在取。

1
2
3
4
5
6
7
8
9
10
11def task(arg):
def inner():
print(arg)
return inner
v1 = task(11)
v2 = task(22)
v3 = task(33)
v1()
v2()
v3()1
2
3
4
5
6
7
8
9
10
11
12def task(arg):
def inner():
print(arg)
return inner
inner_func_list = []
for val in [11,22,33]:
inner_func_list.append( task(val) )
inner_func_list[0]() # 11
inner_func_list[1]() # 22
inner_func_list[2]() # 331
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37""" 基于多线程去下载视频 """
from concurrent.futures.thread import ThreadPoolExecutor
import requests
def download_video(url):
res = requests.get(
url=url,
headers={
"user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.88 Safari/537.36 FS"
}
)
return res.content
def outer(file_name):
def write_file(response):
content = response.result()
with open(file_name, mode='wb') as file_object:
file_object.write(content)
return write_file
POOL = ThreadPoolExecutor(10)
video_dict = [
("东北F4模仿秀.mp4", "https://aweme.snssdk.com/aweme/v1/playwm/?video_id=v0300f570000bvbmace0gvch7lo53oog"),
("卡特扣篮.mp4", "https://aweme.snssdk.com/aweme/v1/playwm/?video_id=v0200f3e0000bv52fpn5t6p007e34q1g"),
("罗斯mvp.mp4", "https://aweme.snssdk.com/aweme/v1/playwm/?video_id=v0200f240000buuer5aa4tij4gv6ajqg")
]
for item in video_dict:
future = POOL.submit(download_video, url=item[1])
future.add_done_callback(outer(item[0]))
POOL.shutdown()
3.装饰器
现在给你一个函数,在不修改函数源码的前提下,实现在函数执行前和执行后分别输入 “before” 和 “after”。
1 | def func(): |
3.1 第一回合
你的实现思路:
1 | def func(): |
我的实现思路:
1 | def func(): |
处理返回值:
1 | def func(): |
3.2 第二回合
在Python中有个一个特殊的语法糖:
1 | def outer(origin): |
3.3 第三回合
请在这3个函数执行前和执行后分别输入 “before” 和 “after”
1 | def func1(): |
你的实现思路:
1 | def func1(): |
我的实现思路:
1 | def outer(origin): |
装饰器,在不修改原函数内容的前提下,通过@函数可以实现在函数前后自定义执行一些功能(批量操作会更有意义)。
优化
优化以支持多个参数的情况。
1 | def outer(origin): |
其中,我的那种写法就称为装饰器。
实现原理:基于@语法和函数闭包,将原函数封装在闭包中,然后将函数赋值为一个新的函数(内层函数),执行函数时再在内层函数中执行闭包中的原函数。
实现效果:可以在不改变原函数内部代码 和 调用方式的前提下,实现在函数执行和执行扩展功能。
适用场景:多个函数系统统一在 执行前后自定义一些功能。
装饰器示例
1
2
3
4
5
6
7
8
9
10
11
12
13
14def outer(origin):
def inner(*args, **kwargs):
# 执行前
res = origin(*args, **kwargs) # 调用原来的func函数
# 执行后
return res
return inner
def func():
pass
func()
伪应用场景
在以后编写一个网站时,如果项目共有100个页面,其中99个是需要登录成功之后才有权限访问,就可以基于装饰器来实现。
1 | pip3 install flask |
基于第三方模块Flask(框架)快速写一个网站:
1 | from flask import Flask |
基于装饰器实现的伪代码:
1 | from flask import Flask |
重要补充:functools
你会发现装饰器实际上就是将原函数更改为其他的函数,然后再此函数中再去调用原函数。
1 | def handler(): |
1 | def auth(func): |
1 | import functools |
其实,一般情况下大家不用functools也可以实现装饰器的基本功能,但后期在项目开发时,不加functools会出错(内部会读取__name__,且__name__重名的话就报错),所以在此大家就要规范起来自己的写法。
1 | import functools |
总结
函数可以定义在全局、也可以定义另外一个函数中(函数的嵌套)
学会分析函数执行的步骤(内存中作用域的管理)
闭包,基于函数的嵌套,可以将数据封装到一个包中,以后再去调用。
装饰器
实现原理:基于@语法和函数闭包,将原函数封装在闭包中,然后将函数赋值为一个新的函数(内层函数),执行函数时再在内层函数中执行闭包中的原函数。
实现效果:可以在不改变原函数内部代码 和 调用方式的前提下,实现在函数执行和执行扩展功能。
适用场景:多个函数系统统一在 执行前后自定义一些功能。
装饰器示例
1
2
3
4
5
6
7
8
9
10
11import functools
def auth(func):
def inner(*args, **kwargs):
"""巴巴里吧"""
res = func(*args, **kwargs) # 执行原函数
return res
return inner
作业
请为以下所有函数编写一个装饰器,添加上装饰器后可以实现:执行func时,先执行func函数内部代码,再输出 “after”
1
2
3
4
5
6
7
8
9def func(a1):
return a1 + "傻叉"
def base(a1,a2):
return a1 + a2 + '傻缺'
def foo(a1,a2,a3,a4):
return a1 + a2 + a3 + a4 + '傻蛋'请为以下所有函数编写一个装饰器,添加上装饰器后可以实现:将被装饰的函数执行5次,讲每次执行函数的结果按照顺序放到列表中,最终返回列表。
1
2
3
4
5
6
7
8import random
def func():
return random.randint(1,4)
result = func() # 内部自动执行5次,并将每次执行的结果追加到列表最终返回给result
print(result)请为以下函数编写一个装饰器,添加上装饰器后可以实现: 检查文件所在路径(文件件)是否存在,如果不存在自动创建文件夹(保证写入文件不报错)。
1
2
3
4
5
6def write_user_info(path):
file_obj = open(path, mode='w', encoding='utf-8')
file_obj.write("武沛齐")
file_obj.close()
write_user_info('/usr/bin/xxx/xxx.png')分析代码写结果:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15def get_data():
scores = []
def inner(val):
scores.append(val)
print(scores)
return inner
func = get_data()
func(10)
func(20)
func(30)看代码写结果
1
2
3
4
5
6
7
8
9
10
11
12
13name = "武沛齐"
def foo():
print(name)
def func():
name = "root"
foo()
func()看代码写结果
1
2
3
4
5
6
7
8
9
10
11
12
13name = "武沛齐"
def func():
name = "root"
def foo():
print(name)
foo()
func()看代码写结果
1
2
3
4
5
6
7
8
9
10
11
12
13def func(val):
def inner(a1, a2):
return a1 + a2 + val
return inner
data_list = []
for i in range(10):
data_list.append( func(i) )
print(data_list)看代码写结果
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17def func(val):
def inner(a1, a2):
return a1 + a2 + val
return inner
data_list = []
for i in range(10):
data_list.append(func(i))
v1 = data_list[0](11,22)
v2 = data_list[2](33,11)
print(v1)
print(v2)