一、生成器

1、什么是生成器?

在python中,一边循环一边计算的机制,称为生成器:generator.

2、生成器有什么优点?

  1、节约内存。python在使用生成器时对延迟操作提供了支持。所谓延迟,是指在需要的时候才产生结果,而不是立即产生结果。这样在需要的时候才去调用结果,而不是将结果提前存储起来要节约内存。比如用列表的形式存放较大数据将会占用不少内存。这是生成器的主要好处。比如大数据中,使用生成器来调取数据结果而不是列表来处理数据,因为这样可以节约内存。

  2、迭代到下一次的调用时,所使用的参数都是第一次所保留下的。

3、在python中创建生成器

在python中,有两种创建生成器的方式:

  1、生成器表达式

类似与列表推导,但是,生成器返回按需产生结果的一个对象,而不是一次构建一个结果列表。

使用列表推导,将会一次返回所有结果:

 li=[i**2 for i in range(5)]
print(li) [0, 1, 4, 9, 16]
[Finished in 0.3s]

将列表推导的中括号,替换成圆括号,就是一个生成器表达式:

 li=(i**2 for i in range(5))
print(li)
for x in range(5):
print(next(li))

输出结果为:

 <generator object <genexpr> at 0x0000000001E18938>
0
1
4
9
16
[Finished in 0.3s]

注意:创建完成生成器后,可以使用next()来调用生成器的数据结果,每调用一次返回一个值,直到调用结束。调用结束后li中为空的,不在有任何值。要想使用它,只能重新创建新的生成器。(生成器表达式的第四行代码也可以改成:print(x).)

  2、生成器函数

常规函数定义,但是使用yield语句而不是return语句返回结果。yield语句每次返回一个结果,但每个结果中间,挂起函数的状态,以便下次从它离开的地方继续执行。

我们下面来看一个例子。下面为一个可以无穷生产奇数的生成器函数。

 def odd():
n=1
while True:
yield n
n+=2
odd_num=odd()
count=0
for o in odd_num:
if count >=5:
break
print(o)
count += 1

输出结果为:

 1
3
5
7
9
[Finished in 0.3s]

上面的函数中,yield是必备的,当一个普通函数中包含yield时,系统会默认为是一个generator。

再举一个例子。使用生成器函数来生成斐波纳契数列。

 def fib(times):
n = 0
a,b = 0,1
while n < times:
yield b
a,b = b,a + b
n += 1
return "done"
f = fib(5)
for x in f:
print(x)

输出结果为:

 1
1
2
3
5
[Finished in 0.3s]

生成器的两种方法:__next__() 和 send() 方法。

其中,__next__() 方法和next的作用是一样的。如下所示。

 def fib(times):
n=0
a,b=0,1
while n < times:
yield b
a,b=b,a+b
n+=1
f = fib(5)
for i in range(5):
print(f.__next__())

输出结果为:

 1
1
2
3
5
[Finished in 0.3s]

从上面的程序中可以看出,f._next_() 和 next(f) 是作用是一样的。

下面再来看看send() 方法的使用。

 def fib(times):
n = 0
a,b = 0,1
while n < times:
temp = yield b
print(temp)
a,b = b,a+b
n += 1
f = fib(5)
print(f.__next__())
print(f.send("haha"))
print(f.send("wangji"))

输出结果为:

 1
haha
1
wangji
2
[Finished in 0.3s]

从上面代码可以看出:使用send()时,必须在yield前面加上一个变量,并打印这个变量。在调用send()方法时其前面需要至少使用一次next()或__next__()方法,因为生成器不可以在使用之前导入任何非None参数。由此可以知道,send()是用来向生成器中导入参数并返回该参数的,与该参数一起返回的还有生成器中原先保存的数据。

4、再看生成器

前面已经对生成器有了感性的认识,我们以生成器函数为例,再来深入探讨一下python的生成器:

  1、语法上和函数类似:生成器函数和常规函数几乎是一样的。它们都是使用def语句进行定义,差别在于,生成器使用yield语句返回一个值,而常规函数使用return语句返回一个值。

  2、自动实现迭代器协议:对于生成器,python会自动实现迭代器协议,以便应用到迭代北京中(如for循环,sum函数)。由于生成器自动实现了迭代器协议,所以,我们可以调用它的next方法,并且,在没有值可以返回的时候,生成器自动产生StopIteration异常

  3、状态挂起:生成器使用yield语句返回一个值。yield语句挂起该生成器函数的状态,保留足够的信息,以便以后从它离开的地方继续执行

5、示例

我们再来看两个生成器的例子,以便大家更好的理解生成器的作用。

首先,生成器的好处是延迟计算,一次返回一个结果。也就是说,它不会一次生成所有的结果,这对于大数据量处理,将会非常有用。

大家可以在自己的电脑上试试下面两个表达式,并且观察内存占用情况。对于前一个表达式,我在自己的电脑上进行测试,还没有看到最终结果电脑就已经卡死,对于后一个表达式,几乎没有什么内存占用。

 a=sum([i for i in range(1000000000)])
print(a) b=sum(i for i in range(1000000000))
print(b)

除了延迟计算,生成器还能有效提高代码可读性。例如,现在有一个需求,求一段文字中,每个单词出现的位置。

不使用生成器的情况:

 def index_words(text):
result = []
if text:
result.append(0)
for index,letter in enumerate(text,1):
if letter == " ":
result.append(index)
return result

使用生成器的情况:

 def index_word(text):
if text:
yield 0
for index,letter in enumerate(text,1):
if letter == " ":
yield index

这里,至少有两个充分的理由说明,使用生成器比不使用生成器代码更加清晰:

  1、使用生成器以后,代码行数更少。大家要记住,如果想把代码写的Pythonic,在保证代码可读性的前提下,代码行数越少越好。

  2、不使用生成器的时候,对于每次结果,我们首先看到的是result.append(index),其次,才是index。也就是说,我们每次看到的是一个列表的append操作,只是append的是我们想要的结果。使用生成器的时候,直接yield  index,少了列表append操作的干扰,我们一眼就能够看出,代码是要返回Index。

这个例子充分说明了,合理使用生成器,能够有效提高代码可读性。只要大家完全接受了生成器的概念,理解了yield语句和return语句一样,也是返回一个值。那么,就能够理解为什么使用生成器比不使用生成器要好,能够理解使用生成器真的可以让代码变得清晰易懂。

6、使用生成器的注意事项

相信通过这篇文章,大家已经能够理解生成器的作用和好处了。但是,还没有结束,使用生成器,也有一点注意事项。

我们直接来看例子,假设文件中保存了省份的人口总数,现在,需要求每个省份的人口占全国总人口的比例。显然,我们需要先求出全国的总人口,然后在遍历每个省份的人口,用每个省的人口数除以总人口数,就得到了每个省份的人口占全国人口的比例。

如下所示:

 def get_province_population(filename):
with open(filename) as f:
for line in f:
yield int(line) gen=get_province_population("data.txt")
all_population = sum(gen)
#print all_population
for population in gen:
print(population/all population)

执行上面的这段代码将不会有任何输出,这是因为,生成器只能遍历一次。在我们执行sum语句的时候,就遍历了我们的生成器,当我们再次遍历我们的生成器的时候,将不会有任何记录。所以,上面的代码不会有任何输出。

因此,生成器的唯一注意事项就是:生成器只能遍历一次

7、总结

本文深入浅出地介绍了Python中,一个容易被大家忽略的重要特性,即Python的生成器。在实际工作中,充分利用Python生成器,不但能够减少内存使用,还能够提高代码可读性。掌握生成器也是Python高手的标配。希望本文能够帮助大家理解Python的生成器。

二、迭代器

迭代是访问集合元素的一种方式。迭代器是一个可以记住遍历位置的对象。迭代器只能往前不能后退。

1、什么是可迭代对象(Iterable)

  —  集合数据类型,如 list 、tuple、dict、set、str 等

  —  生成器和带yield 的generator function

2、如何判断对象可迭代?

  —  from collections import Iterable

  —  isinstance([ ],Iterable)

              图2-1 命令行窗口下的操作图

  如上,命令行模式下,先导入Iterable模块,然后输入isinstance([ ],Iterable),括号中前面为待判断的对象,结果以布尔类型结束(True或False),列表是可迭代对象,因此返回True。注意:整数是不可迭代对象。

3、什么是迭代器呢?

迭代器是可以被next() 函数调用并不断返回下一个值的对象称为迭代器。因此生成器是迭代器的子类,但是注意集合类型的可迭代对象不是迭代器。

  —  from collections import Iterator

  —  isinstance((x for x in range(10)),Iterator)

  这两行代码是用来判断是否为迭代器的,返回True或False。

iter()函数:将可迭代对象转换成迭代器。

                图2-2 迭代器的判断

三、闭包

1、什么是闭包?

内部函数对外部函数作用域里变量的引用(非全局变量),则称内部函数为闭包。

闭包三要素:

      1、嵌套函数 

      2、变量的引用

      3、返回内部函数

举例如下:

 #定义一个嵌套函数(要素1)
def test(num):
def test_in(num_in):
#内部函数引用外部函数的变量(非全局变量)(要素2)
print("sum = %s"%(num + num_in))
#返回的结果可以被打印出来
return num,num_in
#返回内部的函数(要素3)
return test_in
#这里的rtn就是test_in
rtn = test(10)
print(rtn)
#内部函数test_in传参
print(rtn(20))

输出结果为:

 <function test.<locals>.test_in at 0x000000000220E378>
sum = 30
(10, 20)
[Finished in 0.3s]

定义闭包时,必须满足上面三个要素。

2、闭包的应用

 #求一元一次方程的值,输入x,求y
def fun(a,b): #其中a,b是固定的
def fun_in(x):
return a*x+b
return fun_in
f = fun(3,5)
print(f(1)) #每次输入不同的x值即可求出对应的y值
print(f(3))

输出结果为:

 8
14
[Finished in 0.3s]

上面的函数中,利用闭包来求一元一次方程的值,更方便,直接输入x的值即可求出对应的y的值。因为这利用了闭包可以记住外部函数的参数的特性。

四、装饰器

1、什么是装饰器(decorator)?

装饰器其实就是一个闭包,把一个函数当作参数然后返回一个替代版函数。

装饰器有2个特性:
 
 1、可以把被装饰的函数替换成其他函数

  2、可以在加载模块时候立即执行

举例如下:

  A、装饰器对无参数函数进行装饰:

 #装饰器本质就是一个闭包,
#将函数当成参数传进去
def deco(fun):
def inner():
print("你想进行的操作1")
print("你想进行的操作2")
fun()
return inner
#@deco是一个“语法糖”
@deco
def test():
print("test函数的操作")
#这个test已经被装饰了,不是原来的test
test()
#14行的test()等同于如下操作:
#rtn = deco(test)
#rtn()

输出结果为:

 你想进行的操作1
你想进行的操作2
test函数的操作
[Finished in 0.3s]

2、装饰器的功能有:

        1、引入日志

        2、函数执行时间统计

        3、执行函数前预备处理

        4、执行函数后清理功能

        5、权限z校验等场景

        6、缓存

3、装饰器的分类

装饰器可分为对有无参数函数进行装饰的装饰器对有无返回值函数进行装饰的装饰器,组合起来一共有4种。即:装饰器对无参数无返回值的函数进行装饰,装饰器对无参数有返回值的函数进行装饰,装饰器对有参数无返回值的函数进行装饰,装饰器对有参数有返回值的函数进行装饰。

下面举一个装饰器实际应用的例子。

  A、装饰器对无参数函数进行装饰

 #定义函数:完成包裹数据
def makeBold(fn):
def wrapped():
return "<b>" + fn() + "</b>"
return wrapped #定义函数:完成包裹数据
def makeItalic(fn):
def wrapped():
return "<i>" + fn() + "</i>"
return wrapped @makeBold
def test1():
return "hello world - 1" @makeItalic
def test2():
return "hello world - 2" @makeBold
@makeItalic
def test3():
return "hello world - 3" print(test1())
print(test2())
print(test3())

输出结果为:

 <b>hello world - 1</b>
<i>hello world - 2</i>
<b><i>hello world - 3</i></b>
[Finished in 0.3s]

上面这个例子是做网页的时候对字体进行设置,对test1()进行加粗,对test2()斜体处理,对test3()先斜体在加粗。注意:对一个函数可以同时使用多个装饰器,装饰顺序由内而外。

  B、装饰器对有参数函数进行装饰

 #定义一个装饰器
def deco(func):
def wrapper(a,b): #内部函数的参数必须和被装饰的函数保持一致
print("添加的功能")
func(a,b) return wrapper @deco
#有参数的函数
def sum(a,b):
print(a+b) sum(10,20)

输出结果为:

 添加的功能
30
[Finished in 0.3s]

当装饰器装饰有参数的函数时,装饰器内部的函数也必须带有和其相同的参数,因为被装饰的参数会被当成参数传进装饰器的内部函数中,如果两者的参数不一致,会报错。

C、装饰器对不定长参数函数进行装饰

 from time import ctime,sleep
#定义一个装饰器,装饰不定长参数函数
def deco(func):
def wrapper(*args,**kwargs):
print("%s called at the %s"%(func.__name__,ctime()))
func(*args,**kwargs)
return wrapper @deco
def test1(a,b,c):
print(a+b+c) @deco
def test2(a,b):
print(a+b) test1(10,20,30)
sleep(2)
test1(30,40,50) sleep(1)
test2(10,20)

输出结果为:

 test1 called at the Fri Nov 10 19:08:03 2017
60
test1 called at the Fri Nov 10 19:08:05 2017
120
test2 called at the Fri Nov 10 19:08:06 2017
30
[Finished in 3.3s]

 D、装饰器对有返回值的函数进行装饰

 from time import ctime,sleep
#定义一个装饰器,装饰不定长参数函数
def deco(func):
def wrapper(*args,**kwargs):
print("%s called at the %s"%(func.__name__,ctime()))
return func(*args,**kwargs)
return wrapper
#上面的装饰器是一个万能的装饰器,因为它可以装饰任意一种函数
#包括有无参数函数和有无返回值函数 @deco
def test():
return "---ha--ha---" t = test()
print(t)

输出结果为:

 test called at the Fri Nov 10 19:19:20 2017
---ha--ha---
[Finished in 0.3s]

上面的装饰器是一个万能的装饰器,因为其可以用来装饰任何函数,包括有无参数函数和有无返回值函数。

python中“生成器”、“迭代器”、“闭包”、“装饰器”的深入理解的更多相关文章

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

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

  2. Day4 - Python基础4 迭代器、装饰器、软件开发规范

    Python之路,Day4 - Python基础4 (new版)   本节内容 迭代器&生成器 装饰器 Json & pickle 数据序列化 软件目录结构规范 作业:ATM项目开发 ...

  3. Python基础4 迭代器、装饰器、软件开发规范

    本节内容 迭代器&生成器 装饰器 Json & pickle 数据序列化 软件目录结构规范 作业:ATM项目开发 1.列表生成式,迭代器&生成器 列表生成式 孩子,我现在有个需 ...

  4. python基础之迭代器生成装饰器

    基本概念 1.容器(container) 容器是一种把多个元素组织在一起的数据结构,容器中的元素可以逐个地迭代获取,可以用in, not in关键字判断元素是否包含在容器中.通常这类数据结构把所有的元 ...

  5. python 函数名 、闭包 装饰器 day13

    1,函数名的使用. 函数名是函数的名字,本质就是变量,特殊的变量.函数名()加括号就是执行此函数. 1,单独打印函数名就是此函数的内存地址. def func1(): print(555) print ...

  6. python基础之迭代器、装饰器、软件开发目录结构规范

    生成器 通过列表生成式,我们可以直接创建一个列表.但是,受到内存限制,列表容量肯定是有限的.而且,创建一个包含100万个元素的列表,不仅占用很大的存储空间,如果我们仅仅需要访问前面几个元素,那后面绝大 ...

  7. Python中生成器,迭代器,以及一些常用的内置函数.

    知识点总结 生成器 生成器的本质就是迭代器. 迭代器:Python中提供的已经写好的工具或者通过数据转化得来的. 生成器:需要我们自己用Python代码构建的 创建生成器的三种方法: 通过生成器函数 ...

  8. python基础语法_9-1闭包 装饰器补充

    1.闭包的概念 closure:内部函数中对enclosing作用域的变量进行引用,外部函数返回内部函数名   2.函数实质与属性 函数是一个对象:在内存中有一个存储空间 函数执行完成后内部变量回收: ...

  9. 【Python入门学习】闭包&装饰器&开放封闭原则

    1. 介绍闭包 闭包:如果在一个内部函数里,对在外部作用域的变量(不是全局作用域)进行引用,那边内部函数被称为闭包(closure) 例如:如果在一个内部函数里:func2()就是内部函数, 对在外部 ...

  10. Python中的多个装饰器装饰一个函数

    def wrapper1(func1): def inner1(): print('w1 ,before') func1() print('w1 after') return inner1 def w ...

随机推荐

  1. win10+eclipse+hadoop2.7.2+maven+local模式直接通过Run as Java Application运行wordcount

    一.准备工作 (1)Hadoop2.7.2 在linux部署完毕,成功启动dfs和yarn,通过jps查看,进程都存在 (2)安装maven 二.最终效果 在windows系统中,直接通过Run as ...

  2. oracle存储过程(返回列表的存储结合游标使用)总结 以及在java中的调用

    这段时间开始学习写存储过程,主要原因还是因为工作需要吧,本来以为很简单的,但几经挫折,豪气消磨殆尽,但总算搞通了,为了避免后来者少走弯路,特记述与此,同时亦对自己进行鼓励. 以下是我在开发项目中第一次 ...

  3. Django---ModelForm详解

    示例: from django.db import models from django.forms import ModelForm TITLE_CHOICES = ( ('MR', 'Mr.'), ...

  4. OpenStack之Neutron网络服务(一)

    1.Neutron概要 OpenStack网络服务提供了一个API接口,允许用户在云上设置和定义网络连接和地址.这个网络服务的项目代码名称是Neutron.OpenStack网络处理虚拟设备的创建和管 ...

  5. 20145302张薇《Java程序设计》实验五报告

    20145302张薇 实验五:Java网络编程及安全 实验内容 掌握Socket程序的编写: 掌握密码技术的使用: 设计安全传输系统. 实验要求 基于Java Socket实现安全传输 基于TCP实现 ...

  6. 探讨"点"语法的奥秘

    点语法 一直以来,都不理解什么是点语法,都说是相当于链接或是路径.也许我浏览的信息量少吧,看过好几篇有关的博文,没什么记载,本篇只是初步见解分析. 在javascript里,属性和方法都使用“点”语法 ...

  7. Base64编码原理

    Base64编码之所以称为Base64,是因为其使用64个字符来对任意数据进行编码,同理有Base32.Base16编码.标准Base64编码使用的64个字符为: 这64个字符是各种字符编码(比如AS ...

  8. 【Semantic Segmentation】DeepLab V3(转)

    原文地址:DeepLabv3 代码: TensorFlow Abstract DeepLabv3进一步探讨空洞卷积,这是一个在语义分割任务中:可以调整滤波器视野.控制卷积神经网络计算的特征响应分辨率的 ...

  9. LA 5135 井下矿工(点—双连通分量模板题)

    https://vjudge.net/problem/UVALive-5135 题意:在一个无向图上选择尽量少的点涂黑,使得任意删除一个点后,每个连通分量至少有一个黑点. 思路: 首先dfs遍历求出割 ...

  10. mysql 跨库查询问题

    MySQL实现跨服务器查询 https://blog.csdn.net/LYK_for_dba/article/details/78180444 mysql> create database l ...