目录
1. 函数嵌套
Python中以函数为作用域,在作用域中定义的相关数据只能被当前作用域或子作用域使用
1.1 函数在作用域中
其实,函数也是定义在作用域中的数据,在执行函数时候,也同样遵循:优先在自己作用域中寻找,没有则向上一接作用域寻找,例如:
# 1. 在全局作用域定义了函数func
def func():
print("你好")
# 2. 在全局作用域找到func函数并执行。
func() # 你好
# 3.在全局作用域定义了execute函数
def execute():
print("开始")
# 优先在当前函数作用域找func函数,没有则向上级作用域中寻找。
func()
print("结束")
# 4.在全局作用域执行execute函数
execute() # 开始 你好 结束
1.2 函数定义的位置
上述示例中的函数均定义在全局作用域,其实函数也可以定义在局部作用域,这样函数被局部作用域和其子作用于中调用(函数的嵌套)
def func():
print("沙河高晓松")
def handler():
print("昌平吴彦祖")
def inner():
print("朝阳大妈")
inner()
func()
print("海淀网友")
handler() # 昌平吴彦祖 朝阳大妈 沙河高晓松 海淀网友
大多数情况下我们都会将函数定义在全局,不会嵌套着定义函数。不过,当我们定义一个函数去实现某功能,想要将内部功能拆分成N个函数,又担心这个N个函数放在全局会与其他函数名冲突时(尤其多人协同开发)可以选择使用函数的嵌套
# 示例代码
"""
生成图片验证码的示例代码,需要提前安装pillow模块(Python中操作图片中一个第三方模块)
pip3 install pillow
"""
import random
from PIL import Image, ImageDraw, ImageFont
def create_image_code(img_file_path, text=None, size=(120, 30), mode="RGB", bg_color=(255, 255, 255)):
""" 生成一个图片验证码 """
_letter_cases = "abcdefghjkmnpqrstuvwxy" # 小写字母,去除可能干扰的i,l,o,z
_upper_cases = _letter_cases.upper() # 大写字母
_numbers = ''.join(map(str, range(3, 10))) # 数字
chars = ''.join((_letter_cases, _upper_cases, _numbers))
width, height = size # 宽高
# 创建图形
img = Image.new(mode, size, bg_color)
draw = ImageDraw.Draw(img) # 创建画笔
def get_chars():
"""生成给定长度的字符串,返回列表格式"""
return random.sample(chars, 4)
def create_lines():
"""绘制干扰线"""
line_num = random.randint(*(1, 2)) # 干扰线条数
for i in range(line_num):
# 起始点
begin = (random.randint(0, size[0]), random.randint(0, size[1]))
# 结束点
end = (random.randint(0, size[0]), random.randint(0, size[1]))
draw.line([begin, end], fill=(0, 0, 0))
def create_points():
"""绘制干扰点"""
chance = min(100, max(0, int(2))) # 大小限制在[0, 100]
for w in range(width):
for h in range(height):
tmp = random.randint(0, 100)
if tmp > 100 - chance:
draw.point((w, h), fill=(0, 0, 0))
def create_code():
"""绘制验证码字符"""
if text:
code_string = text
else:
char_list = get_chars()
code_string = ''.join(char_list) # 每个字符前后以空格隔开
# Win系统字体
# font = ImageFont.truetype(r"C:\Windows\Fonts\SEGOEPR.TTF", size=24)
# Mac系统字体
# font = ImageFont.truetype("/System/Library/Fonts/SFNSRounded.ttf", size=24)
# 项目字体文件
font = ImageFont.truetype("MSYH.TTC", size=15)
draw.text([0, 0], code_string, "red", font=font)
return code_string
create_lines()
create_points()
code = create_code()
# 将图片写入到文件
with open(img_file_path, mode='wb') as img_object:
img.save(img_object)
return code
code = create_image_code("a2.png")
print(code)
1.3 函数嵌套引发的作用域问题
- 优先在自己的作用域找,自己没有就去上级作用域。
- 在作用域中寻找值时,要确保此时此刻值是什么。
- 分析函数的执行,并确定函数
作用域链
。(函数嵌套)
基于内存和执行过程分析作用域
name = "小明"
def run():
name = "小红"
def inner():
print(name)
inner()
run()
name = "小明"
def func():
name = "小红"
def inner():
print(name)
return inner
v1 = func()
v1()
v2 = func()
v2()
name = "小明"
def run():
name = "小红"
def inner():
print(name)
return [inner, inner, inner]
func_list = run()
func_list[2]()
func_list[1]()
funcs = run()
funcs[2]()
funcs[1]()
练习题
- 分析代码写结果
name = '小明'
def func():
def inner():
print(name)
res = inner()
return res
v = func()
print(v)
- 分析代码写结果
name = '小明'
def func():
def inner():
print(name)
return "小红"
res = inner()
return res
v = func()
print(v)
- 分析代码写结果
def func():
name = '小明'
def inner():
print(name)
return '小红'
return inner
v1 = func() # 先执行func() -> v1 = return inner -> v1 = inner函数的内存地址
data = v1() # data = inner()
print(data) # 输出 小明,返回值 小红
- 分析代码写结果
def func():
name = '小明'
def inner():
print(name)
return '小红'
return inner
v1 = func() # 先执行func() -> v1 = inner
data = v1() # v1() = inner()
print(data) # 输出 小明,返回值 小红
v2 = func()() # 先执行func()() -> v2 = inner()
print(v2) # 输出 小明,返回值 小红
- 分析代码写结果
def func(name):
# name="小明"
def inner():
print(name)
return '小红'
return inner
# func(name='小王') -> inner() -> inner() 中 print(name)中没有name的值会去上一级去找,func(name='小王')
v1 = func('小王')()
print(v1) # 输出 小王,返回值 小红
v2 = func('小李')()
print(v2) # 输出 小李,返回值 小红
- 分析代码写结果
def func(name=None):
if not name:
name = '小明'
def inner():
print(name)
return '小红'
return inner
v1 = func()() # func(None),name = '小明' ,inner() ->print('小明'),返回值为小红
v2 = func('小李')() # func(name='小李),inner() -> print('小李'),返回值为小红
print(v1, v2)
2. 闭包
闭包,简而言之就是将数据封装在一个包(区域)中,使用时再去里面取。(本质上闭包是基于函数嵌套搞出来一个特殊嵌套)
- 闭包函数 = 名称空间与作用域 + 函数嵌套 + 函数对象
- 核心点:名字的查找关系是以函数定义阶段为准的
- ''闭'' 指的是该函数的内嵌函数
- ''包'' 函数指的是该函数包含对外出函数作用域名字的引用,(不是对全局作用域)
- 闭包应用场景1:封装数据防止污染全局
name = "小明"
def f1():
print(name, age)
def f2():
print(name, age)
def f3():
print(name, age)
def f4():
pass
- 闭包应用场景2:封装数据封到一个包里,使用时再取
def task(arg): # 1.定义task函数
def inner(): # 2.定义内部函数
print(arg) # 4.内部函数获取到task函数的参数
return inner # 3.闭包,函数返回内部函数内存地址
v1 = task(11) # 5.执行函数 task(11),得到 inner函数的内存地址 print(task(11).__name__)
v1() # 11 # 6.执行 inner()函数,输出arg的实参11
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]() # 33
import os
import requests
from concurrent.futures.thread import ThreadPoolExecutor
""" 视频链接解析: http://jumengfang.com/video"""
"""并行,多线程,并发下载视频 多线程,多个人"""
# 线程池10个人,10线程
POOL = ThreadPoolExecutor(10)
def task(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
# 下载完成之后,Python的多线程内部会执行的函数
def outer(file_name):
def done(arg): # 必须有一个参数,就是下载完成后的返回值 return res.content 下载视频的二进制内容
content = arg.result() # 视频内容 # submit后就有一个future对象,future对象就有result方法,调用result就获取到task函数的返回值
if not os.path.exists('files'):
os.makedirs('files')
file = os.path.join('files', file_name)
with open(file, mode='wb') as file_object:
file_object.write(content)
return done
# 三个视频信息
video_list = [
("带土1.mp4", "https://aweme.snssdk.com/aweme/v1/play/?video_id=v0d00fg10000c820qnrc77u1ofqasjdg&ratio=720p&line=0"),
("带土2.mp4", "https://aweme.snssdk.com/aweme/v1/play/?video_id=v0d00fg10000c4tlq83c77u8tcoaf3cg&ratio=720p&line=0"),
("带土3.mp4", "https://aweme.snssdk.com/aweme/v1/play/?video_id=v0300fg10000c8s5sbrc77ubpv4as270&ratio=720p&line=0")
]
for item in video_list:
print(item)
# 去线程池取一个人,让这个人执行去执行task函数,然后继续for循环
# POOL.submit里面执行的必须是函数
# 这个future对象不是下载完成的值,是临时的一个特殊的对象,译文:将来的,未来的
future = POOL.submit(task, url=item[1])
future.add_done_callback(outer(item[0])) # 当执行完成task函数(下载完成)之后自动执行某个函数
3. 装饰器
装饰器的作用:在不修改原始函数代码的情况下,为其添加额外的功能或修改其行为。
3.1 示例1
def func():
print("我是func函数")
value = (11,22,33,44)
return value
result = func()
print(result)
上面的函数,在不修改函数源码的前提下,实现在函数执行前和执行后分别输入 before
和 after
- 原始方式
def func():
print("before")
print("我是func函数")
value = (11, 22, 33, 44)
print("after")
return value
func()
- 闭包方式 (功能一样,但代码变的复杂了)
def func():
print("我是func函数")
value = (11, 22, 33, 44)
return value
def outer(origin):
def inner():
print('before')
res = origin()
print("after")
return res
return inner
func = outer(func)
result = func()
- 装饰器方式
"""
# python中支持的一个特殊的语法,在某个函数上方使用:
@wrapper
def index():
pass
# python 内部会自动执行,函数名wrapper(index),执行完后,再将结果赋值给 index
# 等于执行了 index = wrapper(index)
"""
def outer(origin): # 1. 定义装饰器函数
def inner(): # 2. 定义装饰器内层函数
print('before') # 4. 执行被装饰函数前的操作
res = origin() # 5. 调用\执行被装饰的函数
print("after") # 6. 执行被装饰函数后的操作
return res # 7. 返回被装饰函数的返回值
return inner # 3. 闭包,函数返回内部函数内存地址
@outer # 8. func = outer(func)
def func(): # 9. 定义func函数
print("我是func函数")
value = (11, 22, 33, 44)
return value
func()
3.2 示例2
def func1():
print("我是func1函数")
value = (11, 22, 33, 44)
return value
def func2():
print("我是func2函数")
value = (11, 22, 33, 44)
return value
def func3():
print("我是func3函数")
value = (11, 22, 33, 44)
return value
func1()
func2()
func3()
在上面的3个函数执行前和执行后分别输入 before
和 after
- 原始方式
def func1():
print('before')
print("我是func1函数")
value = (11, 22, 33, 44)
print("after")
return value
def func2():
print('before')
print("我是func2函数")
value = (11, 22, 33, 44)
print("after")
return value
def func3():
print('before')
print("我是func3函数")
value = (11, 22, 33, 44)
print("after")
return value
func1()
func2()
func3()
- 装饰器方式
"""
def outer(origin):
def inner():
print("before 110")
res = origin() # 调用原来的func函数
print("after")
return res
return inner
@outer # func1 = outer(func1)
def func1():
print("我是func1函数")
value = (11, 22, 33, 44)
return value
@outer # func2 = outer(func2)
def func2():
print("我是func2函数")
value = (11, 22, 33, 44)
return value
@outer # func3 = outer(func3)
def func3():
print("我是func3函数")
value = (11, 22, 33, 44)
return value
func1()
func2()
func3()
"""
"""优化以支持多个参数的情况"""
def outer(origin):
def inner(*args, **kwargs):
print("before 110")
res = origin(*args, **kwargs) # 调用原来的func函数
print("after")
return res
return inner
@outer
def func1(a1):
print("我是func1函数")
value = (11, 22, 33, 44)
return value
@outer
def func2(a1, a2):
print("我是func2函数")
value = (11, 22, 33, 44)
return value
@outer
def func3(a1):
print("我是func3函数")
value = (11, 22, 33, 44)
return value
func1(1)
func2(11, a2=22)
func3(999)
3.3 装饰器作用
- 装饰器示例
def outer(origin):
def inner(*args, **kwargs):
# 执行前
res = origin(*args, **kwargs) # 调用原来的func函数
# 执行后
return res
return inner
@outer
def func():
pass
func()
- 实现原理:基于@语法和函数闭包,将原函数封装在闭包中,然后将函数赋值为一个新的函数(内层函数),执行函数时再在内层函数中执行闭包中的原函数。
- 实现效果:可以在不改变原函数内部代码 和 调用方式的前提下,实现在函数执行和执行扩展功能。
- 适用场景:多个函数系统 统一在
执行前后
自定义一些功能 - 重点:默写装饰器
# 默写模板 (需要默写下来)
def outer(func): # func = # 被装饰的函数
def wrapper(*args, **kwargs):
# 执行被装饰的函数前动作
res = func(*args, **kwargs) # 执行被装饰的函数
# 执行被装饰的函数后动作
# 返回被装饰的函数的返回值
return res
# 闭包,函数返回内部函数内存地址
return wrapper
3.4 伪应用场景
在以后编写一个网站时,如果项目共有100个页面,其中99个是需要登录成功之后才有权限访问,就可以基于装饰器来实现
"""
pip install flask
基于第三方模块Flask(框架)快速写一个网站:
"""
from flask import Flask
app = Flask(__name__)
def auth(func):
def inner(*args, **kwargs):
# 在此处,判断如果用户是否已经登录,已登录则继续往下,未登录则自动跳转到登录页面。
return func(*args, **kwargs)
return inner
@auth
def index():
return "首页"
@auth
def info():
return "用户中心"
@auth
def order():
return "订单中心"
def login():
return "登录页面"
app.add_url_rule("/index/", view_func=index, endpoint='index')
app.add_url_rule("/info/", view_func=info, endpoint='info')
app.add_url_rule("/order/", view_func=order, endpoint='order')
app.add_url_rule("/login/", view_func=login, endpoint='login')
app.run()
3.5 functools
装饰器实际上就是将原函数更改为其他的函数,然后再此函数中再去调用原函数
def auth(func):
def inner(*args, **kwargs):
"""这是装饰器xx"""
res = func(*args, **args)
return res
return inner
def login():
"""这是实现登录功能的函数"""
pass
@auth
def logout():
"""这是实现退出功能的函数"""
pass
# 单独执行函数
print(login.__name__) # 获取函数名 login
print(login.__doc__) # 获取函数里的注释 "这是实现登录功能的函数"
# 执行带装饰器的函数
print(logout.__name__) # 获取函数名 # inner
print(logout.__doc__) # 获取函数里的注释 # 这是装饰器xx
# 引入 functools
import functools
def auth(func):
@functools.wraps(func) # inner.__name__ = func.__name__ # functools.wraps()将函数伪装的更像原函数
def inner(*args, **kwargs):
"""这是装饰器xx"""
res = func(*args, **args)
return res
return inner
def login():
"""这是实现登录功能的函数"""
pass
@auth
def logout():
"""这是实现退出功能的函数"""
pass
# 执行带装饰器的函数
print(logout.__name__) # 获取函数名 # logout
print(logout.__doc__) # 获取函数里的注释 # 这是实现退出功能的函数
其实,一般情况下不用 functools
也可以实现装饰器的基本功能,但后期在项目开发时,不加 functools
会出错(内部会读取__name__
,且__name__
重名的话就报错),所以要规范起来自己的写法
# 带functools的装饰器模板
import functools
def auth(func):
@functools.wraps(func)
def inner(*args, **kwargs):
"""注释内容"""
res = func(*args, **kwargs) # 执行原函数
return res
return inner
4. 总结
- 函数可以定义在全局、也可以定义另外一个函数中(函数的嵌套)
- 学会分析函数执行的步骤(内存中作用域的管理)
- 闭包,基于函数的嵌套,可以将数据封装到一个包中,以后再去调用。
- 装饰器
- 实现原理:基于@语法和函数闭包,将原函数封装在闭包中,然后将函数赋值为一个新的函数(内层函数),执行函数时再在内层函数中执行闭包中的原函数。
- 实现效果:可以在不改变原函数内部代码 和 调用方式的前提下,实现在函数执行和执行扩展功能。适用场景:多个函数系统统一在执行前后自定义一些功能。
- 函数命名不但要做到见名知意,且尽量使用动词命名,不使用名字
5. 练习题
- 为以下所有函数编写一个装饰器,添加上装饰器后可以实现:执行func时,先执行func函数的内部代码,再输出 "alert"
import functools
def outer(origin):
@functools.wraps(origin)
def inner(*args,**kwargs):
res = origin(*args,**kwargs)
print('alert')
return res
return inner
@outer
def func1(a1):
return a1 + '傻瓜'
@outer
def func2(a1,a2):
return a1 + a2 + '傻蛋'
print(func1('1')) # 输出 alert,返回值 1傻瓜
print(func2('1','2')) # 输出 alert,返回值 12傻蛋
- 请为以下所有函数编写一个装饰器,添加上装饰器后可以实现:将被装饰的函数执行5次,并将每次执行函数的结果按照顺序放到列表中,最终返回列表
import random
import functools
def auth(origin):
@functools.wraps(origin)
def inner(*args, **kwargs):
num_list = []
for i in range(5):
res = origin(*args, **kwargs)
num_list.append(res)
return num_list
return inner
@auth
def func():
return random.randint(1, 4)
result = func() # 内部自动执行5次,并将每次执行的结果追加到列表最终返回给result
print(result)
- 请为以下函数编写一个装饰器,添加上装饰器后可以实现: 检查文件所在路径(文件)是否存在,如果不存在自动创建文件夹(保证写入文件不报错)
import os
def auth(origin):
def inner(**kwargs): # path就是origin传入的参数
folder_path = os.path.dirname(kwargs['file']) # 获取路径的上级目录
# folder_path = path.rsplit('/',1)[0]
if not os.path.exists(folder_path):
os.mkdir(folder_path)
res = origin(**kwargs)
return res
return inner
@auth
def write_user_info(**kwargs):
with open(kwargs['file'], mode='w', encoding='utf-8') as f:
f.write(kwargs['text'])
write_user_info(file='files/file1/info.txt', text='这是一个测试文件')
- 看代码写结果
def get_data():
scores = []
def inner(val):
scores.append(val)
print(scores)
return inner
get_data()(10) # [10]
get_data()(20) # [20]
get_data()(30) # [30]
func = get_data()
func(10) # [10]
func(20) # [10, 20]
func(30) # [10, 20, 30]
- 看代码写结果
name = "小明"
def foo():
print(name)
def func():
name = "root"
foo()
func()
- 看代码写结果
def 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) # 10个inner函数 # func(0) func(1) func(2)...
v1 = data_list[0](11, 22) # 等于 print(func(0)(11,22))
print(v1) # 33
v2 = data_list[2](33, 11) # 等于 print(func(2)(11,22))
print(v2) # 46
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· Manus的开源复刻OpenManus初探
· AI 智能体引爆开源社区「GitHub 热点速览」
· 三行代码完成国际化适配,妙~啊~
· .NET Core 中如何实现缓存的预热?