[python]十八、闭包和装饰器

目录

1、闭包

1.1、什么是闭包?

1.2、形成闭包的条件

1.3、闭包的使用

1.4、闭包的结论和好处

2、装饰器

2.1、什么是装饰器?

2.2、装饰器有什么用?

2.2.1、编写一个装饰器实现权限控制

2.2.2、多个装饰器的使用

2.3、元数据

2.3.1、装饰器后为什么元数据会丢失以及怎么保留元数据

3、装饰器带参数

3.1、装饰器带参数的使用方法

3.1.1、用装饰器带参数的方法重新编写上文中权限控制代码

4、用类实现装饰器和装饰器带参数

4.1、用类实现装饰器

4.2、用类实现装饰器带参数

5、装饰类


1、闭包

1.1、什么是闭包?

  • 在函数中可以(嵌套)定义另一个函数时,如果内部的函数引用了外部的函数的变量,则可能产生闭包。
  • 闭包也可以用来在一个函数与一组“私有”变量之间创建关联关系。
  • 在给定函数被多次调用的过程中,这些私有变量能够保持其持久性。

1.2、形成闭包的条件

1、必须要有一个内嵌函数
2、内函数必须引用外函数的变量
3、外函数必须返回内函数(注意这里是返回内函数,而不是内函数的调用)

# 生命周期
def outer():
    a = 10
    print(f"a is {a}")

outer()
print(a)  # 这行代码运行会出现错误

# 这个代码运行会出现错误,是因为外部并没有定义a这个变量。内部使用完a这个变量,就已经把这个变量释放掉了。
def outer(x):
    a = 300  # 变量名的解析原则LEGB
    def inner():
        # x = 90
        print(f"两数之和为:{x + a}")  # 运行结果为310
        print(__name__)  #运行结果为__main__
    return inner

d = outer(10)
d()
print(dir(d))
print(d.__closure__)  # 输出的结果是一个元组,里面存储着变量的内存地址,

# 形成闭包之后,闭包函数会得到一个非空的__closure__属性

1.3、闭包的使用

def outer():
    tmp_list = []
    def inner(name):
        tmp_list.append(1)
        print(f"{name} -- {tmp_list}")
    return inner

d1 = outer()
d2 = outer()
d1("d1")
d2("d2")
print(id(d1),id(d2)) 
# 虽然代码一样,但是每次调用外函数都会重新执行,都会创建一个新的tmp_list和inner
print(id(outer()),id(outer()))  # 这个是匿名变量
print(id(d1),id(d2))
# python变量分为匿名变量和命名变量
# 匿名变量一般都放到统一的内存空间

# 运行结果
d1 -- [1]
d2 -- [1]
2383138097616 2383138098048
2383426954432 2383426954432
2383138097616 2383138098048

1.4、闭包的结论和好处

结论

  • 元组里面的对象为cell对象,而访问cell对象的cell_contents属性则可以得到闭包变量的当前值(即上次调用之后的值)。
  • 随着闭包的继续被调用,变量会再次更新。
  • 一旦形成闭包之后,python确实会将__closure__和闭包函数绑定作为储存闭包变量的场所。

 好处

  • 闭包不是必须的
  • 没了闭包,python的功能一点都不会被影响
  • 有了闭包,只是提供给你一种额外的解决办法

2、装饰器

2.1、什么是装饰器?

定义:装饰器是一种程序设计模式,它的本质就是闭包,它在不改变函数或者类的源代码的基础上,添加额外功能。

2.2、装饰器有什么用?

  • 可以考虑在装饰器中置入通用功能的代码来降低程序复杂度。
  • 引入日志
  • 增加计时逻辑来检测性能
  • 给函数加入事务的能力
  • 权限控制

2.2.1、编写一个装饰器实现权限控制

条件:

  • 定义一个全局变量:username
  • 定义add函数,实现两个数相加
  •  实现login_required装饰器,如果username值为root,提示"欢迎" 并计算结果,否则"没有权限"
def login_required(func):
    def inner(*args,**kwargs):
        if username.lower() == "root":
            result = func(*args,**kwargs)
            print(f"欢迎执行{func.__name__}函数")
            return result
        else:
            return f"执行{func.__name__}没有权限"
    return inner

username = input("请输入你的用户名:")

# 函数被修饰符装饰后,返回的都是内函数的值
@login_required  # add = login_required(add),相当于执行了这个操作。
def add(a=int(input("请输入加数1:")),b=int(input("请输入加数2:"))):
    time.sleep(2)
    return f"两数和为:{a+b}"
print(add())

######## 执行结果
请输入你的用户名:root
请输入加数1:2
请输入加数2:3
欢迎执行add函数
两数和为:5

请输入你的用户名:fdjk
请输入加数1:1
请输入加数2:2
执行add没有权限

2.2.2、多个装饰器的使用

可以应用多个装饰器,但是要注意装饰器的执行顺序:从最上面的装饰器的内函数开始执行

# 统计运行时间的装饰器

import time
import functools
def runtime(func):
    # 保留传递进来的函数的元数据,将它的元素据赋值给inner。元数据为原来的名字、文档注释等。
    @functools.wraps(func)
    def inner(*args,**kwargs):  # 加上可变长位置参数可以让装饰器更加的通用
        start_time = time.time()  # 这个是时间戳,从1970年一月一日到至今的总秒数
        result = func(*args,**kwargs)
        end_time = time.time()
        print(f"函数执行花了{end_time-start_time}s")
        return result
    return inner

def login_required(func):
    def inner(*args, **kwargs):
        if username == 'root':
            print(f"欢迎执行{func.__name__}函数")  # 这里要是没有functools.wraps(func),执行结果就会变成"欢迎执行inner函数"
            result = func(*args, **kwargs)
            return result
        else:
            return f"执行{func.__name__}权限不够"
    return inner

# 限制性login_required的内部函数,执行到func,就跳到runtime的内函数
@login_required  # add = login_required(runtime(add))
@runtime         # add = runtime(add)
def add( a, b):
    return a + b

username = input("请输入您要使用的用户:")
a = int(input("请输入要计算的数字1:"))
b = int(input("请输入要计算的数字2:"))
print(add(a, b))

###### 执行结果
请输入您要使用的用户:root
请输入要计算的数字1:2
请输入要计算的数字2:2
欢迎执行add函数
函数执行花了0.0s
4

2.3、元数据

上面这个练习中使用到了" @functools.wraps(func)",这个东东是用来保留元数据的。

定义:元数据(Metadata),又称中介数据、中继数据,为描述数据的数据(data about data),主要是描述数据属性(property)的信息

函数的重要的元信息,比如:名字、文档注释、字符串和参数签名等。

2.3.1、装饰器后为什么元数据会丢失以及怎么保留元数据

为什么会丢失

  • 因为return执行的是经过调用封装的函数

怎么保留元数据

  • 利用@functools.wraps*(fun),将一个函数的重要内容复制到另一个函数。

温馨提示:任何时候你定义装饰器的时候,都应该使用functools库中的@wraps装饰器来注释底层包装函数。

3、装饰器带参数

原则:

  • 自身不传入参数的装饰器,使用两层的函数定义
  • 自身传入参数的的装饰器,使用三层的函数定义

3.1、装饰器带参数的使用方法

错误使用方法:

import functools
import time

def runtime(func):
    #保留传递进来的函数的元数据,将它的元数据赋值给inner
    @functools.wraps(func)
    def inner(*args, **kwargs):  #让装饰器更加通用
        start = time.time()
        result = func(*args, **kwargs)
        end = time.time()
        print(f"函数执行花了{end -start}s")
        return result
    return inner

# 若是两层的带了参数的话,是这么执行的
# a = runtime("name")
# add = a(add)  

@runtime("name")  # 对于两层的函数,这样写是会报错的。因为func若是为字符串的,func(*args, **kwargs)就没有意义。因为字符串不是一个可调用对象(callable)
def add(a,b):
    return a+b

正确使用方法:

import functools
import time
def decp(name):
    def runtime(func):
        #保留传递进来的函数的元数据,将它的元数据赋值给inner
        @functools.wraps(func)
        def inner(*args, **kwargs):  #让装饰器更加通用
            start = time.time()
            result = func(*args, **kwargs)
            end = time.time()
            print(f"函数执行花了{end -start}s")
            print(f"name is {name}")
            print("1")
            return result
        print("2")
        return inner
    print("3")
    return runtime
@decp(name="sc")  # 这个相当于# runtime = deco(name="sc"), func1 = runtime(func1)
def func1():
    time.sleep(3)
    print("this is func1")
func1()

### 执行结果
3
2
this is func1
函数执行花了8.803873777389526s
name is sc
1

3.1.1、用装饰器带参数的方法重新编写上文中权限控制代码

import time

def test(username):
    def login_required(func):
        def inner(*args, **kwargs):
            if username == 'root':
                print(f"欢迎执行{func.__name__}函数")
                result = func(*args, **kwargs)
                return result
            else:
                return f"执行{func.__name__}权限不够"
        return inner
    return login_required

@test(username=input("请输入你的用户名:"))
def add(a=int(input("请输入加数1:")),b=int(input("请输入加数2:"))):
    time.sleep(2)
    return f"两数和为:{a+b}"
result = add()
print(result)
#  执行结果
请输入你的用户名:root
请输入加数1:2
请输入加数2:3
欢迎执行add函数
两数和为:5

4、用类实现装饰器和装饰器带参数

4.1、用类实现装饰器

class rumtime():
    def __init__(self, func):
        self.func = func
    def __call__(self, *args, **kwargs):
        start = time.time()
        self.func()
        end = time.time()
        print(f"用时{end-start}")

@rumtime
def add():
    time.sleep(2)
    print("day day up")

add()

#### 执行结果
day day up
用时2.001725912094116

4.2、用类实现装饰器带参数

class rumtime():
    def __init__(self, name):
        self.func = name
    def __call__(self, func):
        def deco(*args,**kwargs):
            start = time.time()
            result = func(*args,**kwargs)
            end = time.time()
            print(f"用时{end-start}")
            return result
        return deco

@rumtime("name")
def add():
    time.sleep(2)
    print("day day up")

add()

# 执行结果
day day up
用时2.0109517574310303

5、装饰类

def outer(cls):
    def inner(*args,**kwargs):
        print(f"class name is:{cls.__name__}")
        return cls(*args,**kwargs)
    return inner

@outer  # A = outer(A)
class A:
    def __init__(self, name):
        self.name = name
print(type(A))
m = A("sc")
print(m.name)


### 执行结果
<class 'function'>
class name is:A
sc

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

FanMY_71

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值