Python 笔记(20)— 闭包、单个装饰器、多个装饰器、装饰器修饰类、应用场景

1. 闭包

Python 中,函数也可以作为参数。我们可以执行下面的代码:

def func(a, b):
	return a + b

print(func)

我们直接输出函数名,而没有加括号。输出结果如下:

<function func at 0x000002C83CC96678>

可以看懂结果是一个 function 的对象,我们可以把它赋值给另外的变量:

def func(a, b):
    return a + b

my_func = func
print(my_func(1, 3))

我们把 func 赋值给 my_func 变量,然后发现 my_func 可以和 func 一样使用。输出结果如下:

4

结果和 func 的结果一样。那我们能不能把函数当做参数或者返回值呢?我们可以尝试一下:

def func():

    def inner():
        print("执行了 inner 函数")

    return inner


in_func = func()
in_func()

我们在 func 里面定义了一个 inner 函数,按理我们是不能在外部调用 inner 函数的,但是我们把 inner 当作返回值返回到外部,这样我们就能在外部调用 inner 函数了。我们来看看运行结果:

执行了 inner 函数

可以看到确实执行了 inner 函数。其实 inner 函数我们就可以叫做闭包函数。

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

用比较通俗的话说就是,内部函数使用了外部函数的变量。或者说内部函数的局部作用域中可以访问外部函数局部作用域中变量的行为,我们称为: 闭包。

示例代码:

In [1]: def out(msg):
   ...:     def inner():
   ...:         print msg	# 内部函数使用了外部函数的变量 msg
   ...:     return inner	# 返回的是内部函数
   ...: 

In [2]: fun = out("hello")

In [3]: fun()
hello

2. 装饰器

我们可以利用闭包来实现装饰器。那装饰器又是什么呢?下面我们来详细看一下。

2.1 定义

首先,定义函数 call_print,它的入参 f 为一个函数,它里面内嵌一个函数 g ,并返回函数 g

def call_print(f):
    def g():
        print('you\'re calling %s function' % (f.__name__,))
    return g

Python 中,@call_print 函数,放在函数上面,函数 call_print 就变为装饰器。

变为装饰器后,我们不必自己去调用函数 call_print

@call_print
def myfun():
    pass


@call_print
def myfun2():
    pass

直接调用被装饰的函数,就能调用到 call_print,观察输出结果:

In [27]: myfun()
you're calling myfun function

In [28]: myfun2()
you're calling myfun2 function

使用 call_print@call_print 放置在任何一个新定义的函数上面。都会默认输出一行,输出信息:正在调用这个函数的名称。

3.2 本质

call_print 装饰器实现效果,与下面调用方式,实现的效果是等效的。

def myfun():
    pass


def myfun2():
    pass


def call_print(f):
    def g():
        print('you\'re calling %s function' % (f.__name__,))
    return g

下面两行代码,对于理解装饰器,非常重要:

myfun = call_print(myfun)

myfun2 = call_print(myfun2)

call_print(myfun) 后不是返回一个函数吗,然后,我们再赋值给被传入的函数 myfun。也就是 myfun 指向了被包装后的函数 g,并移花接木到函数 g,使得 myfun 额外具备了函数 g 的一切功能,变得更强大。

以上就是装饰器的本质。

再次调用 myfun, myfun2 时,与使用装饰器的效果完全一致。

In [32]: myfun()
you're calling myfun function

In [33]: myfun2()
you're calling myfun2 function

3.3 其它

装饰器如果从字面意思来讲就是用来装饰的东西,它装饰的对象是函数。我们可以用装饰器在不改变原函数的情况下对函数进行扩展。我们先不说装饰器,我们想想要怎么才能扩展一个函数,最简单的办法就是如下:

def func(a, b):
    return a + b


print("在 func 之前执行")
func(1, 2)
print("在 func 之后执行")

我们直接调用这个函数,然后在函数前面添加一些代码,再在函数执行后面添加一些代码。这样很好的完成了我们的任务,但是这种方式属实有点矬略。如果我们经常要用到这种扩展,或者多个函数需要用到这样的扩展我们这种方式就束手无策了。

这个时候我们就可以使用闭包,闭包能很好的解决这个问题。我们看下面这段代码:

def say_hi(func):
    print("nice to meet you")

    def inner(*args, **kwargs):
        return func(*args, **kwargs)
    print("good bye")
    return inner


def func(greet):
    print(greet)

say_hi_func = say_hi(func)

我们先定义一个函数 say_hi,它的参数是一个函数,也就是我们要扩展的函数。然后我们在里面定义一个 inner 函数,在 inner 函数中完成 func 函数的调用,并接收 func 的参数和返回值。最后将 inner 函数作为参数返回。

我们想对 func 函数进行扩展,我们只需要调用 say_hi(func),它就会给我们返回一个加强后的函数。我们可以执行看看:

def say_hi(func):
    print("nice to meet you")

    def inner(*args, **kwargs):
        return func(*args, **kwargs)
    print("good bye")
    return inner


def func(a, b):
    return a + b

say_hi_func = say_hi(func)
result = say_hi_func(1, 2)
print(result)

执行结果如下:

nice to meet you
good bye
3

可以看到我们成功扩展了 func 函数。但是这样有点混乱,我们还需要先加强函数才能使用,而另一种简单的方式就是使用 @ 符号:

def say_hi(func):
    print("nice to meet you")

    def inner(*args, **kwargs):
        return func(*args, **kwargs)
    print("good bye")
    return inner


@say_hi
def func(a, b):
    return a + b


print(func(1, 2))

我们在 func 函数定义的时候执行了添加了 @say_hi,这个时候 func 就是解释器会帮我们完成下面几句代码:

func = say_hi(func)

这样就不需要再使用一个新的函数了。

3. 多装饰器

我们可以给一个函数添加多个装饰器,其实使用也是一样的。我们可以这样里面,假如我们有如下函数:

def func():
    print("这是函数体")

假如我们有如下装饰器:

def decorate(func):
    print("函数执行前")
    def inner(*args, **kwargs):
        return func(*args, **kwargs)
    print("函数执行后")
    return inner

在外面对 func 函数进行装饰后,func 的内容变为:

def func():
    print("函数执行前")
    print("函数体")
    print("函数执行后")

这时使用装饰器后的函数和普通函数没有区别,外面依旧可以用同样的方式给他添加多个装饰器:

def dec1(func):
    print("start dec1")
    def inner(*args, **kwargs):
        return func(*args, **kwargs)
    print("end dec1")
    return inner

def dec2(func):
    print("start dec2")
    def inner(*args, **kwargs):
        return func(*args, **kwargs)
    print("end dec2")
    return inner

@dec2
@dec1
def func(a, b):
    return a + b

print(func(1, 2))

不过外面需要注意一下生效的顺序:

start dec1
end dec1
start dec2
end dec2
3

可以看到时 dec1 装饰器先起作用,对于多个装饰器也是一样的。

3.1 带参装饰器

def add(func):
    def fun(a, b):
        print("相乘", a * b)
        print("相除", a / b)
        func(a, b)

    return fun


@add
def add_num(a, b):
    # 打印两个数相加
    print("相加:", a + b)


add_num(11, 22)

3.2 通用装饰器

果同一个装饰器既要装饰有参数的函数,又要装饰无参数的函数。那么我们在传参的时候就设置成不定长参数,这样不管被装饰的函数有没有参数都能用。

# 通用装饰器
def add(func):
    def fun(*args, **kwargs):
        print("装饰器的功能代码:登录前")
        func(*args,**kwargs)
        print("装饰器的功能代码:登录后")
    return fun


@add
def index():
    print("这个是网站的首页")

@add
def good_list(num):
    print("这个是商品列表第{}页".format(num))

index()
print("------------")
good_list(9)

其实装饰器也是可以带参数的,我们可以定义一个更加复杂的装饰器:

def dec1(name):
    def decorator(func):
        def inner(*args, **kwargs):
            if name == "wohu":
                print("hi wohu")
            elif name == "jack":
                print("hi jack")
            return func(*args, **kwargs)
        return inner
    return decorator


@dec1("wohu")
def func(a, b):
    return a+b


print(func(1, 2))

我们先来看下面的代码:

@dec1("wohu")
def func(a, b):
    return a +b 

我们可以对 @dec1("wohu") 进行如下理解,其中 dec1("wohu") 的操作是执行 dec1 函数,并传入参数,而 dec1 的返回值是一个装饰器。这样我们就很好理解了。

因此,我们需要在 dec1 函数中定义一个装饰器。这可以从代码中看到。其它部分和之前没有区别,下面是输出结果:

hi wohu
3

这种带参数的装饰器可以让我们的装饰器更加灵活,我们只需要通过不同参数就能让装饰器装饰不同的效果。

4. 装饰器装饰类

4.1 不带参数

#装饰器装饰类

def add(func):
    def fun(*args, **kwargs):
        print("装饰器的功能代码:登录")
        return func(*args,**kwargs)
    return fun


@add  # MyClass=add(MyClass)
class MyClass:
    def __init__(self):
        pass

m = MyClass()
print("m 的值:",m)

把类当作一个参数传到装饰器里面。return fun返回的是 funMyClass接收到的是 fun
MyClass()调用的是 fun

执行代码:

 def fun(*args, **kwargs):
	print("装饰器的功能代码:登录")
        return func(*args,**kwargs)

这里面的功能。先执行装饰器的功能,return func(*args,**kwargs)func()来自def add(func)

调用 MyClass这个类,return func(*args,**kwargs)创建了个对象,MyClass()调用完了接收,m 就能接收这个对象了。

4.2 带参数

#装饰器装饰类

def add(func):
    def fun(*args, **kwargs):
        print("装饰器的功能代码:登录")
        # 装饰器装饰类和装饰函数的不同点,下面这个 return 必须要写 类需要把对象返回出来。
        return func(*args,**kwargs)
    return fun


@add  # MyClass=add(MyClass)
class MyClass:
    def __init__(self,name,age):
      self.name=name
      self.age=age

m = MyClass("qinghan","18")
print("m 的值:",m)

这里用的是不定行参数,所以不管你装饰的类是有参数的还是没参数的,都可以。

5. 装饰器的应用场景

  • 函数运行时间统计;
  • 执行函数之前做准备工作;
  • 执行函数后清理功能;


参考链接

Python中的闭包

Python 中的作用域规则和闭包简析

Python: 携带状态的闭包

说说Python中的闭包 - Closure

Python 中的闭包与装饰器

一步一步教你认识Python闭包

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值