再度认识闭包和装饰器

之前总是为了学习而去学习,所以对一个知识点总是知其然而不知其所以然,如果在认识一个新的知识点的过程中没有疑问,那一定是没有掌握这个东西,甚至可以说是没有入门。这样的结果一定是似懂非懂,最后慢慢遗忘。闭包和装饰器在很早就了解过,并且还推过一篇文章,但是这几天被提及这个东西,我回答起来居然含糊其辞,没有逻辑。这是一件让人感到非常羞愧的事情。所以我打算用自己的语言再去陈述一遍我对这个东西的见解,也希望大家指出其中的不足或者帮忙扩展,一起进步!·

闭包

我们知道在python中一切皆对象,函数和类均是对象,它们可以赋值给一个变量,可以作为入参传递给函数,也可以作为函数的返回值,甚至可以添加到集合对象中去。闭包就是一种在函数内部定义一个新的函数,新函数使用了外部函数定义的变量,并且外部函数返回新函数的引用的方式。

def counter(start=0):
	def add_one():
        nonlocal start += 1
        return start
    retrn add_one
    
c1 = counter(5)
print(c1())
print(c1())

c2 = counter(5)
print(c2())
print(c2())

# 结果:
# 6
# 7
# 6
# 7

当我们调用counter(5)时,返回的是add_one函数的引用,再次调用c1()运行add_one指向的代码块。

我们可以看到,闭包是数据+功能(函数)的结合,而我们常用的类,也是数据(属性)+功能(方法)的结合,类都需要继承object,所以相比较而言,闭包比类更轻量

对于一般的函数而言,在使用完变量后,会自动释放,而通过上例可以看出,在调用c1()后,并未释放内存,在下一次调用时依然使用的是原来定义的变量。闭包会携带包含它的函数的作用域,闭包间函数作用域互不影响,因此会比其它函数占用更多的内存,需要手动释放内存

内部函数只能引用外部函数的局部变量,如果需要修改外部函数的变量,需要使用关键字nonlocal

当函数、匿名函数、闭包、对象 当做实参时,有什么区别?

def test(temp):
    pass

def a():
    pass

# 相当于传递功能,不是传递数据
test(a)

b = lambda x : x*2
# 相当于传递功能,不是传递数据
test(b)

def person(name):
    def say(content):
        print(name, content)
    return say

p = person("xiaoming")
# 相当于传递了say这个功能以及name这个数据
test(p)

class Persons(object):
    def __init__(self, name):
        self.name = name

    def say(self,content):
        print(self.name,content)
#      
p2 = Persons("xiaohua")
# 相当于传递了功能以及name这个数据
test(p2)

闭包应用1:一个人站在原点,然后向X、Y轴进行移动,每次移动后及时打印当前的位置

def create():
    pos = [0,0]
    def player(direction, step):
        new_x = pos[0] + direction[0] * step
        new_y = pos[1] + direction[1] * step
        pos[0] = new_x
        pos[1] = new_y
    return player

player = create()
print(player([1,0], 10))
print(player([0,1], 20))
print(play([-1,0], 10))
        

闭包应用2:对某文件的特殊行进行分析,先要提取出这些特殊行

def make_filter(keep):
    def the_filter(filter_name):
        with open(file_name, 'r'):
            lines = file.readlines()
        file_doc = [i for i in lines if keep in i]
        return fileter_doc
    return the_filter

filter = make_filter("163.com")
filter_result = filter("result.txt")

装饰器

引入

我们可以看以下一个例子,我们想要算出执行一个函数消耗了多少时间,不使用装饰器的情况下,可以这样实现:

import time


def calculate_10w():
    '''
    计算100000以内的每个数的立方和
    :return:
    '''
    sum_ret = 0
    for i in range(1, 100001):
        sum_ret += i ** 3
    print("10w以内的每个数的立方和为:",sum_ret)

start_time = time.time()
calculate_10w()
stop_time = time.time()

print("耗费总时长为:", stop_time - start_time, "(秒)")


# 结果
# 10w以内的每个数的立方和为: 25000500002500000000
# 耗费总时长为: 0.04386019706726074 (秒)

如果我们需要对多个函数都单独计时,采用上述方法,每执行一个函数都需要在调用函数前后加上计时代码,然后执行一条输出时间语句,这样不仅会造成大量的重复代码,而且代码管理维护也会变得困难。可以观察到,除了需要执行的函数不一样,函数前后执行的语句都是相同的,那我们能不能定义一个方法,将函数作为入参,然后直接调用方法呢

import time


def calculate_10w():
    '''
    计算100000以内的每个数的立方和
    :return:
    '''
    sum_ret = 0
    for i in range(1, 100001):
        sum_ret += i ** 3
    print("10w以内的每个数的立方和为:",sum_ret)
 
def caculate_time(fun):
    start_time = time.time()
	fun()
	stop_time = time.time()
	print("耗费总时长为:", stop_time - start_time, "(秒)")
    
caculate_time(calculate_10w)

# 结果
# 10w以内的每个数的立方和为: 25000500002500000000
# 耗费总时长为: 0.04386019706726074 (秒)

显然,重新定义一个函数,将需要计算执行时间的函数作为入参是可行的,也能解决重复代码的问题。但是计算函数执行时间只是我们想要实现的额外功能,运行函数calculate_10w()才是我们最主要的功能,通过上面的方法,calculate_10w()的执行完全隐藏在了caculate_time()函数当中,这样代码的可读性就大大降低了,对于后期代码的维护也没有很好。那还有方法可以更完美地解决这些问题吗?这个时候就引入了装饰器

装饰器是在不改变原函数或者类的功能的情况下,为函数/类添加额外的功能,它本身是个特殊的闭包函数或者类,入参是需要被装饰的函数或者类

import time

def caculate_time(fun):
    def inner():
        start_time = time.time()
        fun()
        stop_time = time.time()
        print("耗费总时长为:", stop_time - start_time, "(秒)")
    return inner

@caculate_time
def calculate_10w():
    '''
    计算100000以内的每个数的立方和
    :return:
    '''
    sum_ret = 0
    for i in range(1, 100001):
        sum_ret += i ** 3
    print("10w以内的每个数的立方和为:",sum_ret)
    

calculate_10w()

# 结果
# 10w以内的每个数的立方和为: 25000500002500000000
# 耗费总时长为: 0.04386019706726074 (秒)

我们可以看到,通过上述方式,实现一个装饰器,然后在calculate_10w()函数上添加@caculate_time,再调用calculate_10w(),也能够实现计时器功能,那它是怎么做到的呢?

1、首先定义了一个入参为函数的闭包caculate_time(fun)函数,返回的是它内部的inner函数的引用

2、在需要添加计时功能的函数calculate_10w()上使用@caculate_time

3、调用calculate_10w()函数

4、当python解释器运行到caculate_time(fun)闭包函数时,会将函数加载到内存(只有被调用时才会被执行)

5、当执行到@caculate_time时,会将caculate_time看作可执行对象,去调用caculate_time()函数,并将被修饰的函数引用作为入参传入,将返回的inner引用赋值给calculate_10w,即

calculate_10w = caculate_time(calculate_10w)

5、当解释器执行calculate_10w()时,实际上是在运行inner函数

校验@的功能

import time

def caculate_time(fun):
    print("--------开始装饰----------")
    def inner():
        print("-----开始调用原函数------")
        start_time = time.time()
        fun()
        stop_time = time.time()
        print("耗费总时长为:", stop_time - start_time, "(秒)")
        print("------结束调用原函数------")
    print("-----完成装饰-------")
    return inner

# @caculate_time
def calculate_10w():
    '''
    计算100000以内的每个数的立方和
    :return:
    '''
    sum_ret = 0
    for i in range(1, 100001):
        sum_ret += i ** 3
    print("10w以内的每个数的立方和为:",sum_ret)
    

calculate_10w = caculate_time(calculate_10w)
calculate_10w()


# 结果
# -------开始装饰----------
# ----完成装饰-------
# ----开始调用原函数------
# 0w以内的每个数的立方和为: 25000500002500000000
# 费总时长为: 0.04487943649291992 (秒)
# -----结束调用原函数------

函数装饰器

无参装饰器和有参装饰器

上述例子属于无参装饰器,装饰器不需要传入任何参数,如果我们需要使用参数,又不确定需要被装饰的函数的入参个数和类型,可以在装饰器的内函数中使用*args和**kwargs作为入参。当调用原函数的时候,实参会传递到闭包中的内部函数的形参变量中,在内部函数执行的时候,将这些数据作为实参传递到原函数中

def timefun(func):
    def inner(*args, **kwargs):
        func(*args, **kwargs)
    return inner

@timefun
def foo(a, b):
    print(a+b)
    
@timefun
def count(a, b, c):
    print(a+b+c)
    
foo(1,2)
count(1,2,3)

# 结果
# 3
# 6

无返回值装饰器和有返回值装饰器

装饰器内部函数有return返回,则为有返回值装饰器,一般返回一个数据,也可以返回多个数据,相应地,装饰器内部函数没有return返回值,则是无返回值装饰器

def test(func):
    def inner(*args, **kwargs):
        res1 = func(*args, **kwargs)
        res2 = 5
        return res1,res2
    return inner

@test
def myfunc(num):
    return num

res1,res2 = myfunc(4)
print(res1)
print(res2)

# 结果
# 4
# 5

装饰器传入参数

如果我们还希望装饰器也能够传入一些参数,可以这样做:

import time


def outter(timeout=0):
    def wrapper(func):
        def inner(*args, **kwargs):
            print("开始执行函数")
            time.sleep(timeout)
            res = func(*args, **kwargs)
            print("函数运行结束")
            return res
        return inner
    return wrapper

@outter(3)
def test(num):
    return num

res = test(5)
print(res)


# 结果
# 开始执行函数
# 函数运行结束
# 5

执行@outter(3),得到wrapper引用,然后会执行test = wrapper(test),得到inner的引用;调用test(5),执行inner函数,得到返回值5

类装饰器

以上都是封装的函数装饰器,除了函数可以作为装饰器,类也能够作为装饰器来使用,基本的使用方式和函数装饰器类似。我们来看下面的例子:

class Test(object):
    def __init__(self,func):
        print("-----初始化------")
        print("func name is %s" % func.__name__)
        self.__func = func

    def __call__(self):
        print("-----装饰器中的功能----")
        self.__func()

@Test
def mytest():
    print("----mytest----")

mytest()

# 结果

# -----初始化------
# func name is mytest
# -----装饰器中的功能----
# ----mytest----

执行@Test时,相当于mytest = Test(mytest),即实例化了一个Test对象,并将mytest函数引用作为初始化参数传入__init__中,再将对象的引用赋值给mytest。当运行mytest()时,相当于调用了Test的实例对象,会自动调用并执行魔法函数__call__,从而运行原来的mytest函数

适用场景

  • 引入日志
  • 函数执行时间统计
  • 执行函数前预备处理
  • 执行函数后清理功能
  • 权限校验等场景
  • 缓存
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值