本文首发于公众号: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笔记三之闭包与装饰器的更多相关文章

  1. python基础(三)闭包与装饰器

    闭包(closure): 内嵌函数通过调用外部嵌套函数作用域内的变量,则这个内嵌函数就是闭包. 闭包必须满足三个条件: 必须有一个内嵌函数 内嵌函数必须引用外部嵌套函数中的变量 外部函数的返回值必须是 ...

  2. Python之命名空间、闭包、装饰器

    一.命名空间 1. 命名空间 命名空间是一个字典,key是变量名(包括函数.模块.变量等),value是变量的值. 2. 命名空间的种类和查找顺序 - 局部命名空间:当前函数 - 全局命名空间:当前模 ...

  3. Python 变量作用域,闭包和装饰器

    from dis import dis b = 6 def f1(a): print(a)print(b) b = 9 f1(3) print(dis(f1)) # dis模块可以查看python函数 ...

  4. python函数作用域,闭包,装饰器

    第一:函数作用域: L:local 函数内部作用域 E:enclosing       函数内部与内嵌函数之间(闭包) G:global            全局作用域 B:build_in    ...

  5. Python之面向对象:闭包和装饰器

    一.闭包 1. 如果一个函数定义在另一个函数的作用域内,并且引用了外层函数的变量,则该函数称为闭包. def outter(): name='python' def inner(): print na ...

  6. Python函数进阶:闭包、装饰器、生成器、协程

    返回目录 本篇索引 (1)闭包 (2)装饰器 (3)生成器 (4)协程 (1)闭包 闭包(closure)是很多现代编程语言都有的特点,像C++.Java.JavaScript等都实现或部分实现了闭包 ...

  7. python 内嵌函数, 闭包, 函数装饰器

    一.  函数内嵌 闭包 在python中,函数可以作为返回值, 可以给变量赋值. 在python中, 内置函数必须被显示的调用, 否则不会执行. #!/usr/bin/env python #-*- ...

  8. python函数知识七 闭包、装饰器一(入门)、装饰器二(进阶)

    21.闭包 闭包:在嵌套函数内,使用非全局变量(且不使用本层变量) 闭包的作用:1.保证数据的安全性(纯洁度).2.装饰器使用 .__closure__判断是否是闭包 def func(): a = ...

  9. guxh的python笔记三:装饰器

    1,函数作用域 这种情况可以顺利执行: total = 0 def run(): print(total) 这种情况会报错: total = 0 def run(): print(total) tot ...

  10. Python编程四大神兽:迭代器、生成器、闭包和装饰器

    生成器 生成器是生成一个值的特殊函数,它具有这样一个特点:第一次执行该函数时,先从头按顺序执行,在碰到yield关键字时该函数会暂停执行该函数后续的代码,并且返回一个值:在下一次调用该函数执行时,程序 ...

随机推荐

  1. selenium库浅析

    selenium库浅析 基于4.3 pip install selenium安装好后,在sitepackages下 2个主要的目录,common和webdriver 1- common 该目录一共就一 ...

  2. 归并排序 nO(lgn) 审核中

    大家好,我是蓝胖子,我一直相信编程是一门实践性的技术,其中算法也不例外,初学者可能往往对它可望而不可及,觉得很难,学了又忘,忘其实是由于没有真正搞懂算法的应用场景,所以我准备出一个系列,囊括我们在日常 ...

  3. 如何在 Ubuntu上使用snap安装Docker

    1 检查系统版本 具有sudo或root用户权限 2 安装 SNAP ctrl+alt+T 打开终端 运行以下命令以安装 SNAP sudo apt update sudo apt install s ...

  4. P8815 [CSP-J 2022] 逻辑表达式

    Problem 考察算法:后缀表达式计算.建表达式树.\(DFS\). 题目简述 给你一个中缀表达式,其中只有 \(\&\) 和 \(\mid\) 两种运算. 求:\(\&\) 和 \ ...

  5. 搞懂Event Loop

    本文关键: V8是单线程的 任务队列排队执行 抽出io命令抽出到evenloop线程,消息线程,区别与主线程.(同步和异步) 微任务和宏任务执行顺序 重绘和回流 以上流程无限循环 可以这样理解,一个人 ...

  6. JVM-JAVA基本类型

    1 package javap.fload; 2 3 import static jdk.nashorn.internal.objects.Global.Infinity; 4 5 public cl ...

  7. picgo+GitHub搭建图床

    picgo+GitHub 搭建图床 目录 picgo+GitHub 搭建图床 图床的概念 使用 GitHub 创建图床服务器 在 GitHub 上面新建仓库 生成 token 令牌 创建 img 分支 ...

  8. BFS广搜小谈

    个人认为BFS比DFS难度要大一些,所以来这里做个笔记. 比较可怜的是本蒟蒻并没有找到BFS这个东西解题有什么规律,所以我只能粘上3个代码. 模板 当然一个差不多点儿的模板还是要有的. //模板1 # ...

  9. 如何实现一套简单的oauth2授权码类型认证,一些思路,供参考

    背景 组内人不少,今年陆陆续续研发了不少系统,一般都会包括一个后台管理系统,现在问题是,每个管理系统都有RBAC那一套用户权限体系,实在是有点浪费人力,于是今年我们搞了个统一管理各个应用系统的RBAC ...

  10. 超详细的Mysql锁 实战分析,你想知道的都在这里~

    1.mysql回表查询 在这里提起主要是用于说明mysql数据和索引的结构,有助于理解后续加锁过程中的一些问题. mysql索引结构和表数据结构是相互独立的,根据索引查询,只能找到索引列和主键聚簇索引 ...