微信公众号:码农充电站pro

个人主页:https://codeshellme.github.io

当你选择了一种语言,意味着你还选择了一组技术、一个社区。

目录

本节我们来介绍闭包与装饰器。

闭包与装饰器是函数的高级用法,其实在介绍完Python 函数我们就可以介绍本节的内容,但由于Python中的类也可以用来实现装饰器,所以我们等到介绍完了Python 类再来统一介绍闭包与装饰器。

装饰器使用的是闭包的特性,我们先来介绍闭包,再来介绍装饰器。

1,什么是闭包

Python 的函数内部还允许嵌套函数,也就是一个函数中还定义了另一个函数。如下:

def fun_1():

    def fun_2():
return 'hello' s = fun_2() return s s = fun_1()
print(s) # 'hello'

在上面的代码中,我们在函数fun_1 的内部又定义了一个函数fun_2,这就是函数嵌套。

我们在学习函数的时候,还知道,Python 函数可以作为函数参数和函数返回值。

因此,我们可以将上面代码中的函数fun_2作为函数fun_1的返回值,如下:

def fun_1():
def fun_2():
return 'hello' return fun_2

此时,函数fun_1 返回了一个函数,我们这样使用fun_1:

fun = fun_1()    # fun 是一个函数
s = fun() # 调用函数 fun
print(s) # s 就是 'hello'

我们再来改进函数fun_1,如下:

def fun_1(s):
s1 = 'hello ' + s def fun_2():
return s1 return fun_2

上面的代码中,内部函数fun_2返回了变量s1,而s1是函数fun_2的外部变量,这种内部函数能够使用外部变量,并且内部函数作为外部函数的返回值,就是闭包。

编写闭包时都有一定的套路,也就是,闭包需要有一个外部函数包含一个内部函数,并且外部函数的返回值是内部函数。

2,用闭包实现一个计数器

我们来实现一个计数器的功能,先写一个框架,如下:

def counter():

    # 定义内部函数
def add_one():
pass # 返回内部函数
return add_one

再来实现计数的功能,如下:

def counter():
# 用于计数
l = [0] # 定义内部函数
def add_one():
l[0] += 1
return l[0] # 返回数字 # 返回内部函数
return add_one

上面的代码中,我们使用了一个列表l[0]来记录累加数,在内部函数add_one中对l[0]进行累加。

我们这样使用这个计数器:

c = counter()

print(c())   # 1
print(c()) # 2
print(c()) # 3

我们还可以使这个计数器能够设置累加的初始值,就是为counter 函数设置一个参数,如下:

def counter(start):
l = [start] def add_one():
l[0] += 1
return l[0] return add_one

这样我们就可以使用counter 来生成不同的累加器(从不同的初始值开始累加)。我们这样使用该计数器:

c1 = counter(1)   # c1 从 1 开始累加
print(c1()) # 2
print(c1()) # 3
print(c1()) # 4 c5 = counter(5) # c5 从 5 开始累加
print(c5()) # 6
print(c5()) # 7
print(c5()) # 8

c1 从 1 开始累加,c5 从 5 开始累加,两个互不干扰。

3,什么是装饰器

装饰器是闭包的一种进阶应用。装饰器从字面上理解就是用来装饰,包装的。装饰器一般用来在不修改函数内部代码的情况下,为一个函数添加额外的新功能。

装饰器虽然功能强大,但也不是万能的,它也有自己适用场景:

  • 缓存
  • 身份认证
  • 记录函数运行时间
  • 输入的合理性判断

比如,我们有一个函数,如下:

def hello():
print('hello world.')

如果我们想计算这个函数的运行时间,最直接的想法就是修改该函数,如下:

import time

def hello():
s = time.time() print('hello world.') e = time.time()
print('fun:%s time used:%s' % (hello.__name__. e - s)) # 调用函数
hello()

其中,time 模块是Python 中的内置模块,用于时间相关计算。

每个函数都有一个__name__ 属性,其值为函数的名字。不管我们是直接查看一个函数的__name__ 属性,还是将一个函数赋值给一个变量后,再查看这个变量的__name__ 属性,它们的值都是一样的(都是原来函数的名字):

print(hello.__name__)  # hello
f = hello # 调用 f() 和 hello() 的效果是一样的
print(f.__name__) # hello

但是,如果我们要为很多的函数添加这样的功能,要是都使用这种办法,那会相当的麻烦,这时候使用装饰器就非常的合适。

最简单的装饰器

装饰器应用的就是闭包的特性,所以编写装饰器的套路与闭包是一样的,就是有一个外部函数和一个内部函数,外部函数的返回值是内部函数。

我们先编写一个框架:

def timer(func):
def wrapper():
pass return wrapper

再来实现计时功能:

import time

def timer(func):
def wrapper():
s = time.time()
ret = func()
e = time.time()
print('fun:%s time used:%s' % (func.__name__, e - s)) return ret return wrapper def hello():
print('hello world.')

该装饰器的名字是timer,其接受一个函数类型的参数func,func 就是要修饰的函数。

func 的函数原型要与内部函数wrapper 的原型一致(这是固定的写法),即函数参数相同,函数返回值也相同。

英文 wrapper 就是装饰的意思。

其实timer 就是一个高阶函数,其参数是一个函数类型,返回值也是一个函数。我们可以这样使用timer 装饰器:

hello = timer(hello)
hello()

以上代码中,hello 函数作为参数传递给了timer 装饰器,返回结果用hello 变量接收,最后调用hello()。这就是装饰器的原本用法。

只不过,Python 提供了一种语法糖,使得装饰器的使用方法更加简单优雅。如下:

@timer
def hello():
print('hello world.') hello()

直接在原函数hello 的上方写一个语法糖@timer,其实这个作用就相当于hello = timer(hello)。

用类实现装饰器

在上面的代码中,是用函数(也就是timer 函数)来实现的装饰器,我们也可以用类来实现装饰器。

用类实现装饰器,主要依赖的是__init__ 方法和__call__ 方法。

我们知道,实现__call__ 方法的类,其对象可以像函数一样被调用。

用类来实现timer 装饰器,如下:

import time

class timer:
def __init__(self, func):
self.func = func def __call__(self):
s = time.time()
ret = self.func()
e = time.time()
print('fun:%s time used:%s' % (self.func.__name__, e - s)) return ret @timer
def hello():
print('hello world.') print(hello())

其中,构造方法__init__接收一个函数类型的参数func,然后,__call__方法就相当于wrapper 函数。

用类实现的装饰器的使用方法,与用函数实现的装饰器的使用方法是一样的。

4,被修饰的函数带有参数

如果hello 函数带有参数,如下:

def hello(s):
print('hello %s.' % s)

那么装饰器应该像下面这样:

import time

def timer(func):
def wrapper(args):
s = time.time() ret = func(args) e = time.time()
print('fun:%s time used:%s' % (func.__name__, e - s)) return ret return wrapper @timer
def hello(s):
print('hello %s.' % s) hello('python')

timer 函数的参数依然是要被修饰的函数,wrapper 函数的原型与hello 函数保持一致。

用类来实现,如下:

import time

class timer:
def __init__(self, func):
self.func = func def __call__(self, args):
s = time.time()
ret = self.func(args)
e = time.time()
print('fun:%s time used:%s' % (self.func.__name__, e - s)) return ret @timer
def hello(s):
print('hello %s.' % s) print(hello('python'))

不定长参数装饰器

如果hello 函数的参数是不定长的,timer 应该是如下这样:

import time

def timer(func):
def wrapper(*args, **kw):
s = time.time() ret = func(*args, **kw) e = time.time()
print('fun:%s time used:%s' % (func.__name__, e - s)) return ret return wrapper @timer
def hello(s1, s2): # 带有两个参数
print('hello %s %s.' % (s1, s2)) @timer
def hello_java(): # 没有参数
print('hello java.') hello('python2', 'python3')
hello_java()

这样的装饰器timer,可以修饰带有任意参数的函数。

用类来实现,如下:

import time

class timer:
def __init__(self, func):
self.func = func def __call__(self, *args, **kw):
s = time.time()
ret = self.func(*args, **kw)
e = time.time()
print('fun:%s time used:%s' % (self.func.__name__, e - s)) return ret @timer
def hello(s1, s2): # 带有两个参数
print('hello %s %s.' % (s1, s2)) @timer
def hello_java(): # 没有参数
print('hello java.') hello('python2', 'python3')
hello_java()

5,装饰器带有参数

如果装饰器也需要带有参数,那么则需要在原来的timer 函数的外层再嵌套一层函数Timer,Timer 也带有参数,如下:

import time

def Timer(flag):
def timer(func):
def wrapper(*args, **kw):
s = time.time()
ret = func(*args, **kw)
e = time.time()
print('flag:%s fun:%s time used:%s' % (flag, func.__name__, e - s)) return ret return wrapper return timer @Timer(1)
def hello(s1, s2): # 带有两个参数
print('hello %s %s.' % (s1, s2)) @Timer(2)
def hello_java(): # 没有参数
print('hello java.') hello('python2', 'python3')
hello_java()

从上面的代码中可以看到,timer 的结构没有改变,只是在wrapper 的内部使用了flag 变量,然后timer 的外层多了一层Timer,Timer 的返回值是timer,我们最终使用的装饰器是Timer。

我们通过函数.__name__ 来查看函数的__name__ 值:

print(hello.__name__)       # wrapper
print(hello_java.__name__) # wrapper

可以发现hello 和 hello_java 的__name__ 值都是wrapper(即内部函数wrapper 的名字),而不是hello 和 hello_java,这并不符合我们的需要,因为我们的初衷只是想增加hello 与hello_java的功能,但并不想改变它们的函数名字。

6,使用 @functools.wraps

我们可以使用functools模块的wraps装饰器来修饰wrapper 函数,以解决这个问题,如下:

import time
import functools def Timer(flag):
def timer(func):
@functools.wraps(func)
def wrapper(*args, **kw):
s = time.time()
ret = func(*args, **kw)
e = time.time()
print('flag:%s fun:%s time used:%s' % (flag, func.__name__, e - s)) return ret return wrapper return timer @Timer(1)
def hello(s1, s2): # 带有两个参数
print('hello %s %s.' % (s1, s2)) @Timer(2)
def hello_java(): # 没有参数
print('hello java.')

此时,再查看hello 与 hello_java 的 __name__值,分别是hello 和 hello_java。

7,装饰器可以叠加使用

装饰器也可以叠加使用,如下:

@decorator1
@decorator2
@decorator3
def func():
...

上面代码的所用相当于:

decorator1(decorator2(decorator3(func)))

8,一个较通用的装饰器模板

编写装饰器有一定的套路,根据上文的介绍,我们可以归纳出一个较通用的装饰器模板:

def func_name(func_args):

    def decorator(func):

        @functools.wraps(func)
def wrapper(*args, **kw):
# 在这里可以使用func_args,*args,**kw
# 逻辑处理
...
ret = func(*args, **kw)
# 逻辑处理
... return ret return wrapper return decorator # 使用装饰器 func_name
@func_name(func_args)
def func_a(*args, **kw):
pass

在上面的模板中:

  • func_name 是装饰器的名字,该装饰器可以接收参数 func_args
  • 内部函数 decorator 的参数 func,是一个函数类型的参数,就是将来要修饰的函数
  • func 的参数列表可以是任意的,因为我们使用的是*args, **kw
  • 内部函数wrapper 的原型(即参数与返回值)要与 被修饰的函数func 保持统一
  • @functools.wraps 的作用是保留被装饰的原函数的一些元信息(比如__name__ 属性)

与装饰器相关的模块有functools 和 wrapt,可以使用这两个模块来优化完善你写的装饰器,感兴趣的小伙伴可以自己拓展学习。

(完。)


推荐阅读:

Python 简明教程 --- 17,Python 模块与包

Python 简明教程 --- 18,Python 面向对象

Python 简明教程 --- 19,Python 类与对象

Python 简明教程 --- 20,Python 类中的属性与方法

Python 简明教程 --- 21,Python 继承与多态


欢迎关注作者公众号,获取更多技术干货。

Python 简明教程 --- 22,Python 闭包与装饰器的更多相关文章

  1. Python的高级特性7:闭包和装饰器

    本节跟第三节关系密切,最好放在一起来看:python的高级特性3:神奇的__call__与返回函数 一.闭包:闭包不好解释,只能先看下面这个例子: In [23]: def outer(part1): ...

  2. 13、python中的函数(闭包与装饰器)

    一.嵌套函数 函数的内部又再定义另一个函数,这个函数就叫嵌套函数,里面含函数就叫内部函数. 示例: 二.返回函数 函数可以接收函数对象作为参数,同理函数也能返回一个函数对象作为返回值. 示例: 返回函 ...

  3. python 生成器,迭代器,闭包,装饰器

    1.生成器,迭代器,闭包,装饰器的优点 生成器就是一类特殊的迭代器 迭代器的优点也即生成器的优点: 1.节约内存.python在使用生成器时对延迟操作提供了支持. 2.迭代到下一次的调用时,所使用的参 ...

  4. Python虚拟机函数机制之闭包和装饰器(七)

    函数中局部变量的访问 在完成了对函数参数的剖析后,我们再来看看,在Python中,函数的局部变量时如何实现的.前面提到过,函数参数也是一种局部变量.所以,其实局部变量的实现机制与函数参数的实现机制是完 ...

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

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

  6. Python基础之函数的闭包与装饰器的介绍

    1.闭包的概念: 如果在一个函数中,定义了另外一个函数,并且那个函数使用了外面函数的变量,并且外面那个函数返回了里面这个函数的引用,那么称为里面的这个函数为闭包. 2.话不多说,以demo示例: de ...

  7. Python 简明教程 --- 23,Python 异常处理

    微信公众号:码农充电站pro 个人主页:https://codeshellme.github.io 要么做第一个,要么做最好的一个. 目录 我们在编写程序时,总会不自觉的出现一些错误,比如逻辑错误,语 ...

  8. Python 简明教程 --- 24,Python 文件读写

    微信公众号:码农充电站pro 个人主页:https://codeshellme.github.io 过去的代码都是未经测试的代码. 目录 无论是哪种编程语言,IO 操作都是非常重要的部分.I 即Inp ...

  9. Python 简明教程 --- 25,Python 目录操作

    微信公众号:码农充电站pro 个人主页:https://codeshellme.github.io 做技术一定要一颗恒心,这样才不会半途而废. 目录 上一节我们介绍了文件相关的操作,本节我们来介绍目录 ...

随机推荐

  1. Java实现 基础算法 求100以内的质数

    public class 求质数 { public static void main(String[] args) { for (int i = 2; i < 100; i++) { int t ...

  2. Java动态规划实现最短路径问题

    问题描述 给定一个加权连通图(无向的或有向的),要求找出从每个定点到其他所有定点之间的最短路径以及最短路径的长度. 2.1 动态规划法原理简介 动态规划算法通常用于求解具有某种最优性质的问题.在这类问 ...

  3. java实现第六届蓝桥杯垒骰子

    垒骰子 题目描述 赌圣atm晚年迷恋上了垒骰子,就是把骰子一个垒在另一个上边,不能歪歪扭扭,要垒成方柱体. 经过长期观察,atm 发现了稳定骰子的奥秘:有些数字的面贴着会互相排斥! 我们先来规范一下骰 ...

  4. python3 基本书写规范

    一.缩进在类.函数定义完成后需要接着写子代码快需要在定义完成后加上: 缩进格式为首字母开始空格4个位置(取消了大括号以冒号代替子模块)例: class pop: #类的定义方式 def pip: #函 ...

  5. LNMP zabbix 4.4

    硬件配置需求 环境 平台 CPU/内存 数据库 硬盘 监控主机数 小型 CentOS 2CPU/1GB MySQL.InnoDB 普通 100 中型 CentOS 2CPU/2GB MySQL.Inn ...

  6. Spring AOP学习笔记02:如何开启AOP

    上文简要总结了一些AOP的基本概念,并在此基础上叙述了Spring AOP的基本原理,并且辅以一个简单例子帮助理解.从本文开始,我们要开始深入到源码层面来一探Spring AOP魔法的原理了. 要使用 ...

  7. html中隐藏域hidden

    基本语法: <input type="hidden" name="field_name" value="value"> 作用: ...

  8. ROS 机器人技术 - 解决 ROS_INFO 不能正确输出 string 的问题!

    一.输出「??」 项目调试一个节点,打印 ROS 信息时发现设置的节点名称都是问号: ROS_INFO("[%s]: camera_extrinsic_mat", kNodeNam ...

  9. 使用jdk1.8 stream特性对参数名称进行排序

    在对外对接的时候,通常会碰到签名方式, 然后签名的时候,要求按照参数名称进行排序. 比如参数为 c=22&a=1, 需要将结果排序为a=1&c=22, 然后再进行别的运算. 可以使用j ...

  10. Docker+Selenium+TestNG+Maven+Jenkins环境搭建

    一.Selenium环境准备 standalone-chrome Docker容器启动: docker pull selenium/standalone-chrome version: '3' ser ...