-day12--函数高级

day12 函数高级

课程目标:掌握函数嵌套、闭包、装饰器等高级知识点。

概要:

  • 函数的嵌套
  • 闭包
  • 装饰器

上述内容均属于函数部分必备知识,以后开发时直接和间接都会使用,请务必理解(重在理解,不要死记硬背)

1. 函数嵌套

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

NAME = "小佩奇"
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()

案例:

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\CENTURY.TTF", size=24)
        # Mac系统字体
        # font = ImageFont.truetype("/System/Library/Fonts/SFNSRounded.ttf", size=24)
        # 项目字体文件
        font = ImageFont.truetype("CENTURY.TTF", 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

# 用逗号分隔,可以输入自定义字符,ode = create_image_code("a2.png", "自定义")
code = create_image_code("a2.png")
print(code)

1.3 嵌套引发的作用域问题

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

name = "小佩奇"

def run():
    name = "tomcat"
    def inner():
        print(name)
	inner()
    
run()
# 执行一个函数内部会生成一个调用栈,调用栈可以理解为是创建了一个作用域。
name = "小佩奇"

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

v2 = run()
v2()
# 一个函数执行完后,数据会销毁,引用计算器会归零若再次执行时会创建一个新的作用域。
name = "小佩奇"

def run():
    name = "tomcat"
    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)
    
    # 小佩奇
    # None
    
  2. 分析代码,写结果

    name = '小佩奇'
    
    def func():
        def inner():
            print(name)
            return "tomcat"
        res = inner()
    	return res
    
    v = func()
    print(v)
    
    # 小佩奇
    # tomcat
    
  3. 分析代码,写结果

    name = 'root'
    
    def func():
        def inner():
            print(name)
            return 'admin'
        return inner
    
    v = func()
    result = v()
    print(result)
    
    # root
    # admin
    
  4. 分析代码,写结果

    def func():
        name = '火影鸣人'
        def inner():
            print(name)
            return '路飞'
        return inner
    
    v11 = func()
    data = v11()
    print(data)
    
    
    v2 = func()()
    print(v2)
    
  5. 分析代码,写结果

    def func(name):
        # name="tomcat"
        def inner():
            print(name)
            return 'lufei'
        return inner
    
    v1 = func('小佩奇')()
    print(v1)
    
    v2 = func('tomcat')()
    print(v2)
    
    def func(name):
        def inner():
            print(name)
            return 'lufei'
        return inner
    
    v1 = func('小佩奇')
    v2 = func('tomcat')
    v1()
    v2()
    # 没有接收返回值
    d1 = v1()
    print(d1)
    
  6. 分析代码,写结果

    def func(name=None):
        if not name:
            name= '小佩奇'
        def inner():
            print(name)
            return 'root'
        return inner
    
    v1 = func()()
    v2 = func('tomcat')()
    print(v1,v2)
    
    # 小佩奇
    # tomcat
    # root root
    

2.闭包

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

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

    name = "小佩奇"
    
    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)
    
  • 闭包应用场景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
    
    """
    下载3个视频。
    """
    
    # ############### 原来方式:串行,效率较低,必须前一个下载完毕后才能下载下一个 ###############
    """
    import requests
    video_list = [
        ("东北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_list:
        res = requests.get(
            url=item[1],
            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"
            }
        )
        with open(item[0], mode='wb') as file_object:
            file_object.write(res.content)
    """
    
    # ###############  并行,多线程,并发下载 ###############
    """
    多线程,多个人。
    """
    import requests
    from concurrent.futures.thread import ThreadPoolExecutor
    
    
    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):
        # 必须有一个参数,就是下载完成后的返回值【return res.content 下载视频的二进制内容
        def done(arg):
    # submit后就有一个future对象,future对象就有result方法,调用result就获取到task函数的返回值
            # 视频内容,.result()固定用法
            content = arg.result()
            with open(file_name, mode='wb') as file_object:
                file_object.write(content)
        return done
    
    
    # 线程池10个人,10线程
    POOL = ThreadPoolExecutor(10)
    
    # 三个视频信息
    video_list = [
        ("东北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_list:
        # 去线程池取一个人,让这个人执行去执行task函数(函数内部定义下载逻辑),必须是函数
        future = POOL.submit(task, url=item[1])
    # 这个future对象不是下载完成的值,是临时的一个特殊的对象,译文:将来的,未来的。
        # 当执行完成task函数(下载完成)之后自动执行某个函数。
        future.add_done_callback( outer(item[0]) )
        print(item)
    
    # done 完成
    # callback 回调函数
    

理解闭包函数

一. 概要:

闭包函数 = 名称空间与作用域 + 函数嵌套 + 函数对象

核心点:名字的查找关系是以函数定义阶段为准的

二. 什么是闭包函数

“闭” 指的是该函数的内嵌函数

“包” 函数指的是该函数包含对外出函数作用域名字的引用,(不是对全局作用域)

def f1():
    x = 33333333333333333333
    def f2():
        print(x)
    f2()


x=11111
def bar():
    x=444444
    f1()

def foo():
    x=2222
    bar()

foo()
# 不管怎么给x赋值,都不是f2的包,f2的x始终遵循先在自己的作用域中取值(如果取得到了就不是闭包了)取不到才去他的上一级取值。

闭包函数:函数对象,魅力所在

# 突破上面只能在自己的作用域中调用
def f1():
    x = 33333333
    def f2():
        print('函数f2:  ', x)
    return f2  # 没有括号,否则返回的是内存地址

# 获取内存地址
f = f1()
print(f)

# 在全局中调用 f1函数,并且f是可执行的
x = 44
f()

# 在别的作用域中也可以调用 f1函数,其实就是执行f,只是一个变量名称而已,不管叫什么都可以的
def foo():
    x = 555
    f()

foo()

三.闭包函数的应用

闭包本质就是一种为函数的传参方式。

方式一:直接把函数体需要的参数定义成形参
方式二:就是通过闭包的方式去传参

# 方式二:
def f1(x):
    def f2():
        print(x)
    return f2

x=f1(11)
print(x)

x()

3.装饰器

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

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

3.1 第一回合

需求:在函数执行前打印before,执行后打印after。

你的实现思路:

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

处理返回值:

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

3.2 第二回合

在Python中有个一个特殊的语法糖:

# python中支持的一个特殊的语法,在某个函数上方使用:
@wrapper
def index():
    pass
# python 内部会自动执行,函数名wrapper(index),执行完后,再将结果赋值给 index
# 等于执行了 index = wrapper(index)
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 第三回合

请在这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()
    
    
    # 默写模板
    def outter(func):
    # func = # 被装饰的函数
        def wrapper(*args, **kwargs):
            res = func(*args, **kwargs)
            return res
        return wrapper
    

伪应用场景

在以后编写一个网站时,如果项目共有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 rbac():
    pass

print(rbac.__name__)

# 获取函数注释
def rbac():
    """这个是xxx函数"""
    pass

print(rbac.__doc__)

# 查看函数功能,输出的就是__doc__的内容
def rbac():
    """这个是xxx函数"""
    pass

print(help(rbac))

# 还有更多功能
输入:
rbac.__  查看
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) # inner.__name__ = func.__name__
    def inner(*args, **kwargs):
        return func(*args, **kwargs)
    return inner

@auth
def handler():
    pass

handler()
print(handler.__name__)  # handler

# functools.wraps()将函数伪装的更像原函数

其实,一般情况下大家不用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
      
    • 函数命名不但要做到见名知意,且尽量使用动词命名,不使用名字

4.装饰器理解

一.什么是装饰器

装饰器指的是定义一个函数,该函数时用来为其他函数添加额外的功能

二.为什么要用装饰器

开放封闭原则

  • 开放:指的是对扩展功能是开放的
  • 封闭:指的是对参数修改源代码是封闭的

装饰器就是在不修改被装饰对象源代码也及调用方式的前提下为被装饰对象添加新功能。

三.如何使用

案例分析

# 需求:在不修改index函数的源代码以及调用方式的前提下为其添加统计运行时间的功能
import time
def index(x, y):
    time.sleep(3)
    print('index %s %s' %(x, y))
index(111, 222) # 传参和顺序是没有关系的

方案一:

# 方案一:失败
# 问题:没有修改被装饰对象的调用方式,但修改了其源代码
import time


def index(x, y):
    start = time.time()
    time.sleep(3)
    print('index %s %s' % (x, y))
    stop = time.time()
    print(stop - start)


index(111, 222)

方案二:

# 方案二:失败
# 问题:没有修改被装饰对象的调用方式,也没修改其源代码,并且加上了新功能,但是代码有冗余
import time

def index(x, y):
    time.sleep(3)
    print('index %s %s' % (x, y))

start = time.time()
index(111, 222)
stop = time.time()
print(stop - start)

start = time.time()
index(111, 222)
stop = time.time()
print(stop - start)

start = time.time()
index(111, 222)
stop = time.time()
print(stop - start)

方案三:

# 解决方案三:失败
# 问题:解决了方案二代码冗余的问题,但带来一个新问题,即函数的调用方式改变了
import time

def index(x, y):
    time.sleep(3)
    print('index %s %s' % (x, y))

def wrapper():
    start = time.time()
    index(111, 222)
    stop = time.time()
    print(stop - start)

wrapper()

# 方案三的优化一:将index的参数写后了,但是只能装饰index
import time


def index(x, y, z):
    time.sleep(3)
    print('index %s %s %s' % (x, y, z))


def wrapper(*args, **kwargs):
    start = time.time()
    index(*args, **kwargs)
    stop = time.time()
    print(stop - start)


wrapper(333, 444, 555)
wrapper(333, z=666, y=888)

# 方案三优化二:在优化一的基础上把被装饰对象写活了,原来只能装饰index
import time

# 被装饰对象
def index(x, y, z):
    time.sleep(3)
    print('index %s %s %s' % (x, y, z))

# 被装饰对象
def home(name):
    time.sleep(2)
    print('welcome %s to home page' % name)

# 装饰器
def outter(func):
    # func = index的内存地址
    def wrapper(*args, **kwargs):  # index的内存地址
        start = time.time()
        func(*args, **kwargs)  # index的内存地址
        stop = time.time()
        print(stop - start)

    return wrapper  # 不能写括号,写括号返回的是函数的执行后的值,不写括号返回的是函数的内存地址

# index只是名字而已,叫什么都是一样的
index = outter(index)  # index = wrapper的内存地址,index不能加括号
home = outter(home)  # home = wrapper 的内存地址,home也不能加括号,加括号返回的是wrapper执行后的值
# 指向的不是同一个wrapper

home('egon')
home(name='egon')


# 方案三优化三:将wrapper做的跟被装饰对象一模一样,返回值也写活了,以假乱真
import time


# 被装饰对象
# def index(x, y, z):
#     time.sleep(3)
#     print('index %s %s %s' % (x, y, z))

# 被装饰对象
def home(name):
    time.sleep(2)
    print('welcome %s to home page' % name)
    return 123

# 装饰器
def outter(func):
    # func = home
    def wrapper(*args, **kwargs):
        start = time.time()
        res = func(*args, **kwargs) # 等于去调用home函数,且将home的返回值赋值给res
        stop = time.time()
        print(stop - start)
        return res   # 将res(home的返回值) 返回给wrapper

    return wrapper
# 现在 wrapper只能在outter中调用, 返回wrapper的内存的内存地址是为了能在全局调用 wrapper
# 如:f = outter() 调用的就是wrapper


# 偷梁换柱:home这个名字指向的wrapper函数的内存地址
home = outter(home)
res = home('egon')  # ress获取的是wrapper('egon')的返回值
print('返回值--》》', res)

语法糖

import time

# index=原函数的内存地址
def index(x, y, z):
    time.sleep(3)
    print('index %s %s %s' % (x, y, z))


def home(name):
    time.sleep(2)
    print('welcome %s to home page' % name)
    return 123


def outter(func):
    # func = home
    def wrapper(*args, **kwargs):
        start = time.time()
        res = func(*args, **kwargs)
        stop = time.time()
        print(stop - start)
        return res

    return wrapper


index = outter(index) # index指向的是函数wrapper的内存地址
home = outter(home)

index(x=1, y=2, z=3)
home('egon')

每次装饰一个对象时,都需要执行一下outter(),把被装饰对象赋值进去,得到新的内存地址覆盖给原来的地址,有没有一个办法把它更简洁的完成。

在被装饰对象整上方的单独一行写@装饰器名字

import time


def timmer(func):
    # func = home
    def wrapper(*args, **kwargs):
        start = time.time()
        res = func(*args, **kwargs)
        stop = time.time()
        print(stop - start)
        return res

    return wrapper

 #index= timmer(index),调用timmer装饰器,将index的内存地址传进来,然后赋值给名字index
@timmer
def index(x, y, z):
    time.sleep(3)
    print('index %s %s %s' % (x, y, z))


@timmer
def home(name):
    time.sleep(2)
    print('welcome %s to home page' % name)
    return 123


index(x=1, y=2, z=3)
home('egon')


# 思考是否可以加多个装饰器?
# 叠加多个装饰器,加载顺序云运行顺序

@deco1 # index=deco1(deco2.wrapper的内存地址)
@deco2 # deco2.wrapper的内存地址=deco2(deco3.wrapper的内存地址)
@deco3 # deco3中index内存地址:deco3.wrapper的内存地址=dec03(index)
def index():
    pass

无参装饰器模板

# 无参装饰器模板
def outter(func):
    def wrapper(*args, **kwargs):
        # 1.调用原函数
        # 2.为其增加新功能
        res = func(*args, **kwargs)
        return res
    return wrapp
import time


def timmer(func):
    def wrapper(*args, **kwargs):
        # 1.调用原函数
        # 2.为其增加新功能
        start = time.time()
        res = func(*args, **kwargs)
        stop = time.time()
        print(stop - start)
        return res

    return wrapper


@timmer
def index():
    print('from index')

index()

案例:

def auth(func):
    def wrapper(*args, **kwargs):
        # 1.调用原函数
        # 2.为其增加新功能
        name = input('your name >>: ').strip()
        pwd = input('your password>>: ').strip()
        if name == 'egon' and pwd == '123':
            res = func(*args, **kwargs)
            return res
        else:
            print('账号密码错误')
    return wrapper


@auth
def index():
    print('from index')


index()

多装饰器案例

import time


# 装饰器,计算程序运行时间
def timmer(func):
    # func = home
    def wrapper(*args, **kwargs):
        start = time.time()
        res = func(*args, **kwargs)
        stop = time.time()
        print(stop - start)
        return res

    return wrapper


# 装饰器,认证功能
def auth(func):
    def wrapper(*args, **kwargs):
        # 1.调用原函数
        # 2.为其增加新功能
        name = input('your name >>: ').strip()
        pwd = input('your password>>: ').strip()
        if name == 'egon' and pwd == '123':
            res = func(*args, **kwargs)
            return res
        else:
            print('账号密码错误')

    return wrapper


# 被装饰对象

@auth
@timmer
def home(name):
    time.sleep(2)
    print('welcome %s to home page' % name)
    return 123


home('egon')

@的本质
@print
def home(a, b, c):
    pass
# @的本质: 调用的是print的内存地址,相当于 执行 print(),然后会把下一行的函数名当成参数传入,切赋值给home,等价于 home = print(home)

@print
def home(a, b, c):
    pass



def home(a, b, c):
    pass
home = print(home)

# 输出的结果是一致的
D:\python39\python.exe C:/Users/dw/Desktop/python笔记/模块二/代码/day12/演示装饰器.py
<function home at 0x00000169B92DF0D0>
<function home at 0x00000169B92DF0D0>
# 报错
@print("hello")
def home(a, b, c):
    pass


D:\python39\python.exe C:/Users/dw/Desktop/python笔记/模块二/代码/day12/演示装饰器.py
Traceback (most recent call last):
  File "C:\Users\dw\Desktop\python笔记\模块二\代码\day12\演示装饰器.py", line 282, in <module>
    def home(a, b, c):
TypeError: 'NoneType' object is not callable
hello


# 等价于调用了print("hello")的返回值,没有返回值是None,
# @None是不可调用的所有报错如上

wraps 装饰器补充

from functools import wraps


def auth(func):
    @wraps(func)
    def wrapper(*args, **kwargs):
        """注释内容"""
        res = func(*args, **kwargs)  # 执行原函数
        return res

    return wrapper

有参装饰器

由于语法糖@的限制,outter函数只能有一个参数,并且该参数只用来接收被装饰对象的内存地址

# 如果def outter(func) 要传的参数不止func,该如何传参?
from functools import wraps


def outter(func):
    @wraps(func)
    # func = 函数的内存地址
    def wrapper(*args, **kwargs):
        res = func(*args, **kwargs)
        return res

    return wrapper


@outter
def index(x, y):
    print(x, y)

# 偷梁换柱之后
# index的参数什么样子,wrapper的参数就应该什么样子
# index的返回值什么样子,wrapper的返回值就应该什么样子
# index的属性什么样子,wrapper的属性就应该什么样子==》from functools import wraps

需求演变

# 山炮玩法
def auth(func, db_type):
    def wrapper(*args, **kwargs):
        name = input('your name >>:').strip()
        pwd = input('yourr password>>:').strip()

        if db_type == 'file':
            print('基于文件的验证')
            if name == 'egon' and pwd == '123':
                res = func(*args, **kwargs)
                return res
            else:
                print('user or password error')
        elif db_type == 'mysql':
            print('基于mysql的验证')
        elif db_type == 'ldap':
            print('基于ldap的验证')
        else:
            print('不支持该db_type')

    return wrapper


# @auth  # 账号密码的来源是文件
def index(x, y):
    print('index->>%s:%s' % (x, y))


# @auth # 账号密码的来源是数据库
def home(name):
    print('home->>%s' % name)


# @auth # 账号密码的来源是ldap
def transfer():
    print('transfer')


index = auth(index, 'file')
home = auth(home, 'mysql')
transfer = auth(transfer, 'ldap')

index(1, 2)
home('egon')
transfer()
# 优化一
def auth(db_type):
    def deco(func):
        def wrapper(*args, **kwargs):
            name = input('your name >>:').strip()
            pwd = input('yourr password>>:').strip()

            if db_type == 'file':
                print('基于文件的验证')
                if name == 'egon' and pwd == '123':
                    res = func(*args, **kwargs)
                    return res
                else:
                    print('user or password error')
            elif db_type == 'mysql':
                print('基于mysql的验证')
            elif db_type == 'ldap':
                print('基于ldap的验证')
            else:
                print('不支持该db_type')

        return wrapper

    return deco


deco = auth(db_type='file')
@deco

# @auth  # 账号密码的来源是文件
def index(x, y):
    print('index->>%s:%s' % (x, y))


deco = auth(db_type=',mysql')
@deco

# @auth # 账号密码的来源是数据库
def home(name):
    print('home->>%s' % name)


deco = auth(db_type='ldap')
@deco

# @auth # 账号密码的来源是ldap
def transfer():
    print('transfer')



index(1, 2)
home('egon')
transfer()

语法糖

def auth(db_type):
    def deco(func):
        def wrapper(*args, **kwargs):
            name = input('your name >>:').strip()
            pwd = input('yourr password>>:').strip()

            if db_type == 'file':
                print('基于文件的验证')
                if name == 'egon' and pwd == '123':
                    res = func(*args, **kwargs)
                    return res
                else:
                    print('user or password error')
            elif db_type == 'mysql':
                print('基于mysql的验证')
            elif db_type == 'ldap':
                print('基于ldap的验证')
            else:
                print('不支持该db_type')

        return wrapper

    return deco


@auth(db_type='file')
# @auth  # 账号密码的来源是文件
def index(x, y):
    print('index->>%s:%s' % (x, y))


@auth(db_type=',mysql')
# @auth # 账号密码的来源是数据库
def home(name):
    print('home->>%s' % name)


@auth(db_type='ldap')
# @auth # 账号密码的来源是ldap
def transfer():
    print('transfer')


index(1, 2)
home('egon')
transfer()

有参装饰器模板

因为第三次没有限制,所以不管有多少个参数都可以通过三次传,无需再套第四层。

def 有参装饰器(x, y, z):
    def outter(func):
        def wrapper(*args, **kwargs):
            res = func(*args, **kwargs)
            return res

        return wrapper

    return outter


@有参装饰器(1, y=2, z=3)
def a():
    pass  
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值