Bootstrap

Python函数(09函数嵌套&闭包&装饰器&匿名函数)

 Python基础语法文章导航:

  1. Python基础(01初识数据类型&变量)
  2. Python基础(02条件&循环语句)
  3. Python基础(03字符串格式化&运算符&进制&编码)
  4. Python基础(04 基础练习题)
  5. Python数据类型(day05整型&布尔类型&字符串类型)
  6. Python数据类型(06列表&元组)
  7. Python数据类型(07集合&字典&浮点型&None)
  8. Python文件操作01(自动化测试文件相关操作)
  9. Python函数入门(08函数定义&参数&返回值)

目录

前言

一. 函数嵌套

1.函数在作用域中

2.函数定义的位置

3.嵌套引发的作用域问题

​编辑​

二.闭包

三.装饰器

1. 第一回合

2.第二回合

3. 第三回合

优化

重要补充:functools

四.匿名函数

总结:


前言

        本章的学习很重要,要深入理解并掌握。

一. 函数嵌套

        Python中以函数为作用域,在作用域中定义的相关数据只能被当前作用域或子作用域使用。

1.函数在作用域中

        其实,函数也是定义在作用域中的数据,在执行函数时候,也同样遵循:优先在自己作用域中寻找,没有则向上一级作用域寻找,例如:

# 1. 在全局作用域定义了函数func
def func():
    print("你好")
    
# 2. 在全局作用域找到func函数并执行。
func()

# 3.在全局作用域定义了execute函数
def execute():
    print("开始")
    # 优先在当前函数作用域找func函数,没有则向上级作用域中寻找。
    func()
    print("结束")

# 4.在全局作用域执行execute函数
execute()

# 你好
# 开始
# 你好
# 结束

此处,有一个易错点:作用域中的值在被调用时到底是啥?

        作用域中的值在被调用时,是通过作用域链、闭包和参数传递等机制来决定的。

 情景一:

def func():
    print("你好")
func()
def execute():
    print("开始")
    func()
    print("结束")
execute()
def func():
    print(666)
func()

# 你好
# 开始
# 你好
# 结束
# 666

情景二:

def func():
    print("你好")
func()
def execute():
    print("开始")
    func()
    print("结束")
def func():
    print(666)
func()
execute()
# 你好
# 666
# 开始
# 666
# 结束

2.函数定义的位置

        上述示例中的函数均定义在全局作用域,其实函数也可以定义在局部作用域,这样函数被局部作用域和其子作用于中调用(函数的嵌套)。

def func():
    print("沙河高晓松")

def handler():
    print("昌平吴彦祖")
    def inner():
        print("朝阳大妈")
        inner()
    func()
    print("海淀网友")
handler()

# 昌平吴彦祖
# 沙河高晓松
# 海淀网友

        其实,大多数情况下我们都会将函数定义在全局,不会嵌套着定义函数。不过,当我们定义一个函数去实现某功能,想要将内部功能拆分成N个函数,又担心这个N个函数放在全局会与其他函数名冲突时(尤其多人协同开发)可以选择使用函数的嵌套。

def func():
    def f1():
        pass

    def f2():
        pass
	f1()
    f2()

案例:

"""
生成图片验证码的示例代码,需要提前安装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)

3.嵌套引发的作用域问题

三句话搞定作用域:

  • 优先在自己的作用域找,自己没有就去上级作用域。

  • 在作用域中寻找值时,要确保此次此刻值是什么。

  • 分析函数的执行,并确定函数作用域链。(函数嵌套)

(1)案例一,理解作用域问题

name = "武沛齐"
def run():
    name = "alex"
    def inner():
        print(name)
    inner()
run()

#alex

(1)案例二,理解作用域问题

name = "武沛齐"
def run():
    name = "alex"
    def inner():
        print(name)
    return inner
v1 = run()
v1()
v2 = run()
v2()

#alex
#alex

(3)案例三,理解作用域问题

name = "武沛齐"

def run():
    name = "alex"
    def inner():
        print(name)
	return [inner,inner,inner]
    
func_list = run()
func_list[2]()
func_list[1]()

funcs = run()
funcs[2]()
funcs[1]()

#alex
#alex
#alex
#alex

二.闭包

        闭包,简而言之就是将数据封装在一个包(区域)中,使用时再去里面取。(本质上 闭包是基于函数嵌套搞出来一个中特殊嵌套)

        闭包应用场景1:封装数据防止污染全局。

name = "武沛齐"
age=19
def f1():
    print(name, age)

def f2():
	print(name, age)

def f3():
	print(name, age)
    
def f4():
    pass
def func(age):
    name = "武沛齐"

    def f1():
        print(name, age)

    def f2():
        print(name, age)

    def f3():
        print(name, age)
    f1()
    f2()
    f3()
func(123)
# 武沛齐 123
# 武沛齐 123
# 武沛齐 123

        闭包应用场景2:封装数据封到一个包里,使用时在取。

def task(arg):
    def inner():
        print(arg)
    return inner

v1 = task(11)
v2 = task(22)
v3 = task(33)
v1()
v2()
v3()

#11
#22
#33
def 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]() # 33

案例:

""" 基于多线程去下载视频 """
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()

三.装饰器

        现在给你一个函数,在不修改函数源码的前提下,实现在函数执行前和执行后分别输入 "before" 和 "after"。

def func():
    print("我是func函数")
    value = (11,22,33,44) 
    return value
    
result = func()
print(result)

1. 第一回合

def func():
    print("before")  
    print("我是func函数")
    value = (11,22,33,44)    
    print("after")    
    return value    
result = func()

        我的实现思路:

def func():
    print("我是func函数")
    value = (11, 22, 33, 44)
    return value

def outer(origin):
    def inner():
        print('inner')
        origin()
        print("after")
    return inner

func = outer(func)
result = func()
#inner
#我是func函数
#after

        处理返回值:

def func():
    print("我是func函数")
    value = (11, 22, 33, 44)
    return value

def outer(origin):
    def inner():
        print('inner')
        res = origin()
        print("after")
        return res
    return inner

func = outer(func)
result = func()
#inner
#我是func函数
#after

2.第二回合

def outer(origin):
    def inner():
        print('inner')
        res = origin()
        print("after")
        return res
    return inner

@outer
def func():
    print("我是func函数")
    value = (11, 22, 33, 44)
    return value

func()

3. 第三回合

        在这3个函数执行前和执行后分别输入 "before" 和 "after"

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

实现思路:

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
def func1():
    print("我是func1函数")
    value = (11, 22, 33, 44)
    return value

@outer
def func2():
    print("我是func2函数")
    value = (11, 22, 33, 44)
    return value

@outer
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  # func1 = outer(func1)
def func1(a1):
    print("我是func1函数")
    value = (11, 22, 33, 44)
    return value


@outer  # func2 = outer(func2)
def func2(a1, a2):
    print("我是func2函数")
    value = (11, 22, 33, 44)
    return value


@outer  # func3 = outer(func3)
def func3(a1):
    print("我是func3函数")
    value = (11, 22, 33, 44)
    return value


func1(1)
func2(11, a2=22)
func3(999)

其中,我的实现思路中用的写法就称为装饰器。

  • 实现原理:基于@语法和函数闭包,将原函数封装在闭包中,然后将函数赋值为一个新的函数(内层函数),执行函数时再在内层函数中执行闭包中的原函数。

  • 实现效果:可以在不改变原函数内部代码 和 调用方式的前提下,实现在函数执行和执行扩展功能。

  • 适用场景:多个函数系统统一在 执行前后自定义一些功能。

  • 装饰器示例

def outer(origin):
    def inner(*args, **kwargs):
		# 执行前
        res = origin(*args, **kwargs)  # 调用原来的func函数
        # 执行后
        return res
    return inner


@outer
def func():
    pass

func()

重要补充:functools

你会发现装饰器实际上就是将原函数更改为其他的函数,然后再此函数中再去调用原函数。

def handler():
    pass

handler()
print(handler.__name__) # handler
def auth(func):
    def inner(*args, **kwargs):
        return func(*args, **kwargs)
    return inner

@auth
def handler():
    pass

handler()
print(handler.__name__) # inner
import functools

def auth(func):
    @functools.wraps(func)
    def inner(*args, **kwargs):
        return func(*args, **kwargs)
    return inner

@auth
def handler():
    pass

handler()
print(handler.__name__)  # handler

        其实,一般情况下大家不用functools也可以实现装饰器的基本功能,但后期在项目开发时,不加functools会出错(内部会读取__name__,且__name__重名的话就报错),所以在此大家就要规范起来自己的写法。

import functools


def auth(func):
    @functools.wraps(func)
    def inner(*args, **kwargs):
        """巴巴里吧"""
        res = func(*args, **kwargs)  # 执行原函数
        return res

    return inner

四.匿名函数

        传统的函数的定义包括了:函数名 + 函数体。 匿名函数,则是基于lambda表达式实现定义一个可以没有名字的函数,例如:

data_list = [ lambda x:x+100,  lambda x:x+110, lambda x:x+120 ]
print( data_list[0] )
#<function <lambda> at 0x000002AC2855C1F0>

f1 = lambda x:x+100
res = f1(100)
print(res)
#200

        定义  基于Lambda定义的函数格式为:lambda 参数:函数体

        参数,支持任意参数。

lambda x: 函数体
lambda x1,x2: 函数体
lambda *args, **kwargs: 函数体

        函数体,只能支持单行的代码。

def xxx(x):
    return x + 100
    
lambda x: x + 100

        返回值,默认将函数体单行代码执行的结果返回给函数的执行这。

func = lambda x: x + 100
v1 = func(10)
print(v1) # 110
def func(a1,a2):
    return a1 + a2 + 100

foo = lambda a1,a2: a1 + a2 + 100
print(func(1,2))
#103

        简单的条件语句,可以基于三元运算实现,例如:

func = lambda x: "大了" if x > 66 else "小了"

v1 = func(1)
print(v1) # "小了"

v2 = func(100)
print(v2) # "大了"

总结:

  1. 函数可以定义在全局、也可以定义另外一个函数中(函数的嵌套)

  2. 学会分析函数执行的步骤(内存中作用域的管理)

  3. 闭包,基于函数的嵌套,可以将数据封装到一个包中,以后再去调用。

  4. 装饰器实现原理:基于@语法和函数闭包,将原函数封装在闭包中,然后将函数赋值为一个新的函数(内层函数),执行函数时再在内层函数中执行闭包中的原函数。

    (1)实现效果:可以在不改变原函数内部代码 和 调用方式的前提下,实现在函数执行和执行扩展功能。

        (2)适用场景:多个函数系统统一在 执行前后自定义一些功能。装饰器示例

import functools


def auth(func):
    @functools.wraps(func)
    def inner(*args, **kwargs):
        """巴巴里吧"""
        res = func(*args, **kwargs)  # 执行原函数
        return res

    return inner

5.匿名函数,基于lambda表达式实现一行创建一个函数。一般用于编写简单的函数。

6.三元运算,用一行代码实现处理简单的条件判断和赋值。

;