python基础之迭代器、生成器、装饰器

一、列表生成式

a = [0,1,2,3,4,5,6,7,8,9]
b = []
for i in a:
    b.append(i+1)
print(b)
a = b
print(a)
----------------------------------------------
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
a = [1,3,4,5,4,5,6,7,8,11]
for index,i in enumerate(a):
    a[index] += 1
print(a)
------------------------------------------
[2, 4, 5, 6, 5, 6, 7, 8, 9, 12]
a = [1,2,3,4,5,6,7,8,9,10]
a = map(lambda x:x+1, a)
print(a)
for i in a:
    print(i)
-----------------------------------------------------
<map object at 0x000000C5343A32B0>
2
3
4
5
6
7
8
9
10
11
a = [i+1 for i in range(10)]
print(a)
--------------------------------------------
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

二、生成器

列表解析式(列表推导式)生成器generator

通过列表生成式,我们可以直接创建一个列表。但是,受到内存限制,列表容量肯定是有限的。而且,创建一个包含100万个元素的列表,不仅占用很大的存储空间,如果我们仅仅需要访问前面几个元素,那后面绝大多数元素占用的空间都白白浪费了。所以,如果列表元素可以按照某种算法推算出来,那我们是否可以在循环的过程中不断推算出后续的元素呢?这样就不必创建完整的list,从而节省大量的空间。在Python中,这种一边循环一边计算的机制,称为生成器:generator。

要创建一个generator,有很多种方法。第一种方法很简单,只要把一个列表生成式的[]改成(),就创建了一个generator:

In [1]: l = [x*x for x in range(10)]
In [2]: l
Out[2]: [0, 1, 4, 9, 16, 25, 36, 49, 64, 81]
In [3]: g = (x*x for x in range(10))
In [4]: g
Out[4]: <generator object <genexpr> at 0x0000003E8157CE58>

创建Lg的区别仅在于最外层的[]()L是一个list,而g是一个generator。

我们可以直接打印出list的每一个元素,但我们怎么打印出generator的每一个元素呢?

如果要一个一个打印出来,可以通过next()函数获得generator的下一个返回值:

In [5]: next(g)
Out[5]: 0
In [6]: next(g)
Out[6]: 1
In [7]: next(g)
Out[7]: 4
In [8]: next(g)
Out[8]: 9

生成器保存的是算法,每次调用 next(g) ,就计算出 g 的下一个元素的值,直到计算到最后一个元素,没有更多的元素时,抛出 StopIteration 的异常。

当然,上面这种不断调用next(g)实在是太变态了,正确的方法是使用for循环,因为generator也是可迭代对象:

In [9]: g = (x * x for x in range(10))
In [10]: for n in g:
    ...:     print(n)
    ...: 
0
1
4
9
16
25
36
49
64
81

所以,我们创建了一个generator后,基本上永远不会调用next(),而是通过for循环来迭代它,并且不需要关心StopIteration的错误。

generator非常强大。如果推算的算法比较复杂,用类似列表生成式的for循环无法实现的时候,还可以用函数来实现。

比如,著名的斐波拉契数列(Fibonacci),除第一个和第二个数外,任意一个数都可由前两个数相加得到:

1, 1, 2, 3, 5, 8, 13, 21, 34, ...

斐波拉契数列用列表生成式写不出来,但是,用函数把它打印出来却很容易:

def fib(max):
    n, a, b = 0, 0, 1
    while n < max:
        print(b)
        a, b = b, a + b #相当于t = (b, a + b);a = t[0];b = t[1](t是一个tuple)
        n = n + 1
    return 'done'

fib(10)
----------------------------------------------------------------------
1
1
2
3
5
8
13
21
34
55

fib函数实际上是定义了斐波拉契数列的推算规则,可以从第一个元素开始,推算出后续任意的元素,这种逻辑其实非常类似generator。

也就是说,上面的函数和generator仅一步之遥。要把fib函数变成generator,只需要把print(b)改为yield b就可以了:

def fib(max):
    n, a, b = 0, 0, 1
    while n < max:
        # print(b)
        yield b
        a, b = b, a + b #相当于t = (b, a + b);a = t[0];b = t[1](t是一个元组)
        n = n + 1
    return 'done'

print(fib(10))
---------------------------------------------------
<generator object fib at 0x0000001082BB7A20>

这就是定义generator的另一种方法。如果一个函数定义中包含yield关键字,那么这个函数就不再是一个普通函数,而是一个generator。

函数是顺序执行,遇到return语句或者最后一行函数语句就返回。而变成generator的函数,在每次调用next()的时候执行,遇到yield语句返回,再次执行时从上次返回的yield语句处继续执行。

例子:通过yield实现在单线程的情况下并发运算的效果

 1 #!/usr/bin/env python
 2 # -*- coding:utf-8 -*-
 3 # Author: Renyz
 4 #_*_coding:utf-8_*_
 5 import time
 6 def consumers(name):
 7     print("%s 准备吃包子啦!" %name)
 8     while True:
 9        baozi = yield
10        print("包子[%s]来了,被[%s]吃了!" %(baozi,name))
11 
12 def producer():
13     c1 = consumers('')
14     c2 = consumers('')
15     c1.__next__()
16     c2.__next__()
17     print("开始做包子啦!")
18     for i in range(10):
19         time.sleep(1)
20         print("做了2个包子!")
21         c1.send(i)
22         c2.send(i)
23 
24 producer()
25 # 通过生成器实现协程并行运算
View Code

三、迭代器

迭代是一个重复的过程,即每一次重复为一次迭代,并且每次迭代的结果都是下一次迭代的初始值

可以直接作用于for循环的数据类型有以下几种:
一类是集合数据类型,如list、tuple、dict、set、str等;
一类是generator,包括生成器和带yield的generator function。
这些可以直接作用于for循环的对象统称为可迭代对象:Iterable。
可以使用isinstance()判断一个对象是否是Iterable对象:

In [1]: from collections import Iterable
In [2]: isinstance([],Iterable)
Out[2]: True
In [3]: isinstance({},Iterable)
Out[3]: True
In [4]: isinstance((),Iterable)
Out[4]: True
In [5]: isinstance('abc',Iterable)
Out[5]: True
In [7]: isinstance((x for x in range(10)),Iterable)
Out[7]: True
In [8]: isinstance(123,Iterable)
Out[8]: False

而生成器不但可以作用于for循环,还可以被next()函数不断调用并返回下一个值,直到最后抛出StopIteration错误表示无法继续返回下一个值了。

*可以被next()函数调用并不断返回下一个值的对象称为迭代器:Iterator

可以使用isinstance()判断一个对象是否是Iterator对象:

In [9]: from collections import Iterator
In [10]: isinstance((x for x in range(10)),Iterator)
Out[10]: True
In [11]: isinstance(123,Iterator)
Out[11]: False
In [12]: isinstance('abc',Iterator)
Out[12]: False
In [13]: isinstance((),Iterator)
Out[13]: False
In [14]: isinstance([],Iterator)
Out[14]: False
In [15]: isinstance({},Iterator)
Out[15]: False

生成器都是Iterator对象,但listdictstr虽然是Iterable,却不是Iterator

listdictstrIterable变成Iterator可以使用iter()函数:

In [16]: isinstance(iter({}),Iterator)
Out[16]: True
In [17]: isinstance(iter([]),Iterator)
Out[17]: True
In [18]: isinstance(iter('abc'),Iterator)
Out[18]: True

为什么listdictstr等数据类型不是Iterator

这是因为Python的Iterator对象表示的是一个数据流,Iterator对象可以被next()函数调用并不断返回下一个数据,直到没有数据时抛出StopIteration错误。可以把这个数据流看做是一个有序序列,但我们却不能提前知道序列的长度,只能不断通过next()函数实现按需计算下一个数据,所以Iterator的计算是惰性的,只有在需要返回下一个数据时它才会计算。

Iterator甚至可以表示一个无限大的数据流,例如全体自然数。而使用list是永远不可能存储全体自然数的。

小结

凡是可作用于for循环的对象都是Iterable类型;

凡是可作用于next()函数的对象都是Iterator类型,它们表示一个惰性计算的序列;

集合数据类型如listdictstr等是Iterable但不是Iterator,不过可以通过iter()函数获得一个Iterator对象。

Python的for循环本质上就是通过不断调用next()函数实现的

#1、为何要有迭代器?
对于序列类型:字符串、列表、元组,我们可以使用索引的方式迭代取出其包含的元素。但对于字典、集合、文件等类型是没有索引的,若还想取出其内部包含的元素,则必须找出一种不依赖于索引的迭代方式,这就是迭代器

#2、什么是可迭代对象?
可迭代对象指的是内置有__iter__方法的对象,即obj.__iter__,如下:
'world'.__iter__
(4,5,6).__iter__
[1,2,3].__iter__
{'a':1}.__iter__
{'a','b'}.__iter__
open('a.txt').__iter__
#3、什么是迭代器对象?
可迭代对象执行obj.__iter__()得到的结果就是迭代器对象
而迭代器对象指的是即内置有__iter__又内置有__next__方法的对象

#4、文件类型是迭代器对象
open('a.txt').__iter__()
open('a.txt').__next__()

#5、总结:
迭代器对象一定是可迭代对象,而可迭代对象不一定是迭代器对象

四、装饰器

  写代码要遵循开放封闭原则,虽然在这个原则是用的面向对象开发,但是也适用于函数式编程,简单来说,它规定已经实现的功能代码不允许被修改,但可以被扩展即:
  对扩展是开放的,而对修改是封闭的。
  因此,开放封闭原则主要体现在两个方面:
  对扩展开放,意味着有新的需求或变化时,可以对现有代码进行扩展,以适应新的情况。
  对修改封闭,意味着类一旦设计完成,就可以独立完成其工作,而不要对类或者函数内部进行任何修改。
装饰器就是:
  在不修改源代码和调用方式的基础上给其增加新的功能,多个装饰器可以装饰在同一个函数上

1、无参装饰器

原函数
import
time def bar(): time.sleep(1) print('这是bar函数') bar() ---------------------------------------------- 这是bar函数
# 无参装饰器
import time
def timer(func):
    def deco():
        start_time = time.time()
        res = func()    #func()=bar()
        end_time = time.time()
        print('cost', end_time-start_time)
        return res
    return deco
@timer  #bar=deco |func=bar
def bar():
    time.sleep(1)
    print('这是bar函数')
# bar = timer(bar)  #bar=deco |func=bar
bar()

2、有参装饰器

# 有参装饰器
import time
def timer(func):
    def deco(*args,**kwargs):
        start_time = time.time()
        res = func(*args,**kwargs)
        end_time = time.time()
        print('cost', end_time-start_time)
        return res
    return deco
@timer
def bar(a, b):
    time.sleep(1)
    print('这是bar')
    print(a)
    print(b)
bar(1,2)

3、带参数的装饰器

# 带参数的装饰器
def default_engine(engine=None):
    def auth(func):
        def deco(*args, **kwargs):
            user = input('user:')
            password = input('password:')
            if engine == 'mysql':
                if user == 'root' and password == 'root':
                    res = func(*args, **kwargs)
                    return res
                else:
                    print('用户名或密码错误')
            else:
                print('没有这个引擎')
        return deco
    return auth
@default_engine(engine='mysql')
def index():
    print('welcome to home page')
# res = default_engine(engine='mysql')
# index = res(index)
index()

五、闭包

1、闭包定义

(1)闭包函数必须有内嵌函数;

(2)内嵌函数需要引用该嵌套函数上一级namespace中的变量;

(3)闭包函数必须返回内嵌函数;

通过这三点,就可以创建一个闭包;python装饰器就是使用了闭包

闭包的好处:使代码变得简洁

在函数内部再定义一个函数,并且这个函数用到了外边函数的变量,并且把里面的函数返回,我们把这种情况叫闭包

def test(num):  #定义一个函数
    def test_in(num2):  #又定义一个函数
        print(num + num2)
    return test_in  #返回test_in里面的引用/就是闭包的结果
res = test(22)  #22表示给test函数赋值,num=22
res(11) #11是参数num2的实参;加括号表示运行

例子:

def line_conf(x,y):
    def line(n):
        return x*n+y
    return line
l1 = line_conf(3,8)
print(l1(2))

 

posted @ 2019-09-19 09:13  Wolf_Coder  阅读(206)  评论(0编辑  收藏  举报