当前位置:   article > 正文

Python基础——函数高阶之函数嵌套、闭包、装饰器_高阶的嵌套函数

高阶的嵌套函数

1、函数嵌套

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

  1. NAME = "华青水上"
  2. print(NAME)
  3. def func():
  4. print(NAME)
  5. func()

1.1 函数在作用域中

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

  1. # 1. 在全局作用域定义了函数func
  2. def func():
  3. print("你好")
  4. # 2. 在全局作用域找到func函数并执行。
  5. func()
  6. # 3.在全局作用域定义了execute函数
  7. def execute():
  8. print("开始")
  9. # 优先在当前函数作用域找func函数,没有则向上级作用域中寻找。
  10. func()
  11. print("结束")
  12. # 4.在全局作用域执行execute函数
  13. execute()

易错点:

  1. def func():
  2. print("你好")
  3. func()
  4. def execute():
  5. print("开始")
  6. func()
  7. print("结束")
  8. def func():
  9. print(666)
  10. func()
  11. execute()

由于func函数从上到下被定义了两次,这时候上面的func函数被下面的同名func函数替换了,故而在执行execute()函数的时候不会再输出"你好",而是输出666。

1.2 函数定义的位置

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

  1. def func():
  2. print("沙河高晓松")
  3. def handler():
  4. print("昌平吴彦祖")
  5. def inner():
  6. print("朝阳大妈")
  7. inner()
  8. func()
  9. print("海淀网友")
  10. handler()

到现在你会发现,只要理解数据定义时所存在的作用域,并根据从上到下代码执行过程进行分析,再怎么嵌套都可以搞定。

现在的你可能有疑问:为什么要这么嵌套定义?把函数都定义在全局不好吗?

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

  1. """
  2. 生成图片验证码的示例代码,需要提前安装pillow模块(Python中操作图片中一个第三方模块)
  3. pip3 install pillow
  4. """
  5. import random
  6. from PIL import Image, ImageDraw, ImageFont
  7. def create_image_code(img_file_path, text=None, size=(120, 30), mode="RGB", bg_color=(255, 255, 255)):
  8. """ 生成一个图片验证码 """
  9. _letter_cases = "abcdefghjkmnpqrstuvwxy" # 小写字母,去除可能干扰的i,l,o,z
  10. _upper_cases = _letter_cases.upper() # 大写字母
  11. _numbers = ''.join(map(str, range(3, 10))) # 数字
  12. chars = ''.join((_letter_cases, _upper_cases, _numbers))
  13. width, height = size # 宽高
  14. # 创建图形
  15. img = Image.new(mode, size, bg_color)
  16. draw = ImageDraw.Draw(img) # 创建画笔
  17. def get_chars():
  18. """生成给定长度的字符串,返回列表格式"""
  19. return random.sample(chars, 4)
  20. def create_lines():
  21. """绘制干扰线"""
  22. line_num = random.randint(*(1, 2)) # 干扰线条数
  23. for i in range(line_num):
  24. # 起始点
  25. begin = (random.randint(0, size[0]), random.randint(0, size[1]))
  26. # 结束点
  27. end = (random.randint(0, size[0]), random.randint(0, size[1]))
  28. draw.line([begin, end], fill=(0, 0, 0))
  29. def create_points():
  30. """绘制干扰点"""
  31. chance = min(100, max(0, int(2))) # 大小限制在[0, 100]
  32. for w in range(width):
  33. for h in range(height):
  34. tmp = random.randint(0, 100)
  35. if tmp > 100 - chance:
  36. draw.point((w, h), fill=(0, 0, 0))
  37. def create_code():
  38. """绘制验证码字符"""
  39. if text:
  40. code_string = text
  41. else:
  42. char_list = get_chars()
  43. code_string = ''.join(char_list) # 每个字符前后以空格隔开
  44. # Win系统字体
  45. # font = ImageFont.truetype(r"C:\Windows\Fonts\SEGOEPR.TTF", size=24)
  46. # Mac系统字体
  47. # font = ImageFont.truetype("/System/Library/Fonts/SFNSRounded.ttf", size=24)
  48. # 项目字体文件
  49. font = ImageFont.truetype("MSYH.TTC", size=15)
  50. draw.text([0, 0], code_string, "red", font=font)
  51. return code_string
  52. create_lines()
  53. create_points()
  54. code = create_code()
  55. # 将图片写入到文件
  56. with open(img_file_path, mode='wb') as img_object:
  57. img.save(img_object)
  58. return code
  59. code = create_image_code("a2.png")
  60. print(code)

1.3 嵌套引发的作用域问题

  1. name = "华青水上"
  2. def run():
  3. name = "hqss"
  4. def inner():
  5. print(name)
  6. return [inner,inner,inner]
  7. func_list = run()
  8. func_list[2]()
  9. func_list[1]()
  10. funcs = run()
  11. funcs[2]()
  12. funcs[1]()

 三句话搞定作用域:

  • 优先在自己的作用域找,自己没有就去上级作用域。
  • 在作用域中寻找值时,要确保此次此刻值是什么。
  • 分析函数的执行,并确定函数作用域链。(函数嵌套)

2、闭包

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

  • 闭包应用场景1:封装数据防止污染全局。
  1. # 常规方法
  2. name = "华青水上"
  3. def f1():
  4. print(name, age)
  5. def f2():
  6. print(name, age)
  7. def f3():
  8. print(name, age)
  9. def f4():
  10. pass
  11. # 闭包方法
  12. def func(age):
  13. name = "华青水上"
  14. def f1():
  15. print(name, age)
  16. def f2():
  17. print(name, age)
  18. def f3():
  19. print(name, age)
  20. f1()
  21. f2()
  22. f3()
  23. func(123)
  • 闭包应用场景2:封装数据封到一个包里,使用时在取。
  1. # 常规方法
  2. def task(arg):
  3. def inner():
  4. print(arg)
  5. return inner
  6. v1 = task(11)
  7. v2 = task(22)
  8. v3 = task(33)
  9. v1()
  10. v2()
  11. v3()
  12. # 闭包方法
  13. def task(arg):
  14. def inner():
  15. print(arg)
  16. return inner
  17. inner_func_list = []
  18. for val in [11,22,33]:
  19. inner_func_list.append( task(val) )
  20. inner_func_list[0]() # 11
  21. inner_func_list[1]() # 22
  22. inner_func_list[2]() # 33

基于多线程去下载视频的示例:

  1. """ 基于多线程去下载视频 """
  2. from concurrent.futures.thread import ThreadPoolExecutor
  3. import requests
  4. def download_video(url):
  5. res = requests.get(
  6. url=url,
  7. headers={
  8. "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"
  9. }
  10. )
  11. return res.content
  12. def outer(file_name):
  13. def write_file(response):
  14. content = response.result()
  15. with open(file_name, mode='wb') as file_object:
  16. file_object.write(content)
  17. return write_file
  18. POOL = ThreadPoolExecutor(10)
  19. video_dict = [
  20. ("东北F4模仿秀.mp4", "https://aweme.snssdk.com/aweme/v1/playwm/?video_id=v0300f570000bvbmace0gvch7lo53oog"),
  21. ("卡特扣篮.mp4", "https://aweme.snssdk.com/aweme/v1/playwm/?video_id=v0200f3e0000bv52fpn5t6p007e34q1g"),
  22. ("罗斯mvp.mp4", "https://aweme.snssdk.com/aweme/v1/playwm/?video_id=v0200f240000buuer5aa4tij4gv6ajqg")
  23. ]
  24. for item in video_dict:
  25. future = POOL.submit(download_video, url=item[1])
  26. future.add_done_callback(outer(item[0]))
  27. POOL.shutdown()

3、装饰器

举个荔枝:请在以下3个函数执行前和执行后分别输入 "before" 和 "after"

  1. # 题目:
  2. def func1():
  3. print("我是func1函数")
  4. value = (11, 22, 33, 44)
  5. return value
  6. def func2():
  7. print("我是func2函数")
  8. value = (11, 22, 33, 44)
  9. return value
  10. def func3():
  11. print("我是func3函数")
  12. value = (11, 22, 33, 44)
  13. return value
  14. func1()
  15. func2()
  16. func3()

新人方法:

  1. def func1():
  2. print('before')
  3. print("我是func1函数")
  4. value = (11, 22, 33, 44)
  5. print("after")
  6. return value
  7. def func2():
  8. print('before')
  9. print("我是func2函数")
  10. value = (11, 22, 33, 44)
  11. print("after")
  12. return value
  13. def func3():
  14. print('before')
  15. print("我是func3函数")
  16. value = (11, 22, 33, 44)
  17. print("after")
  18. return value
  19. func1()
  20. func2()
  21. func3()

大牛方法:

  1. def outer(origin):
  2. def inner():
  3. print("before 110")
  4. res = origin() # 调用原来的func函数
  5. print("after")
  6. return res
  7. return inner
  8. @outer
  9. def func1():
  10. print("我是func1函数")
  11. value = (11, 22, 33, 44)
  12. return value
  13. @outer
  14. def func2():
  15. print("我是func2函数")
  16. value = (11, 22, 33, 44)
  17. return value
  18. @outer
  19. def func3():
  20. print("我是func3函数")
  21. value = (11, 22, 33, 44)
  22. return value
  23. func1()
  24. func2()
  25. func3()

装饰器,在不修改原函数内容的前提下,通过@函数可以实现在函数前后自定义执行一些功能(批量操作会更有意义)。

优化

优化以支持多个参数的情况。

  1. def outer(origin):
  2. def inner(*args, **kwargs):
  3. print("before 110")
  4. res = origin(*args, **kwargs) # 调用原来的func函数
  5. print("after")
  6. return res
  7. return inner
  8. @outer # func1 = outer(func1)
  9. def func1(a1):
  10. print("我是func1函数")
  11. value = (11, 22, 33, 44)
  12. return value
  13. @outer # func2 = outer(func2)
  14. def func2(a1, a2):
  15. print("我是func2函数")
  16. value = (11, 22, 33, 44)
  17. return value
  18. @outer # func3 = outer(func3)
  19. def func3(a1):
  20. print("我是func3函数")
  21. value = (11, 22, 33, 44)
  22. return value
  23. func1(1)
  24. func2(11, a2=22)
  25. func3(999)

其中,大牛的那种写法就称为装饰器。

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

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

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

  • 装饰器示例:

  1. def outer(origin):
  2. def inner(*args, **kwargs):
  3. # 执行前
  4. res = origin(*args, **kwargs) # 调用原来的func函数
  5. # 执行后
  6. return res
  7. return inner
  8. @outer
  9. def func():
  10. pass
  11. func()

重要补充

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

  1. def handler():
  2. pass
  3. handler()
  4. print(handler.__name__) # handler

从上面栗子看出如果不使用装饰器的话,可以直接获取到函数名。

  1. def auth(func):
  2. def inner(*args, **kwargs):
  3. return func(*args, **kwargs)
  4. return inner
  5. @auth
  6. def handler():
  7. pass
  8. handler()
  9. print(handler.__name__) # inner

从上面例子看出,如果使用装饰器的话,获取到的不是原函数名。

  1. import functools
  2. def auth(func):
  3. @functools.wraps(func)
  4. def inner(*args, **kwargs):
  5. return func(*args, **kwargs)
  6. return inner
  7. @auth
  8. def handler():
  9. pass
  10. handler()
  11. print(handler.__name__) # handler

上述例子通过在内部函数前面添加  @functools.wraps(func)  也可以再次获取原函数名。

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

  1. import functools
  2. def auth(func):
  3. @functools.wraps(func)
  4. def inner(*args, **kwargs):
  5. """巴巴里吧"""
  6. res = func(*args, **kwargs) # 执行原函数
  7. return res
  8. return inner

至此,函数嵌套、闭包、装饰器总结完毕,如有不当之处欢迎批评指正。

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/繁依Fanyi0/article/detail/585286
推荐阅读
相关标签
  

闽ICP备14008679号