1. 闭包

首先我们明确一下函数的引用,如下所示:

def test1():
print("--- in test1 func----") # 调用函数
test1() # 引用函数
ret = test1 print(id(ret))
print(id(test1)) #通过引用调用函数
ret()

运行结果:

--- in test1 func----
140212571149040
140212571149040
--- in test1 func----

以y=kx+b为例,请计算一条线上的某个点,即给x值计算出y值。下面以这个例子引出闭包的概念。

方法1

# 第1种
k = 1
b = 2
y = k*x+b
# 缺点:如果需要多次计算,那么就的写多次y = k*x+b这样的式子

方法2

# 第2种
def line_2(k, b, x):
print(k*x+b) line_2(1, 2, 0)
line_2(1, 2, 1)
line_2(1, 2, 2)
# 缺点:如果想要计算多次这条线上的y值,那么每次都需要传递k,b的值,麻烦

方法3

# 第3种: 全局变量
k = 1
b = 2
def line_3(x):
print(k*x+b) line_3(0)
line_3(1)
line_3(2)
k = 11
b = 22
line_3(0)
line_3(1)
line_3(2)
# 缺点:如果要计算多条线上的y值,那么需要每次对全局变量进行修改,代码会增多,麻烦

这里引申一下,关于全局变量,要是直接读取,不修改的话,是不用加global的。

而且所谓的修改,指的是地址变了,假如我指向的是一个列表,指向没变,列表中的内容改变了,也不用加global的。

我们看一下下面的例子:

a = 100
b = 'chenchi'
c = [1, 2, 3] def func():
print('a的值:{},a的地址:{}'.format(a, id(a)))
print('b的值:{},b的地址:{}'.format(b, id(b)))
print('c的值:{},c的地址:{}'.format(c, id(c))) def func2():
global a
global b
a += 1
b += 'ccc'
# 这里c不用声明global,c的地址没有发生变化
c.append(4) if __name__ == '__main__':
func()
func2()
func()

运行结果:

列表的地址没变,不用加global。

方法4

# 第4种:缺省参数
def line_4(x, k=1, b=2):
print(k*x+b) line_4(0)
line_4(1)
line_4(2) line_4(0, k=11, b=22)
line_4(1, k=11, b=22)
line_4(2, k=11, b=22)
# 优点:比全局变量的方式好在:k, b是函数line_4的一部分 而不是全局变量,因为全局变量可以任意的被其他函数所修改
# 缺点:如果要计算多条线上的y值,那么需要在调用的时候进行传递参数,麻烦

方法5

# 第5种:实例对象
class Line5(object):
def __init__(self, k, b):
self.k = k
self.b = b def __call__(self, x):
print(self.k * x + self.b) line_5_1 = Line5(1, 2)
# 对象.方法()
# 对象()
line_5_1(0)
line_5_1(1)
line_5_1(2)
line_5_2 = Line5(11, 22)
line_5_2(0)
line_5_2(1)
line_5_2(2)
# 缺点:为了计算多条线上的y值,所以需要保存多个k, b的值,因此用了很多个实例对象, 浪费资源

关于__call__()的用法:

方法6

采用闭包的方式,什么是闭包呢?

在函数内部再定义一个函数,并且这个函数用到了外边函数的变量,那么将这个函数以及用到的一些变量称之为闭包。

如下所示:

# 第6种:闭包
def line_6(k, b):
def create_y(x):
print(k*x+b)
return create_y line_6_1 = line_6(1, 2)
line_6_1(0)
line_6_1(1)
line_6_1(2)
line_6_2 = line_6(11, 22)
line_6_2(0)
line_6_2(1)
line_6_2(2)

闭包,红色部分return的不能加(),不加()是函数引用,否则就应该是函数的返回值。

在上面的例子中,函数line_6与变量k,b构成闭包,闭包具有提高代码可复用性的作用。

由于闭包引用了外部函数的局部变量,则外部函数的局部变量没有及时释放,消耗内存,但是相对于实例对象来说,已经好多了。

思考:函数、匿名函数、闭包、对象 当做实参时 有什么区别?
1. 匿名函数能够完成基本的简单功能,传递是这个函数的引用 只有功能。
2. 普通函数能够完成较为复杂的功能,传递是这个函数的引用 只有功能。
3. 闭包能够将较为复杂的功能,传递是这个闭包中的函数以及数据,因此传递是功能+数据。
4. 对象能够完成最为复杂的功能,传递是很多数据+很多功能,因此传递是功能+数据。

修改外部函数中的变量

x = 300
def outer():
x = 200
def inner():
nonlocal x
# global x
print('---1---x=%d' % x)
x = 100
print('---1---x=%d' % x)
return inner t1 = outer()
t1()

注意,这是python3,输出结果如下:

如果把nonlocal改成global,第一个x就变成300了。

2. 装饰器

在聊Python中的装饰器之前,我们明确一个概念:Python中的装饰器就是实现了设计模式中的装饰器模式,只不过不像Java用的实现接口的方式,而是采用闭包的方式。

装饰器模式可以参考我的博客:

https://www.cnblogs.com/DarrenChan/p/10343885.html

我们从一个简单的例子入手:

def set_func(func):
def call_func():
print("---这是权限验证1----")
print("---这是权限验证2----")
func()
return call_func # 下面就相当于test1 = set_func(test1)
@set_func
def test1():
print("-----test1----") test1()

运行结果:

这里就相当于是实现了一个代理,我们重新执行的test1()实际上是代理。

我们所营造出来的一种效果就是,test1()加不加@set_func,方法执行的方式是不变的。

@set_func的含义是:

set_func(test1)

我们可以用任何一个方式来接收,比如:

ret = set_func(test1)

ret()

这样执行结果没有变化,但是感觉不协调,所以我们用test1来接收,即

test1 = set_func(test1)

test1()

就是把test1重新指向了call_func()函数。

我们可以看到,set_func函数里面就是闭包。

一个装饰器可以对多个函数进行装饰

def set_func(func):
def call_func(a):
print("---这是权限验证1----")
print("---这是权限验证2----")
func(a)
return call_func @set_func # 相当于 test1 = set_func(test1)
def test1(num):
print("-----test1----%d" % num) @set_func # 相当于 test2 = set_func(test2)
def test2(num):
print("-----test2----%d" % num) test1(100)
test2(200)

运行结果:

装饰器在没有调用函数之前已经装饰了

def set_func(func):
print("---开始进行装饰")
def call_func(a):
print("---这是权限验证1----")
print("---这是权限验证2----")
func(a)
return call_func @set_func # 相当于 test1 = set_func(test1)
def test1(num):
print("-----test1----%d" % num) @set_func # 相当于 test2 = set_func(test2)
def test2(num):
print("-----test2----%d" % num) # 装饰器在调用函数之前,已经被python解释器执行了,所以要牢记 当调用函数之前 其实已经装饰好了,尽管调用就可以了
# test1(100)
# test2(200)

运行结果:

对不定长参数的函数进行装饰

def set_func(func):
print("---开始进行装饰")
def call_func(*args, **kwargs): # 这里是形参,相当于定义,前面接收元组多个,后面接收字典多个
print("---这是权限验证1----")
print("---这是权限验证2----")
# func(args, kwargs) # 不行,相当于传递了2个参数 :1个元组,1个字典
func(*args, **kwargs) # 这里是实参,拆包,对应拆元组和拆字典
return call_func @set_func # 相当于 test1 = set_func(test1)
def test1(num, *args, **kwargs):
print("-----test1----%d" % num)
print("-----test1----" , args)
print("-----test1----" , kwargs) test1(100)
test1(100, 200)
test1(100, 200, 300, mm=100)

运行结果:

代码中的红色部分,传值的问题,可以参考:

https://www.cnblogs.com/DarrenChan/p/7931735.html

对带有返回值的函数进行装饰(装饰模板)

def set_func(func):
print("---开始进行装饰")
def call_func(*args, **kwargs):
print("---这是权限验证1----")
print("---这是权限验证2----")
# func(args, kwargs) # 不行,相当于传递了2个参数 :1个元组,1个字典
return func(*args, **kwargs) # 拆包
return call_func @set_func # 相当于 test1 = set_func(test1)
def test1(num, *args, **kwargs):
print("-----test1----%d" % num)
print("-----test1----" , args)
print("-----test1----" , kwargs)
return "ok" @set_func
def test2():
pass ret = test1(100)
print(ret) ret = test2()
print(ret)

运行结果:

你可能会问,如果被修饰的函数没有返回值,那么红色部分是否会报错?

在Python中不会的,返回None而已。

这也是我们的一个模板写法。

多个装饰器对同一个函数进行装饰

def add_qx(func):
print("---开始进行装饰权限1的功能---")
def call_func(*args, **kwargs):
print("---这是权限验证1----")
return func(*args, **kwargs)
return call_func def add_xx(func):
print("---开始进行装饰xxx的功能---")
def call_func(*args, **kwargs):
print("---这是xxx的功能----")
return func(*args, **kwargs)
return call_func @add_qx
@add_xx
def test1():
print("------test1------") test1()

运行结果:

这就跟包裹一样,红色部分越上面的越先执行。至于开始装饰为什么先执行的xx,是因为@add_qx找下面,发现不是函数,而@add_xx下面才是函数,所以开始进行装饰的顺序反过来了。

举例:

def set_func_1(func):
def call_func():
# "<h1>haha</h1>"
return "<h1>" + func() + "</h1>"
return call_func def set_func_2(func):
def call_func():
return "<td>" + func() + "</td>"
return call_func @set_func_1
@set_func_2
def get_str():
return "haha" print(get_str())

结果:

<h1><td>haha</td></h1>

使用类当做装饰器

class Test(object):
def __init__(self, func):
self.func = func def __call__(self):
print("这里是装饰器添加的功能.....")
return self.func() @Test # 相当于get_str = Test(get_str)
def get_str():
return "haha" print(get_str())

运行结果:

装饰器带参数

这种情况需要在原有的外面,再次嵌套一层,作为外部变量传参。

运行结果:

聊聊Python中的闭包和装饰器的更多相关文章

  1. python中的闭包和装饰器

    重新学习完了函数,是时候将其中的一些重点重新捋一捋了,本次总结的东西只有闭包和装饰器 1.闭包 闭包是python函数中的一个比较重要功能,一般闭包都是用在装饰器上,一般学完闭包就会去学习装饰器,这俩 ...

  2. 21.python中的闭包和装饰器

    python中的闭包从表现形式上定义(解释)为:如果在一个内部函数里,对在外部作用域(但不是在全局作用域)的变量进行引用,那么内部函数就被认为是闭包(closure). 以下说明主要针对 python ...

  3. Python 中的闭包与装饰器

    闭包(closure)是函数式编程的重要的语法结构.闭包也是一种组织代码的结构,它同样提高了代码的可重复使用性. 如果在一个内嵌函数里,对在外部函数内(但不是在全局作用域)的变量进行引用,那么内嵌函数 ...

  4. 轻松理解python中的闭包和装饰器 (下)

    在 上篇 我们讲了python将函数做为返回值和闭包的概念,下面我们继续讲解函数做参数和装饰器,这个功能相当方便实用,可以极大地简化代码,就让我们go on吧! 能接受函数做参数的函数我们称之为高阶函 ...

  5. 轻松理解python中的闭包和装饰器(上)

    继面向对象编程之后函数式编程逐渐火起来了,在python中也同样支持函数式编程,我们平时使用的map, reduce, filter等都是函数式编程的例子.在函数式编程中,函数也作为一个变量存在,对应 ...

  6. python中的闭包与装饰器

    #原创,转载请留言联系 装饰器的本质就是闭包,所以想知道装饰器是什么,首先要理解一下什么是闭包. 闭包 1. 外部函数返回内部函数的引用.2. 内部函数使用外部函数的变量或者参数. def outer ...

  7. python中函数总结之装饰器闭包

    1.前言 函数也是一个对象,从而可以增加属性,使用句点来表示属性. 如果内部函数的定义包含了在外部函数中定义的对象的引用(外部对象可以是在外部函数之外),那么内部函数被称之为闭包. 2.装饰器 装饰器 ...

  8. python基础16_闭包_装饰器

    不了解是否其他语言也有类似 python 装饰器这样的东西. 最近才发现ECMAScript6也是有生成器函数的,也有 yield  generator 装饰器的基础知识是闭包: # 闭包:嵌套函数, ...

  9. 第十七篇 Python函数之闭包与装饰器

    一. 装饰器 装饰器:可以拆解来看,器本质就是函数,装饰就是修饰的意思,所以装饰器的功能就是为其他函数添加附加功能. 装饰器的两个原则: 1. 不修改被修饰函数的源代码 2. 不修改被修饰函数的调用方 ...

随机推荐

  1. 第01章 准备工作.md

    第1章 准备工作 1.1 本书的内容 本书讲的是利用Python进行数据控制.处理.整理.分析等方面的具体细节和基本要点.我的目标是介绍Python编程和用于数据处理的库和工具环境,掌握这些,可以让你 ...

  2. vim编辑器基本操作

    命令模式: 按(i)键进入编辑模式,将在光标前面插入: 按(I)键进入编辑模式,将在光标行首插入: 按(a)进入编辑模式,在光标后面插入: 按(A)键进入编辑模式,将在光标行末插入: 按(o)进入编辑 ...

  3. GreenDao与ReactiveX的完美搭配

    转载请注明出处:http://www.cnblogs.com/cnwutianhao/p/6719380.html 作为Android开发者,一定不会对 GreenDao 和 ReactiveX 陌生 ...

  4. Orleans逐步教程

    参考文档:https://dotnet.github.io/orleans/Tutorials/index.html 一.通过模板创建Orleans ①下载vs插件:https://marketpla ...

  5. 潭州课堂25班:Ph201805201 爬虫基础 第十三课 cookie (课堂笔记)

    # -*- coding: utf-8 -*- # 斌彬电脑 # @Time : 2018/9/15 0015 4:52 #cookie 是服务器发给浏览器的特殊信息 # 可以理解为一个临时通行证 # ...

  6. [NOI导刊2010提高]黑匣子

    OJ题号:洛谷1801 思路:建立一个大根堆.一个小根堆.大根堆维护前i小的元素,小根堆维护当前剩下的元素. #include<cstdio> #include<queue> ...

  7. 47. 全排列 II

    47. 全排列 II 题意 给定一个可包含重复数字的序列,返回所有不重复的全排列. 示例: 输入: [1,1,2]输出:[ [1,1,2], [1,2,1], [2,1,1]] 解题思路 去重的全排列 ...

  8. X5功能目录排序

    在UI/system/config/functionSequecce.xml下面. <?xml version="1.0" encoding="utf-8" ...

  9. Android笔记(四):RecyclerView

    RecyclerView是ListView的增强版.有了它之后,你就可以抛弃ListView了. recycle,重复利用.在ListView里,我们得自己写重复利用View的代码,而Recycler ...

  10. C语言事实上不简单:sizeof

    问:C语言中一共同拥有多少个keyword? 答:32个. 答不上来的没关系.非常正常.我们玩的是程序的艺术.而不是背数字. 只是这个特殊的数字1<<5也是非常好记的-.-. 问:size ...