posts - 24,comments - 0,views - 1022

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]()

练习题

  1. 分析代码写结果
name = '小明'


def func():
    def inner():
        print(name)

    res = inner()
    return res


v = func()
print(v)
  1. 分析代码写结果
name = '小明'


def func():
    def inner():
        print(name)
        return "小红"

    res = inner()
    return res


v = func()
print(v)
  1. 分析代码写结果
def func():
    name = '小明'

    def inner():
        print(name)
        return '小红'

    return inner


v1 = func()  # 先执行func() -> v1 = return inner -> v1 = inner函数的内存地址
data = v1()  # data = inner()
print(data)  # 输出 小明,返回值 小红
  1. 分析代码写结果
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)  # 输出 小明,返回值 小红
  1. 分析代码写结果
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)  # 输出 小李,返回值 小红
  1. 分析代码写结果
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个函数执行前和执行后分别输入 beforeafter

  • 原始方式
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. 总结

  1. 函数可以定义在全局、也可以定义另外一个函数中(函数的嵌套)
  2. 学会分析函数执行的步骤(内存中作用域的管理)
  3. 闭包,基于函数的嵌套,可以将数据封装到一个包中,以后再去调用。
  4. 装饰器
  • 实现原理:基于@语法和函数闭包,将原函数封装在闭包中,然后将函数赋值为一个新的函数(内层函数),执行函数时再在内层函数中执行闭包中的原函数。
  • 实现效果:可以在不改变原函数内部代码 和 调用方式的前提下,实现在函数执行和执行扩展功能。适用场景:多个函数系统统一在执行前后自定义一些功能。
  1. 函数命名不但要做到见名知意,且尽量使用动词命名,不使用名字

5. 练习题

  1. 为以下所有函数编写一个装饰器,添加上装饰器后可以实现:执行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傻蛋
  1. 请为以下所有函数编写一个装饰器,添加上装饰器后可以实现:将被装饰的函数执行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)
  1. 请为以下函数编写一个装饰器,添加上装饰器后可以实现: 检查文件所在路径(文件)是否存在,如果不存在自动创建文件夹(保证写入文件不报错)
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='这是一个测试文件')
  1. 看代码写结果
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]
  1. 看代码写结果
name = "小明"

def foo():
    print(name)

def func():
    name = "root"
    foo()

func()
  1. 看代码写结果
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
posted on   龙泉寺老方丈  阅读(13)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· Manus的开源复刻OpenManus初探
· AI 智能体引爆开源社区「GitHub 热点速览」
· 三行代码完成国际化适配,妙~啊~
· .NET Core 中如何实现缓存的预热?
< 2025年3月 >
23 24 25 26 27 28 1
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 1 2 3 4 5

点击右上角即可分享
微信分享提示