Python笔记三之闭包与装饰器

这篇具有很好参考价值的文章主要介绍了Python笔记三之闭包与装饰器。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

本文首发于公众号:Hunter后端

原文链接:Python笔记三之闭包与装饰器

这一篇笔记介绍 Python 里面的装饰器。

在介绍装饰器前,首先提出这样一个需求,我想统计某个函数的执行时间,假设这个函数如下:

import time

def add(x, y):
    time.sleep(1)
    return x + y

想要统计 add 函数的执行时间,可以如何操作,在一般情况下,可能会想到如下操作:

start_time = time.time()
add(1, 2)
end_time = time.time()
print("函数执行时间为:", end_time - start_time)

而如果我们想要统计很多个函数的执行时间,然后打印出来,应该如何操作呢?

这里就可以用上 Python 里装饰器的操作。

本篇笔记目录如下:

  1. 闭包
    1. 闭包
    2. 闭包实现计数器
      1. 自由变量
  2. 装饰器
  3. 装饰器代码示例装饰器原理
  4. 装饰器加参数
  5. 多重装饰器
  6. 装饰器类

1、闭包

在介绍装饰器前,先来理解一下闭包的概念。

1. 闭包

我们知道,一个函数内部的变量是局部变量,在函数执行结束之后,函数内部的变量就会被销毁,而闭包,则可以使我们能够读取函数内部变量。

比如下面这个示例:

def outer_func():
    msg = "outer info"

    def inner_func():
        print(msg)
        return msg

    return inner_func


func = outer_func()
func()

关于闭包,2023.11.13 百度百科的释义如下:

闭包就是能够读取其他函数内部变量的函数。例如在javascript中,只有函数内部的子函数才能读取局部变量,所以闭包可以理解成“定义在一个函数内部的函数“。在本质上,闭包是将函数内部和函数外部连接起来的桥梁。

所以闭包的作用可以是避免全局变量可能带来的维护问题,又能够长久的保存变量。

但是同时,基于这个特性,闭包函数内部的局部变量因为会保持在内存中,不会在调用后被自动清除,所以需要注意其可能带来的内存泄漏的问题。

2. 闭包实现计数器

下面我们使用闭包来实现一个计数器的功能:

def create_counter():
    count = 0

    def add_counter():
        nonlocal count
        count += 1
        return count
    return add_counter

f = create_counter()
print(f())
print(f())
print(f())

这里使用 nolocalcount 变量进行了声明,作用是声明该变量只在函数局部内起作用,也就是 create_counter() 内,所以在 add_counter() 外声明 count 变量之后,在 add_counter() 内可以保存其相应的状态,也就是这里我们的计数功能。

nolocal 关键字是专门定义在闭包内使用的。

相对应的 global 字段时定义的全局变量,这里不多做介绍了。

自由变量

自由变量的含义是指未绑定到本地作用域的变量,比如上面的示例里,countadd_counter() 函数里就是一个自由变量,因为它在外层函数 create_counter() 里定义,但没有在内层的 add_counter() 中定义。

至于为什么在 add_counter() 里对 count 变量进行 nolocal 的声明,是因为修饰的对象类型是 int,与之类似的还有 strtuple,他们都属于不可变类型。

而如果我们闭包的内外部函数里的对象是 list,dict 这种可变类型,那么则不需要使用 nolocal 来进行修饰,比如下面的操作:

def create_counter():
    count_dict = [0]  
    
    def add_counter():        
        count_dict[0] += 1        
        return count_dict[0]    
        
    return add_counter

2、 装饰器

装饰器的作用是在不修改被装饰函数的情况下,给被装饰的函数添加额外的功能。

而装饰器就是基于闭包的操作,不过外层函数传入的参数是被装饰的函数,且在 Python 里,使用装饰器的方式是在被装饰函数前加一行,使用 @ 符号来调用。

最简单的装饰器的操作如下:

def decorator(func):
    print("calling decorator ...")
    return func


@decorator
def test():
    print("calling test ...")

我们在下面的操作中使用一个示例介绍如何基于闭包使用装饰器。

3、装饰器代码示例

前面我们介绍了一个需求场景,需要统计函数的执行时间,基于这个需求,我们就可以使用装饰器的操作来完成,以下是代码示例:

import time

def time_decorator(func):

    def inner_func(*args, **kwargs):
        start_time = time.time()
        result = func(*args, **kwargs)
        total_time = time.time() - start_time
        print("func 耗时:", total_time)
        return result
    return inner_func


@time_decorator
def add(x, y):
    time.sleep(1)
    return x + y

add(1, 7)

装饰器原理

我们使用 @ 加上装饰器函数名称,即表示调用这个装饰器,然后将被装饰的函数,上面的示例是 add() 函数,作为参数传入装饰器,然后在内部函数 inner_func() 中添加额外的功能,这里是统计函数运行时间,然后将其返回。

将装饰器的操作扁平化操作,就和前面闭包示例计数器的使用是一致的:

def add(x, y):
    time.sleep(1)
    return x + y

func = time_decorator(add)
func(1, 2)

所以,在加了装饰器的函数运行中,实际上运行的是装饰器的内部函数,我们可以通过打印函数的名称来进行验证:

print(add.__name__)  # inner_func

如果想要保存原函数的基本信息,比如函数名称,我们可以给装饰器的内部函数加上装饰器自动复制函数信息,functools.wraps,使用示例如下:

import time
import functools

def time_decorator(func):

    @functools.wraps(func)
    def inner_func(*args, **kwargs):
        start_time = time.time()
        result = func(*args, **kwargs)
        total_time = time.time() - start_time
        print("func 耗时:", total_time)
        return result
    return inner_func

@time_decorator
def add(x, y):
    time.sleep(1)
    return x + y
   
print(add.__name__)  # add

这样打印的就是原始函数的函数名称了。

4、装饰器加参数

如果我们想调用装饰器的时候,给装饰器加一个参数,比如这里的 time_decorator,想加一个默认的时间参数(这个想要实现的功能可能并没有实际意义,纯粹是为了实现给装饰器加默认参数这个功能),调用的时候就是:

@time_decorator(default_time=2)

那么装饰器的定义则如下所示:

def time_decorator(default_time=2):
    def decorator(func):
        def inner_func(*args, **kwargs):
            start_time = time.time()
            time.sleep(default_time)
            result = func(*args, **kwargs)
            total_time = time.time() - start_time
            print("func 耗时:", total_time)
            return result
        return inner_func
    return decorator


@time_decorator(2)
def add(x, y):
    time.sleep(1)
    return x + y

add(1, 8)

如果调用装饰器的时候想使用默认参数,直接不赋值即可:

@time_decorator()
def add(x, y):
    time.sleep(1)
    return x + y

5、多重装饰器

如果我们想要调用多个装饰器来装饰一个函数,其执行顺序是怎么要的呢,我们可以用下面的例子做个实验。

比如我们要做一个汉堡,最外层两片面包,中间夹两片青菜,最中间是一片肉,可以如下操作:

def bread_decorator(func):

    def inner(*args, **kwargs):
        print("先加片面包")
        func(*args, **kwargs)
        print("再加片面包")
    return inner


def vegetable_decorator(func):

    def inner(*args, **kwargs):
        print("先加片蔬菜")
        func(*args, **kwargs)
        print("再加片蔬菜")
    return inner


@bread_decorator
@vegetable_decorator
def make_hamburger():
    print("加片肉")


make_hamburger()

输出的结果为:

先加片面包
先加片蔬菜
加片肉
再加片蔬菜
再加片面包

所以这里装饰器的执行时按照顺序从上到下执行的。

我们可以尝试将装饰器的调用拉平,用到的其实就是设计模式里的装饰器模式了(设计模式的几种类型我回头会更新一个系列),我们先将 make_hamburger() 的函数重新定义,然后调用,bread_decorator()vege_decorator() 还是保持不变:

def make_hamburger():    
    print("加片肉")

food = vegetable_decorator(make_hamburger)
food = bread_decorator(food)
food()

执行的结果和前面使用装饰器的方式调用是一致的。

6、装饰器类

前面介绍的是用函数作为装饰器,我们还可以设计一个类用作装饰器,示例如下:

class TimeLogDecorator:
    def __init__(self, func):
        self.func = func

    def __call__(self, *args, **kwargs):

        start_time = time.time()
        result = self.func(*args, **kwargs)
        print(f"函数 {self.func.__name__} 运行时间为:{time.time() - start_time}")
        return result


@TimeLogDecorator
def add(x, y):
    time.sleep(1)
    return x + y


result = add(1, 6)

在类的 __call__ 方法写入我们在函数装饰器的内部函数里的内容即可实现装饰器的功能。

如果想要给类装饰器带参数的话,示例如下:

class TimeLogDecoratorArg:
    def __init__(self, base_gap_time):
        self.base_gap_time = base_gap_time

    def __call__(self, func):

        def inner_func(*args, **kwargs):
            start_time = time.time()
            time.sleep(self.base_gap_time)
            result = func(*args, **kwargs)
            print(f"函数 {func.__name__} 运行时间为:{time.time() - start_time}")
            return result
        return inner_func


@TimeLogDecoratorArg(2)
def add(x, y):
    time.sleep(1)
    return x + y

如果想获取更多相关文章,可扫码关注阅读:
Python笔记三之闭包与装饰器文章来源地址https://www.toymoban.com/news/detail-779922.html

到了这里,关于Python笔记三之闭包与装饰器的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • Python 知识体系补全(闭包、装饰器、关键字参数**kwargs,位置参数*args)

    闭包:     什么是闭包,一句话,在函数中再嵌套一个函数,并且引用外部函数的变量,这就是一个闭包了     一般外部函数的返回值为内部函数      def outer(x):          def inner(y):              return x + y         return outer     # 外部函数的返回值,是内部函数     

    2024年02月06日
    浏览(39)
  • Python连接es笔记三之es更新操作

    本文首发于公众号:Hunter后端 原文链接:Python连接es笔记三之es更新操作 这一篇笔记介绍如何使用 Python 对数据进行更新操作。 对于 es 的更新的操作,不用到 Search() 方法,而是直接使用 es 的连接加上相应的函数来操作,本篇笔记目录如下: 获取连接 update() update_by_query() 批

    2024年02月07日
    浏览(48)
  • 闭包和装饰器

    在函数内部再定义⼀个函数,并且这个内部函数⽤到了外部的变量,这个函数以及⽤到外部函数的变量及参数叫闭包 再比如二次函数: 从这段代码中,函数line与变量a,b构成闭包。在创建闭包的时候,我们通过line_conf的参数a,b说明了这两个变量的取值,这样,我们就确定了函数

    2024年02月12日
    浏览(31)
  • js封装SDK 在VUE、小程序、公众号直接调用js调用后端接口(本文以vue项目为例)

    1.封装一个js文件 msgSdk.js 注意:需要修改这个请求地址  apiServiceAddress 2.在index.html中引入 msgSdk.js文件 和 jquery文件 3.在页面中调用

    2024年04月27日
    浏览(38)
  • Django笔记三十三之缓存操作

    本文首发于公众号:Hunter后端 原文链接:Django笔记三十三之缓存操作 这一节介绍一下如何在 Django 中使用 redis 做缓存操作。 在 Django 中可以有很多种方式做缓存,比如数据库,比如服务器文件,或者内存,这里介绍用的比较多的使用 redis 作为缓存。 这篇笔记主要内容如下:

    2024年02月01日
    浏览(53)
  • celery笔记三之task和task的调用

    本文首发于公众号:Hunter后端 原文链接:celery笔记三之task和task的调用 这一篇笔记介绍 task 和 task 的调用。 以下是本篇笔记目录: 基础的 task 定义方式 日志处理 任务重试 忽略任务运行结果 task 的调用 前面两篇笔记中介绍了最简单的定义方式,使用 @app.task 作为装饰器:

    2024年02月08日
    浏览(39)
  • python第6课(函数,内置高阶函数,匿名函数,装饰器)笔记

    变量名的查找规则; 在访问变量时,先查找本地变量,然后是包裹此函数的外部函数的内部变量,之后是全局变量,最后是内置变量 1 在函数内使用全局变量 2使用nonlocal声明的变量不是局部变量,也不是全局变量,而是外部嵌套函数内的变量 函数名是变量,它在创建函数时绑

    2024年02月11日
    浏览(47)
  • Python笔记2(函数参数、面向对象、装饰器、高级函数、捕获异常、dir)

    Python笔记1(赋值、浅拷贝和深拷贝、字符串日期转换、argparse、sys、overwrite、eval、json.dumps/json.loads、os.system(cmd)、zfill、endswith、open) 参数定义 在python中定义函数的时候,函数名后面的括号里就是用来定义参数的,如果有多个参数的话,那么参数之间直接用逗号, 隔开。 定义

    2024年02月08日
    浏览(50)
  • Django笔记四十三之使用uWSGI部署Django系统

    本文首发于公众号:Hunter后端 原文链接:Django笔记四十三之使用uWSGI部署Django系统 目前部署 Django 的方式一般来说是使用 Nginx + uWSGI + Django 来实现。 处理流程是,当一个请求发送过来会先经过 Nginx,如果是静态文件请求,Nginx 这一层直接处理,如果是后端动态接口,则会发送

    2024年02月05日
    浏览(64)
  • es笔记三之term,match,match_phrase 等查询方法介绍

    本文首发于公众号:Hunter后端 原文链接:es笔记三之term,match,match_phrase 等查询方法介绍 首先介绍一下在 es 里有两种存储字符串的字段类型,一个是 keyword,一个是 text。 keyword 在存储数据的时候是作为一个整体存储的,不会对其进行分词处理 text 存储数据的时候会对字符串

    2024年02月05日
    浏览(51)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包