闭包&装饰器

Posted fort

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了闭包&装饰器相关的知识,希望对你有一定的参考价值。

闭包

1.函数引用

def test():
    print(--test--)

# 调用函数
test()
# 引用函数
ret = test

print(id(ret))
print(id(test))

# 通过引用调用函数
ret()

#输出结果
--test--
1718807047704
1718807047704
--test--

 

2.什么是闭包

# 定义一个函数
def test(num):
    # 在函数内部再定义一个函数,并且这个函数用到外部函数的变量,那么将这个函数以及用到的一些变量称之为闭包
    def inner_test(inner_num):
        print(inner_num:%d %inner_num)
        return num +inner_num
    # 其实这里返回的就是闭包的结果
    return inner_test

# 给test函数赋值,这个20就是给参数num
ret = test(10)
# 这里的50其实给参数inner_num
print(ret(50))
# 这里的100其实给参数inner_num
print(ret(100))
# 输出结果: inner_num:50 60 inner_num:100 110

 

3. 一个闭包的实际例子:

def line_conf(a, b):
    def line(x):
        return a*x + b
    return line

line1 = line_conf(1, 1)
line2 = line_conf(4, 5)
print(line1(5))
print(line2(5))

这个例子中,函数line与变量a,b构成闭包。在创建闭包的时候,我们通过line_conf的参数a,b说明了这两个变量的取值,这样,我们就确定了函数的最终形式(y = x + 1和y = 4x + 5)。我们只需要变换参数a,b,就可以获得不同的直线表达函数。由此,我们可以看到,闭包也具有提高代码可复用性的作用。

如果没有闭包,我们需要每次创建直线函数的时候同时说明a,b,x。这样,我们就需要更多的参数传递,也减少了代码的可移植性。

注意点:

由于闭包引用了外部函数的局部变量,则外部函数的局部变量没有及时释放,消耗内存

 

4. 修改外部函数中的变量

python3的方法:

def outer(start=0):
    def inner():
        nonlocal start
        start += 1
        return start
    return inner

o1= outer(5)
print(o1())  # 6
print(o1())  # 7

o2 = outer(10)
print(o2())  # 11
print(o2())  # 12

python2的方法:

def outer(start=0):
    count=[start]
    def inner():
        count[0] += 1
        return count[0]
    return inner

o1 = closeure.outer(5)
print(o1())  # 6
print(o1())  # 7
o2 = closeure.outer(10)
print(o2())  # 11
print(o2())  # 12

 

装饰器

1、先明白这段代码

#### 第一波 ####
def foo():
    print(foo)

foo  # 表示是函数
foo()  # 表示执行foo函数

#### 第二波 ####
def foo():
    print(foo)

foo = lambda x: x + 1

foo()  # 执行lambda表达式,而不再是原来的foo函数,因为foo这个名字被重新指向了另外一个匿名函数

函数名仅仅是个变量,只不过指向了定义的函数而已,所以才能通过函数名()调用,如果 函数名=xxx被修改了,那么当在执行 函数名()时,调用的就不知之前的那个函数了

 

2、需求

初创公司有N个业务部门,基础平台部门负责提供底层的功能,如:数据库操作、redis调用、监控API等功能。业务部门使用基础功能时,只需调用基础平台提供的功能即可。如下:

############### 基础平台提供的功能如下 ###############

def f1():
    print(f1)

def f2():
    print(f2)

def f3():
    print(f3)

def f4():
    print(f4)

############### 业务部门A 调用基础平台提供的功能 ###############

f1()
f2()
f3()
f4()

############### 业务部门B 调用基础平台提供的功能 ###############

f1()
f2()
f3()
f4()

目前公司有条不紊的进行着,但是,以前基础平台的开发人员在写代码时候没有关注验证相关的问题,即:基础平台的提供的功能可以被任何人使用。现在需要对基础平台的所有功能进行重构,为平台提供的所有功能添加验证机制,即:执行功能前,先进行验证。

老大把工作交给员工C,他是这么做的:

############### 基础平台提供的功能如下 ############### 

def f1():
    # 验证1
    # 验证2
    # 验证3
    print(f1)

def f2():
    # 验证1
    # 验证2
    # 验证3
    print(f2)

def f3():
    # 验证1
    # 验证2
    # 验证3
    print(f3)

def f4():
    # 验证1
    # 验证2
    # 验证3
    print(f4)

############### 业务部门不变 ############### 
### 业务部门A 调用基础平台提供的功能### 

f1()
f2()
f3()
f4()

### 业务部门B 调用基础平台提供的功能 ### 

f1()
f2()
f3()
f4()

老大把工作交给员工D,他是这么做的:

############### 基础平台提供的功能如下 ############### 

def check_login():
    # 验证1
    # 验证2
    # 验证3
    pass


def f1():

    check_login()

    print(f1)

def f2():

    check_login()

    print(f2)

def f3():

    check_login()

    print(f3)

def f4():

    check_login()

    print(f4)

老大看了下员工D的实现,嘴角漏出了一丝的欣慰的笑,语重心长的跟员工D聊了个天:

写代码要遵循开放封闭原则,虽然在这个原则是用的面向对象开发,但是也适用于函数式编程,简单来说,它规定已经实现的功能代码不允许被修改,但可以被扩展,即:

  • 封闭:已实现的功能代码块
  • 开放:对扩展开发

如果将开放封闭原则应用在上述需求中,那么就不允许在函数 f1 、f2、f3、f4的内部进行修改代码,老板就给了员工D一个实现方案:

def w1(func):
    def inner():
        # 验证1
        # 验证2
        # 验证3
        func()
    return inner

@w1
def f1():
    print(f1)
@w1
def f2():
    print(f2)
@w1
def f3():
    print(f3)
@w1
def f4():
    print(f4)

 

# 单独以f1为例:

def w1(func):
    def inner():
        # 验证1
        # 验证2
        # 验证3
        func()
    return inner

@w1
def f1():
    print(f1)

python解释器就会从上到下解释代码,步骤如下:

  1. def w1(func): ==>将w1函数加载到内存
  2. @w1

没错,从表面上看解释器仅仅会解释这两句代码,因为函数在 没有被调用之前其内部代码不会被执行。

从表面上看解释器着实会执行这两句,但是 @w1 这一句代码里却有大文章, @函数名 是python的一种语法糖。

上例@w1内部会执行一下操作:

执行w1函数

执行w1函数 ,并将 @w1 下面的函数作为w1函数的参数,即:@w1 等价于 w1(f1) 所以,内部就会去执行:

def inner(): 
    #验证 1 
    #验证 2 
    #验证 3 
    f1() # func是参数,此时 func 等于 f1 
return inner# 返回的 inner,inner代表的是函数,非执行函数 ,其实就是将原来的 f1 函数塞进另外一个函数中

w1的返回值

将执行完的w1函数返回值 赋值 给@w1下面的函数的函数名f1 即将w1的返回值再重新赋值给 f1,即:

新f1 = def inner(): 
        #验证 1 
        #验证 2 
        #验证 3 
        原来f1() 
 return inner

所以,以后业务部门想要执行 f1 函数时,就会执行 新f1 函数,在新f1 函数内部先执行验证,再执行原来的f1函数,然后将原来f1 函数的返回值返回给了业务调用者。

如此一来, 即执行了验证的功能,又执行了原来f1函数的内容,并将原f1函数返回值 返回给业务调用者

 

3. 装饰器(decorator)功能

  1. 引入日志

  2. 函数执行时间统计

  3. 执行函数前预备处理

  4. 执行函数后清理功能

  5. 权限校验等场景

  6. 缓存

 

4. 装饰器示例

例1:无参数的函数

技术分享图片
import time

def set_func(foo):
    def call_func():
        start_time = time.time()
        foo()
        end_time = time.time()
        print("运行时间:%f" % (end_time-start_time))
    return call_func

@set_func
def test():
    print("test")
    for i in range(100000):
        pass
test()

test = set_fun(foo)

# test先作为参数赋值给foo后,test接收指向set_fun返回的call_func
# 调用test(),即等价调用call_func()
# 内部函数call_func被引用,所以外部函数的foo变量(自由变量)并没有释放
# foo里保存的是原test函数对象
View Code

例2:带参数函数的装饰

技术分享图片
def set_func(foo):
    def call_func(num):
        foo(num)
    return call_func

@set_func
def test(num):
    print(num)

test(11)
View Code

例3:对多个函数进行装饰

技术分享图片
def set_func(foo):
    def call_func():
        foo()
    return call_func

@set_func
def test1():
    print("test1")

@set_func
def test2():
    print("test2")

test1()
test2()
View Code

例4:在调用函数之前已经开始装饰

技术分享图片
def set_func(foo):
    print("开始装饰")
    def call_func():
        foo()
    return call_func

@set_func
def test1():
    print("test1")
View Code

例5:对不定长参数的函数装饰

技术分享图片
def set_func(foo):
    def call_func(*args, **kwargs):
        foo(*args, **kwargs)
        print("--end--")
    return call_func

@set_func
def test1(num, *args, **kwargs):
    print("test1",num)
    print("test1",args)
    print("test1",kwargs)

test1(11)
test1(11,22,33)
test1(11,22,33,a=55)
View Code

例6;对有返回值的函数装饰(通用装饰器)

技术分享图片
def set_func(foo):
    def call_func(*args, **kwargs):
        return foo(*args, **kwargs)
    return call_func

@set_func
def test1():
    print("test1")
    return "ok"
ret = test1()
print(ret)
View Code

例7:多个装饰器对一个函数装饰

技术分享图片
def set_func1(foo):
    print("装饰器1开始装饰")
    def call_func(*args, **kwargs):
        print("装饰器1功能")
        return foo(*args, **kwargs)
    return call_func

def set_func2(foo):
    print("装饰器2开始装饰")
    def call_func(*args, **kwargs):
        print("装饰器2功能")
        return foo(*args, **kwargs)
    return call_func

@set_func1
@set_func2
def test1():
    print("test1")

test1()

# 输出结果:
装饰器2开始装饰
装饰器1开始装饰
装饰器1功能
装饰器2功能
test1
View Code
技术分享图片
def set_func1(foo):
    print("装饰器1开始装饰")
    def call_func():
        return "<tr>"+foo()+"</tr>"
    return call_func

def set_func2(foo):
    print("装饰器2开始装饰")
    def call_func():
        return "<td>" + foo() + "</td>"
    return call_func

@set_func1
@set_func2
def test1():
    return "ok"
print(test1())

# 输出结果:
装饰器2开始装饰
装饰器1开始装饰
<tr><td>ok</td></tr>
View Code

例8:用类对函数进行装饰

技术分享图片
class Test(object):
    def __init__(self, func):
        self.func = func

    def __call__(self, *args, **kwargs):
        print("装饰器的功能")
        return self.func(*args, **kwargs)

@Test
def test():
    return "ok"

print(test())

#说明:
#1. 当用Test来装作装饰器对test函数进行装饰的时候,首先会创建Test的实例对象
#   并且会把test这个函数名当做参数传递到__init__方法中
#   即在__init__方法中的属性func指向了test指向的函数
#
#2. test指向了用Test创建出来的实例对象
#
#3. 当在使用test()进行调用时,就相当于让这个对象(),因此会调用这个对象的__call__方法
#
#4. 为了能够在__call__方法中调用原来test指向的函数体,所以在__init__方法中就需要一个实例属性来保存这个函数体的引用
#   所以才有了self.func = func这句代码,从而在调用__call__方法中能够调用到test之前的函数体
View Code

例9:装饰器带参数,在原有装饰器的基础上,设置外部变量

技术分享图片
def level(level_num):
    def set_func(func):
        def call_func(*args, **kwargs):
            if level_num == 1:
                print("极品货")
            elif level_num == 2:
                print("A货")
            func(*args, **kwargs)
        return call_func
    return set_func


@level(1)
def test1():
    print("ok")

@level(2)
def test2():
    print("ok")

test1()
test2()
# 下面的装饰过程
# 1. 调用level("1")
# 2. 将步骤1得到的返回值,即set_func返回, 然后set_func(func)
# 3. 将set_func(func)的结果返回,即call_func
# 4. 让test1 = call_func,即test1现在指向call_func
View Code

 



以上是关于闭包&装饰器的主要内容,如果未能解决你的问题,请参考以下文章

python 装饰器&生成器&迭代器

闭包函数,装饰器

装饰器语法糖运用

Python闭包和装饰器

9.23闭包函数/装饰器/迭代器/生成器

python函数作用域+装饰器