本文目录:

1. 闭包的解析和用法

2. 函数式装饰器

3. 类装饰器

一、闭包

闭包是一种函数,从形式上来说是函数内部定义(嵌套)函数,实现函数的扩展。在开发过程中,考虑到兼容性和耦合度问题,如果想在原有的函数基础上添加东西而又不改动原有函数的结构,通常会使用闭包。但闭包的功能还不只是这个。实际上,闭包会保留定义函数时存在的自由变量的绑定,这样在调用函数时,虽然定义作用域不可用了,但是仍然可以使用那些绑定的变量。简单来说,普通函数在调用完后,函数内部的变量就释放了(因为直接调用的函数没有绑定在某一个对象上,Cpython解析器就会把它回收了),而闭包内部的变量仍然保存着。

普通函数不会保存变量的值:

例如:
def function(value):
nums = []
nums.append(value)
return nums func = function(2)
func2 = function(3)
print(func) # [2]
print(func2) # [3]
两次调用函数返回的值都是不相同的

在闭包中,外部函数的变量一直会为内部函数“保留”着,每次调用函数都可以获取这些变量

def closure():
nums =[]
def function(value):
nums.append(value)
return nums
return function close = closure()
close(5)
close(6)
close(7)
# 元组形式返回嵌套函数function的变量
print(close.__code__.co_varnames) #('value',)
print(close.__code__.co_freevars) #('nums',)
# 列表形式保存嵌套函数中自由变量的值
print(close.__closure__[0].cell_contents) #[5, 6, 7]

闭包的执行顺序可以理解为:

closure(function(value))

因此close = closure()相当于为闭包创建了一个绑定的对象,这个对象内部有变量nums和函数function。当变量在下一次调用的时候,这些量还会保存着。因此当多次调用close()的时候,nums列表会更新。

以上代码的解析:

对象审查(反射)

_ _code_ _返回对象中的函数

_ _co_varnames 返回内部函数中保存的变量,例如function中的value

_ _co_freenames 返回内部函数中的自由变量,自由变量是编程中的一个专业名词。

如上面的代码中,nums并不是在function函数中绑定的,它是在它的外部函数的作用域范围内绑定的,所以在function内部,nums是一个自由变量。而close对象为function函数保留了这个自由变量,在每次调用函数时,都可以更新这个自由变量。

自由变量(所有的)实际保存在闭包中,可以通过_ _closure_ _来获取,它是一个列表,每个元素都表示一个自由变量,如上面的nums。每个元素都是一个cell,它的属性cell_contents保存着这个自由变量的值,因此有:

_ _closure_ _[0].cell_contents

更多关于函数/类/生成器的审查可以参考官方文档:

https://docs.python.org/2/library/inspect.html

二、函数形式的装饰器

上面讲了如何通过嵌套函数实现一个闭包,下面将装饰器是如何实现的。实际上,装饰器离不开对闭包的理解,函数形式的装饰器看起来像是闭包换了一种表达形式,调用起来更灵活和更方便。

例如:

# 函数形式的闭包
registry = [] def decorator(func):
print('registe %s'%func)
registry.append(func)
return func # 返回的量必须是一个函数,否则会报错 @decorator
def fun1():
print('running fun1') @decorator
def fun2():
print('running fun2') @decorator
def fun3():
print('running fun3') fun1()
fun2()
fun3()

结果:

registe <function fun1 at 0x000002234D891378>
registe <function fun2 at 0x000002234D891400>
registe <function fun3 at 0x000002234D891488>
running fun1
running fun2
running fun3

装饰器看起来有点像闭包,只不过是加了一个@的外壳,而这个外壳函数的参数必须是一个函数,并且必须要有返回函数(返回的一般是内部函数)

装饰器的执行顺序:

decorator(func)

内部函数的参数可以在函数调用时传入,而不必像闭包那样必须由对象传入。

值得注意的是:装饰器函数有导入时运行和运行时运行的区别,装饰器在模块导入的时候就执行了,而被装饰的函数则在调用的时候才执行。

上面这个被装饰器“装饰”的函数似乎看起来跟装饰器“互动”很少,那么下面结合装饰器和闭包实现一个更复杂的装饰器:

def decorator(func):
def outerFunc(*args): # 装饰器内部实现闭包,闭包的外部函数接受任意定位变量
outerFunName = outerFunc.__name__
innerFunName = func.__name__
print('change innerFunc:%s to outerFunc:%s'%(innerFunName, outerFunName))
result = func(*args) # 可以实现,因为闭包中保存了自由变量func
result += " and start running"
return result
return outerFunc # 将改变返回的函数,返回外部函数 @decorator
def fun(str):
return str str = fun('This is funciton1') # change innerFunc:fun to outerFunc:outerFunc
print(str) # This is funciton1 and start running

装饰器执行顺序:

decorator(outerFunc(func(args)))

这个装饰器内部的闭包实现还是比较简单的,只是为了说明原理,在编程过程中可以根据实际添加更多的功能实现。

继续改造,让装饰器也带上参数

# 带参数装饰器
def decorator(name):
def _decorator(func):
def outerFunc(*args):
print(name)
outerFunName = outerFunc.__name__
innerFunName = func.__name__
print('change innerFunc:%s to outerFunc:%s'%(innerFunName, outerFunName))
result = func(*args)
result += " and start running"
return result
return outerFunc
return _decorator @decorator(name='@Author:Tom')
def fun(str):
return str str = fun('This is funciton1')
print(str) 结果:
@Author:Tom
change innerFunc:fun to outerFunc:outerFunc
This is funciton1 and start running

三、类形式的装饰器

讲完了函数形式的装饰器,那么接下来讲讲类形式的装饰器

一般类的定义如下:

class Test(object):
def __init__(self,name):
self._m = 0
self._n = 0
self.name = name def test_print(self):
print(self.name)

而如果想要将一个类变成一个装饰器,那么就需要一个很关键的魔法方法_ _call_ _(),它的作用是将一个类实例变成可调用的,改造一下上面的类:

class Test(object):
def __init__(self):
self.count = 0 def __call__(self):
# print(self.count)
self.count += 1 # 每一次调用这个类实例都记录一次
return self.count # __call__函数将类实例变成可调用形式,而实际上还会有一个返回量(变量/函数),因此需要写return,否则返回为None
test = Test()
# 以函数调用的形式直接调用类实例
print(test()) #
print(test()) #

这样看起来,类形式的装饰器有点像函数形式的装饰器,它也保存了一些变量。实际上这点不足为奇,因为,本来类实例的变量已经绑定在类实例对象中。

还可以这样用:

class Average:
def __init__(self):
self.values = [] # 每次调用average实例都会更新self.values
def __call__(self, newvalue):
self.values.append(newvalue)
total = sum(self.values)
average = total / len(self.values)
return average average = Average()
print(average(6))
print(average(7))
print(average(8)) 结果:
6.0
6.5
7.0

类装饰器:

class Decorator:
def __init__(self, add=1): # 定义可以传入的参数
self.count = 0
self.add = add def __call__(self, fun):
self.fun = fun
return self._call_func def _call_func(self):
self.count += self.add
return self.fun(self.count) # 相当于Decorate(count)
@Decorator(add=2) # 改变传入的参数值
def count(cnt):
print(cnt) count() #
count() #

笔者认为类形式的装饰器会比函数形式的装饰器更加灵活和方便,因为它的内部实现可以更灵活,看起来也比较符合日常使用的习惯,因为函数式的装饰器看起来总有一点怪怪的(笔者本人看法而已)。实际使用中就要根据业务需求来选择了

参考文章:

1. 《流畅的python》

2.  https://docs.python.org/2/library/inspect.html

python闭包和装饰器的更多相关文章

  1. python 闭包和装饰器

    python 闭包和装饰器 一.闭包闭包:外部函数FunOut()里面包含一个内部函数FunIn(),并且外部函数返回内部函数的对象FunIn,内部函数存在对外部函数的变量的引用.那么这个内部函数Fu ...

  2. python闭包与装饰器

    转自小马哥: 闭包和装饰器充分体现了Python语法糖的优雅感觉. 在本文中,我们的实验要完成两个工作,一个是加法,一个是累计调用加法的次数,最普通的Python程序可以这么写: def valida ...

  3. 高逼格利器之Python闭包与装饰器

    生活在魔都的小明,终于攒够了首付,在魔都郊区买了一套房子:有一天,小明踩了狗屎,中了一注彩票,得到了20w,小明很是欢喜,于是想干脆用这20万来装修房子吧(decoration): 整个装修过程,小明 ...

  4. Python—闭包和装饰器

    闭包 定义:内部函数对外部函数变量的引用,则将该函数与用到的变量称为闭包. 闭包必须满足以下三个条件: 必须有一个内嵌函数. 内嵌函数必须引用外部函数中的变量. 外部函数返回值必须是内嵌函数的引用. ...

  5. Python 简明教程 --- 22,Python 闭包与装饰器

    微信公众号:码农充电站pro 个人主页:https://codeshellme.github.io 当你选择了一种语言,意味着你还选择了一组技术.一个社区. 目录 本节我们来介绍闭包与装饰器. 闭包与 ...

  6. Python闭包及装饰器

    Python闭包 先看一个例子: def outer(x): def inner(y): return x+y return innder add = outer(8) print add(6) 我们 ...

  7. python闭包以及装饰器

    通俗的定义:如果在一个内部函数里,对在外部作用域(但不是在全局作用域)的变量进行引用,那么内部函数就被认为是闭包(closure).它只不过是个“内层”的函数,由一个名字(变量)来指代,而这个名字(变 ...

  8. python闭包和装饰器(转)

    一.python闭包 1.内嵌函数 >>> def func1(): ... print ('func1 running...') ... def func2(): ... prin ...

  9. 详解Python闭包,装饰器及类装饰器

    在项目开发中,总会遇到在原代码的基础上添加额外的功能模块,原有的代码也许是很久以前所写,为了添加新功能的代码块,您一般还得重新熟悉源代码,稍微搞清楚一点它的逻辑,这无疑是一件特别头疼的事情.今天我们介 ...

随机推荐

  1. FFmpeg命令行工具学习(一):查看媒体文件头信息工具ffprobe

    一.简述 ffprobe是ffmpeg命令行工具中相对简单的,此命令是用来查看媒体文件格式的工具. 二.命令格式 在命令行中输入如下格式的命令: ffprobe [文件名] 三.使用ffprobe查看 ...

  2. [Java]LeetCode133. 克隆图 | Clone Graph

    Clone an undirected graph. Each node in the graph contains a label and a list of its neighbors. OJ's ...

  3. [Swift]LeetCode594. 最长和谐子序列 | Longest Harmonious Subsequence

    We define a harmonious array is an array where the difference between its maximum value and its mini ...

  4. PHP算法之冒泡排序

    //冒泡排序 //①思路,先比较出第一次,找一个最大的值,排到最后; //②重复count遍之后,就能得到排序; //③优化,每一次循环之后不需要再次全部重复; $array = [11,5,4,58 ...

  5. layui.table图片显示不全和404问题

    1.图片显示不全 在使用layui.table组件中,加载的图片显示不全,需重新定义CSS如下: .layui-table-cell{ height: auto!important; white-sp ...

  6. 机器学习入门16 - 多类别神经网络 (Multi-Class Neural Networks)

    原文链接:https://developers.google.com/machine-learning/crash-course/multi-class-neural-networks/ 多类别分类, ...

  7. 【jpa】spring data jpa 配置使用

    1.spring data jpa 简单介绍 jpa是用于对象持久化的API,jpa是一种规范,而其他的ORM框架(hibernate,topLink等)是其实现,所以jpa可以使用不同的实现方式,修 ...

  8. 【Spark篇】---Spark中控制算子

    一.前述 Spark中控制算子也是懒执行的,需要Action算子触发才能执行,主要是为了对数据进行缓存. 控制算子有三种,cache,persist,checkpoint,以上算子都可以将RDD持久化 ...

  9. 1K star+ 的项目是如何炼成的?

    前言 首先标题党一下,其实这篇文章主要是记录我的第二个过 1K star 的项目 Java-Interview,顺便分享下其中的过程及经验. 需求选择 Java-Interview 之所以要做这个项目 ...

  10. Unity GC 优化要点

    参考:http://blog.csdn.net/znybn1/article/details/76464896 为啥要点?因为讲的重点. 游戏运行时来存储数据,当这些数据不再被使用时,存储这些数据的内 ...