首页 > 编程语言 >Python笔记三之闭包与装饰器

Python笔记三之闭包与装饰器

时间:2024-01-05 22:56:37浏览次数:45  
标签:函数 Python 笔记 add 三之闭 func time 装饰 def

本文首发于公众号: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

如果想获取更多相关文章,可扫码关注阅读:
image

标签:函数,Python,笔记,add,三之闭,func,time,装饰,def
From: https://www.cnblogs.com/hunterxiong/p/17948263

相关文章

  • numpy学习笔记1
    numpy介绍NumPy(NumericalPython)是Python语言的一个扩展程序库,支持大量的维度数组与矩阵运算,此外也针对数组运算提供大量的数学函数库。NumPy是一个运行速度非常快的数学库,主要用于数组计算,包含:一个强大的N维数组对象ndarray广播功能函数整合C/C++/Fortran代码的......
  • 使用 Python 进行简单的人脸识别
    介绍人脸识别技术已经成为当今世界许多领域的重要应用,从安全领域到社交媒体,无处不在。Python提供了许多强大的库和工具,使得实现人脸识别变得更加容易。本文将介绍如何使用Python中的一些流行库来进行简单的人脸识别。准备工作在开始之前,确保你已经安装了以下库:OpenCV:用于......
  • Servlet(JSP)学习笔记
    目录IDEA配置JSP基本语法page指令ScriptLet标签注释包含跳转JSP四大作用域applicationsessionrequestpageJSP九大内置对象responseoutpageContextconfigexceptionJavaBean组件JavaBean组件引入创建JavaBean设置属性值获取属性值JavaBean的保存范围JavaBean的删除ServletHelloWorld......
  • python人脸识别
    应用范围广泛安全与监控:人脸识别在安防领域中具有显著的应用,可用于识别入侵者或追踪犯罪嫌疑人。社交媒体和相册组织:社交媒体平台和相册应用可以利用人脸识别来自动标记照片中的人物,方便用户组织和查找照片。医疗保健:人脸识别技术在医疗保健中用于患者身份验证和监测,例如识别......
  • 《金钱与我》纪录片笔记
    我们的钱都去哪了?食物支出外卖买特定牌子的,通常比较贵一些贷款,还债,通常有较高利率酒类咖啡礼物汽车、出行旅行、度假建议:机票等越早订越好,通常会更便宜,周中,周二周三等会比较便宜;放弃外卖;放弃名牌,食物等感觉不出来区别的,或者区别不大的,都换成较便宜的;便宜点的......
  • [NLP复习笔记] 朴素贝叶斯分类器
    1.贝叶斯决策论假设有\(N\)中类别标记\(\gamma=\{c_1,c_2,\dots,c_N\}\),\(\lambda_{ij}\)是将一个真实标记为\(c_{j}\)分类为\(c_i\)所产生的损失。基于后验概率\(P(c|\mathbf{x})\)可以得到样本\(\mathbf{x}\)分类为\(c_i\)的期望损失(\(\text{expectedlo......
  • Rust权威指南阅读笔记(一)Rust简介与安装
    参考:Windows10配置Rust开发环境|jonssonyan'Websitewin10安装Rust+VSCode配置Rust环境-鱼又悲-博客园(cnblogs.com)1.1Rust简介1.2安装Rusthttps://www.rust-lang.org/zh-CN/tools/install下载RUSTUP-INIT.EXE并运行如遇到:componentdownloadfailedforcli......
  • Python Pandas 数据清洗
    ​ 1、处理缺失数据处理缺失数据是数据清洗过程的一个重要部分。缺失数据可以以多种方式出现,最常见的是作为NaN(NotaNumber)。处理缺失数据涉及使用 isna() 或 isnull() 检测缺失值,fillna() 填充缺失值,dropna() 删除包含缺失值的行或列,以及 interpolate() 对缺失值进......
  • python-bs4获取图片
    一、用一个实例来了解一下 1、既然要用BeautifulSoup来解析,首先要把需要的模块导入;importosimportrequestsfrombs4importBeautifulSoup2、创建一个文件夹来存放要下载的数据:文件的名可以用户自定义file_name="imgs"ifnotos.path.exists(file_name......
  • 【Python入门教程】读取图片信息最全教程(经纬度、偏转角、无人机影像、大疆)
    ​    通常读取图片的属性信息(如经纬度、拍摄时间、IMU数据等)都是通过exifread库进行读取,但是有些图片用这个库读取的效果不好。所以今天我就和大家分享一下如何使用Python读取图片属性信息的三种方法。1GDAL读取    GDAL库是用来处理卫星影像的库,它同样可......