ThankNeko's Blog ThankNeko's Blog
首页
  • 操作系统

    • Linux基础
    • Linux服务
    • WindowsServer笔记
    • Ansible笔记
    • Shell笔记
  • 容器服务

    • Docker笔记
    • Kubernetes笔记
    • Git笔记
  • 数据库服务

    • MySQL笔记
    • ELK笔记
    • Redis笔记
  • 监控服务

    • Zabbix笔记
  • Web服务

    • Nginx笔记
    • Tomcat笔记
  • 数据处理

    • Kettle笔记
  • Python笔记
  • Bootstrap笔记
  • C笔记
  • C++笔记
  • Arduino笔记
  • 分类
  • 标签
  • 归档
  • 随笔
  • 关于
GitHub (opens new window)

Hoshinozora

尽人事,听天命。
首页
  • 操作系统

    • Linux基础
    • Linux服务
    • WindowsServer笔记
    • Ansible笔记
    • Shell笔记
  • 容器服务

    • Docker笔记
    • Kubernetes笔记
    • Git笔记
  • 数据库服务

    • MySQL笔记
    • ELK笔记
    • Redis笔记
  • 监控服务

    • Zabbix笔记
  • Web服务

    • Nginx笔记
    • Tomcat笔记
  • 数据处理

    • Kettle笔记
  • Python笔记
  • Bootstrap笔记
  • C笔记
  • C++笔记
  • Arduino笔记
  • 分类
  • 标签
  • 归档
  • 随笔
  • 关于
GitHub (opens new window)
  • Python笔记

    • 基础知识

      • 常见规范与运行方式
      • 变量与垃圾回收机制
      • 输入与格式化输出
      • 运算符
      • 流程控制语句
      • 浅拷贝和深拷贝
      • 常用数据类型与分类
      • 数据类型方法
      • 字符编码
      • 文件操作
      • 函数与参数
      • 命名空间与作用域
      • 闭包函数与装饰器
        • 函数对象
        • 闭包函数
        • 装饰器介绍
        • 开放封闭原则
        • 无参装饰器
        • 有参装饰器
        • 装饰器语法糖
        • 叠加装饰器
        • 伪装函数属性
      • 迭代器与生成器
      • 三元表达式与生成式
      • 函数递归
      • 面向过程式和函数式编程
      • 模块与包
      • 程序设计目录参考
      • 常用内置模块或函数
      • 序列化和猴子补丁
      • 日志模块-logging
    • 类与面向对象

    • 并发编程

    • Web编程

    • 模块笔记

    • 其他

  • C笔记

  • C++笔记

  • Arduino笔记

  • Dev
  • Python笔记
  • 基础知识
Hoshinozora
2023-02-15
目录

闭包函数与装饰器

# 函数对象

# 介绍

即把函数内存地址当成对象进行传递,作为对象时,只需要写函数名,不可以加()。

def test():
    print(123)
# 将test函数的内存地址给a,使得调用a同等于调用test
a = test
a()
1
2
3
4
5

# 应用案例

通过字典存储或调用函数对象。

def login():
    ...
def register():
    ...

# 存储函数对象
func_dic = {
    '1': ('登录', login),
    '2': ('注册', register
         ),
    '0': ('退出', None)
    }
for k in func_dic:
    print(k, func_dic[k][0])
    choice = input("请输入编号 >")
    if choice in func_dic:
    # 调用函数对象
    func_dic[choice][1]()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

# 闭包函数

# 闭包函数

闭包函数结合使用了名称空间、作用域、函数嵌套、函数对象。

由于名字的查找关系是以函数定义阶段为准。所以闭包即是无论在哪里调取函数,使用的都是包里已经包好的名字与值。

# 闭

闭指的是该函数是内嵌函数,无法被直接访问到。

def f1():
    def f2():
        pass
1
2
3

# 包

包指的是该内嵌函数,包含对外n层函数作用域名字的引用,不包括全局作用域名字的引用。

def f1():
    x = 1
    def f2():
        def f3():
            print(x)
1
2
3
4
5

# 传递闭包的函数对象

我们将闭包函数作为对象传递给全局名称,此时闭包函数对象被全局名称引用。

由于闭包函数可以被全局名称访问到,所以闭包函数所包含的局部变量也不会被回收。

直到全局名称与闭包函数对象解除绑定关系,闭包函数所引用的变量空间才会被释放掉。

def f1():
    x = 123
    def f2():
        print(x)
    return f2
f = f1()
f()
>> 输出 123
1
2
3
4
5
6
7
8

# 装饰器介绍

装饰器是为其他函数添加额外功能的函数,会使用到*args、**kwargs、闭包函数。

装饰器的名称应当尽量描述装饰器的功能。

装饰指为其他事物添加额外的东西点缀,器指工具。

装饰器可以实现开放封闭原则,能在不修改函数源代码、调用方式、返回值的前提下,为其添加新的扩展功能。

# 开放封闭原则

开放,指的是对扩展功能是开放的。

封闭,指的是对修改源代码是封闭的。

想要在原函数之上增加一些额外扩展的新功能,不应该直接修改原函数。而是应该使用装饰器函数,在原有函数的基础上添加额外的功能。

因为原函数可能在很多地方都被调用,如果贸然修改则可能造成程序崩溃。

# 无参装饰器

例如在不修改源代码的情况下,添加计时功能。

# 方案一

直接修改原函数内代码。

缺点:违背开放封闭原则,可能造成其他调用该函数的地方出现问题。

import time

def say(content, times=1):
    start = time.time()
    time.sleep(1)
    print(content * times)  # 原代码被包裹
    stop = time.time()
    print(stop - start)

say("hello")
1
2
3
4
5
6
7
8
9
10

# 方案二

不修改原函数内代码,直接在原函数调用的前后添加代码。

缺点:需要反复使用时会代码冗余。

import time

def say(content, times=1):
    print(content * times)

start = time.time()
time.sleep(1)
say("hello")  # 函数调用被包裹
stop = time.time()
print(stop - start)
1
2
3
4
5
6
7
8
9
10

# 方案三

不修改原函数内代码,使用装饰函数包裹原函数和调用。

缺点:调用方式会被改变。

import time

def say(content, times=1):
    print(content * times)

def timer(func, *args, **kwargs):
    start = time.time()
    time.sleep(1)
    func(*args, **kwargs)  # 调用传入的函数
    stop = time.time()
    print(stop - start)

timer(say, "hello")
1
2
3
4
5
6
7
8
9
10
11
12
13

# 方案四:无参装饰器

无参装饰器会对传入的函数对象进行装饰,装饰完后返回装饰好的函数对象。

  1. 调用装饰器函数将被装饰函数传入装饰器,同时使用被装饰函数的名称,来接收装饰器函数返回的函数对象。

    名称相同同等于调用方式没有改变,又实现了装饰功能。

  2. 装饰器函数中定义了闭包函数,闭包函数会对传入的函数进行装饰。

    闭包函数中有装饰代码和对被装饰函数的调用。同时会接收被装饰函数的返回值,作为闭包函数的返回值。

  3. 装饰器函数返回闭包函数的函数对象。

  4. 最后通过原函数名称来调用装饰后的函数即可。

import time

def timer(func):
    def wrapper(*args, **kwargs):
        start = time.time()
        time.sleep(1)
        ret = func(*args, **kwargs)  # 调用传入的函数
        stop = time.time()
        print(stop - start)
        return ret  # 返回传入的函数的返回值
    return wrapper

def say(content, times=1):
    print(content * times)

say = timer(say)
say("hello ", 2)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

# 有参装饰器

如果装饰器函数需要传入参数,则需要再嵌套一层,以实现有参装饰器。

# 方案一

直接传递参数,两层嵌套就能实现。

缺点:调用方式会被改变。

import time

def timer(func, sleeptime=1):
    def wrapper(*args, **kwargs):
        start = time.time()
        time.sleep(sleeptime) # 使用传入的参数
        ret = func(*args, **kwargs)  # 调用传入的函数
        stop = time.time()
        print(stop - start)
        return ret  # 返回传入的函数的返回值
    return wrapper

def say(content, times=1):
    print(content * times)

say = timer(say,2)
say("hello ", 2)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

# 方案二 - 有参装饰器

在无参装饰器的基础上,wrapper函数外面再加了一层函数嵌套,用于参数的传递,保证了调用方式不变。

最外层:用于函数所需要的参数的传递,并返回中间层的函数对象内存地址。

中间层:用于被装饰函数对象的传递,并返回最内层的函数对象内存地址。

最内层:用于装饰传入的函数对象,并返回函数对象的返回值。

import time

def timer(sleep=1):
    def deco(func):
        def wrapper(*args, **kwargs):
            start = time.time()
            time.sleep(sleep)
            ret = func(*args, **kwargs)  # 调用传入的函数
            stop = time.time()
            print(stop - start)
            return ret  # 返回传入的函数的返回值
        return wrapper
    return deco

def say(content, times=1):
    print(content * times)

say = timer(3)(say)

say("hello ", 2)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

# 装饰器语法糖

语法糖是Python提供给我们简化代码的语法,装饰器的语法糖用于简化装饰器的调用,使用方式是在被装饰器函数定义上方添加:@无参装饰器函数名 或 @有参装饰器函数名(参数)

@无参装饰器函数名 无参装饰器函数必须有且只能有一个参数,用于接收被装饰对象。

@有参装饰器函数名(参数) @后跟函数调用可以应用于有参装饰器,我们可以用来传递参数,它会将该函数调用的返回结果作为无参装饰器函数,所以必须返回无参装饰器函数对象。

import time

def timer(sleep=1):
    def deco(func):
        def wrapper(*args, **kwargs):
            start = time.time()
            time.sleep(sleep)
            ret = func(*args, **kwargs)  # 调用传入的函数
            stop = time.time()
            print(stop - start)
            return ret  # 返回传入的函数的返回值
        return wrapper
    return deco

# 调用装饰器语法糖
@timer(3)
def say(content, times=1):
    print(content * times)

say("hello ", 2)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

@timer(3)会将实参3传入装饰器函数内并返回deco对象,变成@deco。

@deco会将下方的被装饰函数作为对象传入deco函数并返回wrapper对象。

然后语法糖会将wrapper对象赋值给被装饰函数相同的名称say。

所以此处的@timer(3)同等于say = timer(3)(say)

# 叠加装饰器

一个函数可以被多个装饰器装饰。装饰时,会在原有装饰后对象的基础上再进行装饰。调用时,是先执行最外面的装饰代码,然后往内执行装饰代码。

# 同等于 run = timer3(timer2(timer1(run)))
@timmer3
@timmer2
@timmer1
def run(x):
    ...
1
2
3
4
5
6

# 伪装函数属性

# 手动伪装

wrapper.__name__ = func.__name__ 伪装函数名属性。

wrapper.__doc__ = func.__doc__ 伪装函数帮助信息。

添加在装饰器内,返回闭包函数对象之前添加(闭包函数外面)。

def timer(func):
    def wrapper(*args, **kwargs):
        ... ...
    wrapper.__name__ = func.__name__
    wrapper.__doc__ = func.__doc__
    return wrapper
1
2
3
4
5
6

# wraps函数

一个函数有很多属性,所以Python提供了用于复制所有函数属性的伪装装饰器。

from functools import wraps 导入伪装装饰器。

@wraps(函数) 会将括号内函数的属性,复制给下面一行被装饰的函数。

我们在装饰器中闭包函数上面一行添加@wraps(func)即可。

from functools import wraps

def timer(func):
    @wraps(func)
    def wrapper(*args, **kwargs):
        ... ...
    return wrapper
1
2
3
4
5
6
7

没有要求其实不伪装函数属性也行,一般不影响使用。

#函数对象#闭包函数#装饰器#开放封闭原则#无参装饰器#有参装饰器#装饰器语法糖
命名空间与作用域
迭代器与生成器

← 命名空间与作用域 迭代器与生成器→

最近更新
01
二〇二五年四月十七日随笔
04-17
02
二〇二五年四月十六日随笔
04-16
03
二〇二五年四月九日随笔
04-09
更多文章>
Theme by Vdoing | Copyright © 2022-2025 Hoshinozora | MIT License
湘ICP备2022022820号-1
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式