2-4 函数高级(嵌套、闭包、装饰器)

发布时间 2023-11-27 19:08:59作者: 晴朗sky

 概要:

  • 函数的嵌套

  • 闭包

  • 装饰器

 

 

1. 函数嵌套

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

NAME = "ayden"
print(NAME)
​
def func():
    print(NAME)
​
func()

 

1.1 函数在作用域中

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

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

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

  • 情景1

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

     

  • 情景2

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

     

 

1.2 函数定义的位置

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

def func():
    print("沙河高晓松")
    
def handler():
    print("昌平吴彦祖")
    def inner():
        print("朝阳大妈")
    inner()
    func()
    print("海淀网友")
​
handler()

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

 

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

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

def f1():
    pass
​
def f2():
    pass
​
def func():
    f1()
    f2()
def func():
    def f1():
        pass
​
    def f2():
        pass
    f1()
    f2()
 

1.3 嵌套引发的作用域问题

基于内存和执行过程分析作用域。

name = "ayden"

def run():
    name = "alex"
    def inner():
        print(name)
	inner()
    
run()
name = "ayden"

def run():
    name = "alex"
    def inner():
        print(name)
	return inner
    
v1 = run()
v1()

v2 = run()
v2()

 

 

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

 

三句话搞定作用域:

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

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

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

 

2.闭包

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

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

    name = "ayden"
    
    def f1():
        print(name, age)
    
    def f2():
    	print(name, age)
    
    def f3():
    	print(name, age)
        
    def f4():
        pass
    def func(age):
        name = "ayden"
    
        def f1():
            print(name, age)
    
        def f2():
            print(name, age)
    
        def f3():
            print(name, age)
    
        f1()
        f2()
        f3()
    
    func(123)

     

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

    def task(arg):
        def inner():
            print(arg)
        return inner
    
    v1 = task(11)
    v2 = task(22)
    v3 = task(33)
    v1()
    v2()
    v3()
    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()

     

 

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):
    		# 执行前
            res = origin(*args, **kwargs)  # 调用原来的func函数
            # 执行后
            return res
        return inner
    
    
    @outer
    def func():
        pass
    
    func()

     

应用场景

在以后编写一个网站时,如果项目共有100个页面,其中99个是需要登录成功之后才有权限访问,就可以基于装饰器来实现。

pip3 install flask

基于第三方模块Flask(框架)快速写一个网站:

from flask import Flask

app = Flask(__name__)


def index():
    return "首页"


def info():
    return "用户中心"


def order():
    return "订单中心"


def login():
    return "登录页面"


app.add_url_rule("/index/", view_func=index)
app.add_url_rule("/info/", view_func=info)
app.add_url_rule("/login/", view_func=login)

app.run()

 

基于装饰器实现的伪代码:

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

 

重要补充: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

 

 

总结

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

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

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

  4. 装饰器

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

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

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

    • 装饰器示例

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