Python菜鸟编程第十一课之迭代器与生成器

Python菜鸟编程第十一课之迭代器与生成器

1.迭代器

可迭代对象:list,str,tuple,dict,可以用for…in…遍历的序列。(从深层次中理解:指存储了元素的一个容器对象,且容器中的元素可以通过_iter_ ( )方法或 _getitem_( )方法访问。)

迭代器协议:对象必须提供一个next方法,执行该方法要么返回迭代中的下一项,要么引起StopIteration 异常,以终止迭代(只能往下走,不能回退)。

demo:

l1=[1,2,3]
li_iter=iter(l1)
# li_iter=l1.__iter__()#与前一句代码作用相同
print(next(li_iter))
print(next(li_iter))
print(li_iter.__next__())
#print(li_iter.__next__()) 如果再迭代一次,则会报错
运行结果:
1
2
3

demo2:

判断一个对象是否可以迭代。

from collections import Iterable
print(isinstance([],Iterable))
print(isinstance(str(),Iterable))
print(isinstance({},Iterable))
print(isinstance(set(),Iterable))
print(isinstance(123,Iterable))
print(isinstance(True,Iterable))
运行结果:
True
True
True
True
False
False

demo3:

自定义一个容器,判断是否可迭代

from collections import Iterable
class Myclas:
    def __init__(self):
        self.names=[]

    def add(self,name):
        self.names.append(name)

my_class=Myclas()
my_class.add('qqq')
my_class.add('www')
my_class.add('eee')

print('是否为可迭代对象:',isinstance(my_class,Iterable))
print(type(my_class))
运行结果:
是否为可迭代对象: False
<class '__main__.Myclas'>

demo4:

自定义一个容器,判断是否可迭代

from collections import Iterable
class Myclas:
    def __init__(self):
        self.names=[]

    def add(self,name):
        self.names.append(name)

    def __iter__(self):
        return self.names.__iter__()

my_class=Myclas()
my_class.add('qqq')
my_class.add('www')
my_class.add('eee')

print('是否为可迭代对象:',isinstance(my_class,Iterable))
print(type(my_class))
运行结果:
是否为可迭代对象: True
<class '__main__.Myclas'>
qqq
www
eee

与demo3相比,增了一个_iter_()方法。

_iter_()方法,可以为我们提供一个迭代器。在迭代一个对象的时候,实际上就是获取该对象提供的一个迭代器,然后通过该迭代器依次获取对象的每一个数据。

由此可见,for…in…循环的本质就是通过iter()函数获取可迭代对象的Iterable迭代器,然后对获取到的迭代器不断调用next()方法来获取下一个值并将其赋值,当遇到StopIteration的异常后退出。

迭代器的核心就是通过next()函数调用下一个数据,如果每次返回的数据不是在一个已有的数据集合中读取的,而是通过程序按照一定规律生成的,那么也就意味着可以不用依赖一个已有的数据集合,无需将所有的迭代对象数据一次性缓存下来供后续使用,这样可以节省大量的内存空间。

demo5:

通过迭代来实现斐波那契数列

class Fibonacci(object):
    '''斐波那契数列迭代器'''

    def __init__(self, n):
        # 记录生成的斐波那契数列的项数
        self.num1 = 0
        self.num2 = 1
        self.n = n
        self.fbindex = 0

    def __next__(self):
        '''调用next()函数来获取下一个数'''
        if self.fbindex < self.n:
            self.fbindex+=1
            num = self.num1
            self.num1, self.num2 = self.num2, self.num1 + self.num2
            return num
        else:
            raise StopIteration

    def __iter__(self):
        return self

fib=Fibonacci(10)
for num in fib:
    print(num,end=' ')
运行结果:
0 1 1 2 3 5 8 13 21 34 

demo6:

class test:
    def __init__(self, data=1):
        self.data = data

    def __iter__(self):
        return self

    def __next__(self):
        if self.data > 5:
            raise StopIteration
        else:
            self.data += 1
            return self.data

for i  in test(2):
    print(i)
运行结果:
3
4
5
6

从data=2开始迭代,直到data大于5。

2.生成器

生成器:利用迭代器,我们可以在每次迭代获取数据时(通过next()方法)按照特定的规律进行生成。但是我们在实现一个迭代器时,关于当前迭代的状态需要我们自己记录,进而才能根据当前的状态生成下一个数据,为了达到记录当前状态,并配合next()函数进行迭代使用,可以采用更简便的语法。

生成器其实是一种特殊的迭代器,比迭代器更加优雅。

demo:

li=[x**2 for x in range(6)]
print(li)

gen=(x**2 for x in range(6))
print(gen)

print('通过next()函数取得下一个值:',next(gen))
print('通过next()函数取得下一个值:',next(gen))
print('通过next()函数取得下一个值:',next(gen))
print('通过next()函数取得下一个值:',next(gen))
print('通过next()函数取得下一个值:',next(gen))
print('通过next()函数取得下一个值:',next(gen))
gen=(x**2 for x in range(6))#假如注释掉这一句代码,则下面的for循环不会输出结果,因为前面的next以及遍历完了。
for i in gen:
    print(i,end=' ')
运行结果:
[0, 1, 4, 9, 16, 25]
<generator object <genexpr> at 0x000001CEDEC3A750>
通过next()函数取得下一个值: 0
通过next()函数取得下一个值: 1
通过next()函数取得下一个值: 4
通过next()函数取得下一个值: 9
通过next()函数取得下一个值: 16
通过next()函数取得下一个值: 25
0 1 4 9 16 25 

2.1生成器函数

在函数中如果出现了yield关键字,那么该函数就不再是一个普通函数,而是生成器函数。

demo1:

def foo():
    yield 11
    yield 2
    return
    yield 3


f = foo()
print(next(f))#程序会停留在对应yield 后的语句
print(next(f))
print(next(f))
运行结果:
11 
2
Traceback (most recent call last):
  File "D:/PyCharm/BClass/PXClass/2019-7-30.py", line 114, in <module>
    print(next(f))
StopIteration

这里报错,是因为程序遇到return ,不再继续执行后续代码。假如 return 后返回一个值,比如 return 50,

报错信息处的StopIteration则会变为 StopIteration: 50。

demo2:

通过函数啦输出无穷个奇数

def odd():
    n = 1
    while True:
        yield n
        n += 2

odd_num = odd()
print(next((odd_num)))
print(next((odd_num)))
for i in odd_num:#这里限制,只输出到99
    if i <100:
        print(i,end=' ')
运行结果:
1
3
5 7 9 11 13 15 17 19 21 23 25 27 29 31 33 35 37 39 41 43 45 47 49 51 53 55 57 59 61 63 65 67 69 71 73 75 77 79 81 83 85 87 89 91 93 95 97 99 

demo3:

通过类来生成无穷个奇数。

class OddIter:
    def __init__(self):
        self.start = -1

    def __iter__(self):
        return self

    def __next__(self):
        self.start += 2
        return self.start


odd = OddIter()
for i in range(6):#这里限制,只输出到6项
    print(next(odd),end=' ')
运行结果:
1 3 5 7 9 11

2.2close()函数

手动关闭生成器,后面调用会引起StopIteration异常。

demo:

def gen():
    yield 1
    yield 2
    yield 3

g=gen()
print(next(g))
print(next(g))
g.clise()
print(next(g))
运行结果:
1
2
Traceback (most recent call last):
  File "D:/PyCharm/BClass/PXClass/2019-7-30.py", line 156, in <module>
    g.clise()
AttributeError: 'generator' object has no attribute 'clise'

这里本该输出3,但是close()函数存在,使得结果报错。

2.3send()函数

demo:

def gen():
    value = 0
    while True:
        rec = yield value#这里的等式右边相当于一个整体,接受回传值
        if rec == 'e':
            break
        value = 'got : %s' % rec


g = gen()
print(g.send(None))#第一次发送的值必须死None,否则报错,因为没有Python yield语句来接收这个值。
print(g.send('aaa'))
print(g.send('e'))
print(g.send('qwwer'))
运行结果:
0
got : aaa
Traceback (most recent call last):
  File "D:/PyCharm/BClass/PXClass/2019-7-30.py", line 171, in <module>
    print(g.send('e'))
StopIteration

send执行的顺序:rec = yield value这句话是从右往左执行的。当第一次send(None)时,启动生成器(也可以通过gen.next()语句来启动),从生成器gen()函数的第一行代码开始执行,直到第一次执行完yield(对应第4行)后,跳出生成器函数。这个过程中,n1一直没有定义。

运行到send(‘aaa’)时,进入生成器函数,此时,将yield value看做一个整体,赋值给它并且传回。此时即相当于把’aaa’赋值给value,但是并不执行yield部分,然后继续从yield的下一条语句继续执行。当传人的值为’e’时,退出生成器,引发异常,下面传入的’qwwer’并不会被接受到。send和next相比,只是开始多了一次赋值的动作,其他运行流程是相同的。

demo:

def gen():
    i=0
    while i<5:
        temp=yield  i
        print(temp)
        i+=1

obj=gen()
print(next(obj))
print(next(obj))
print(obj.send('city'))
print(next(obj))
运行结果:
0 #第一次运行结果
None #第二次运行结果
1
city #第三次运行结果
2
None #第四次运行结果
3

然后看不太懂的可以看这里:

https://blog.csdn.net/mieleizhi0522/article/details/82142856

3.闭包

闭包:闭是封闭(函数中的函数),包是包含(该函数对外部函数作用域而非全局作用域变量的引用)。

  • 内部函数对外部作用域里的变量的引用
  • 函数内的属性,都是由生命周期,都是在函数执行期间
  • 闭包内的闭包函数私有化了变量,完成了数据的封装,类似面向对象

demo1:

def foo():
    print('in foo()')

    def bar():
        print('in bar()')
#直接运行内部函数报错
bar()
运行结果:
Traceback (most recent call last):
  File "D:/PyCharm/BClass/PXClass/2019-7-30.py", line 204, in <module>
    bar()
NameError: name 'bar' is not defined

demo2:

def foo():
    print('in foo()')

    def bar():
        print('in bar()')

#考虑先运行外部函数,再运行内部函数,依然会报错
foo()
bar()
运行结果:
in foo()
Traceback (most recent call last):
  File "D:/PyCharm/BClass/PXClass/2019-7-30.py", line 206, in <module>
    bar()
NameError: name 'bar' is not defined

由于作用域的问题,函数内的属性都是由生命周期的,只有在函数运行时才会存在。所以,依然会报错

demo3:

假如我们一定要使用bar()函数,只能在foo()内部调用他,比如return bar()即可。

def foo():
    print('in foo()')

    def bar():
        print('in bar()')
    return bar()

foo()
运行结果:
in foo()
in bar()

4.装饰器

装饰器本身也是一个函数,作用是为现有存在的函数,在不改变函数的基础上,增加一些功能进行装饰。它是以闭包的形式去实现。在使用装饰器函数时,在被装饰的函数前一行使用@装饰器函数名形式来进行装饰。

装饰器的意义:

  • 不影响原因函数的功能
  • 可以添加新功能

假如拿到第三方的API接口,第三方不允许修改这个接口。这个时候就可以使用装饰器了。

demo:

现在在一个项目中,有很多函数,由于我们的项目越来越大,功能也越来越多,会导致程序越来越慢。

其中一个功能函数的实现,是实现一百万次的累加。

import time

def my_count():
    s = 0
    for i in range(1000001):
        s += i
    print('sum:', s)

start = time.time()
my_count()
end = time.time()
print('执行时间为:', (end - start))
运行结果:
sum: 500000500000
执行时间为: 0.06248211860656738

一个函数的实现非常简单,假如有几千个这样的函数,每个都写一遍,非常的麻烦,代码量也会非常多。也不符合开发原则,代码过于冗余。

#将上述代码中的执行代码封装成函数。
import time

def my_count():
    s = 0
    for i in range(1000001):
        s += i
    print('sum:', s)

def count_time(func):
    start = time.time()
    func()
    end = time.time()
    print('执行时间为:', (end - start))

count_time(my_count)
运行结果:
sum: 500000500000
执行时间为: 0.05983471870422363

经过修改,定义一个函数来实现时间计算功能。看上去比之前好很多,只需要将对应的函数传入到时间计算函数中去,但仍影响了原来的使用。

import time

def my_count():
    s = 0
    for i in range(1000001):
        s += i
    print('sum:', s)

def count_time(func):
    def wrapper():
        start = time.time()
        func()
        end = time.time()
        print('执行时间为:', (end - start))

    return wrapper

test=count_time(my_count)
test()
运行结果:
sum: 500000500000
执行时间为: 0.06083846092224121

这里再次对上述函数做出修改:使用装饰器。

import time

def count_time(func):
    def wrapper():
        start = time.time()
        func()
        end = time.time()
        print('执行时间为:', (end - start))

    return wrapper
@count_time#使用装饰器
def my_count():
    s = 0
    for i in range(1000001):
        s += i
    print('sum:', s)

my_count()
运行结果:
sum: 500000500000
执行时间为: 0.0738060474395752

这样实现的好处,定义闭包函数后,主需要通过@装饰器函数名形式的装饰器语法,就可以将@装饰器函数名加到要装饰的函数前即可。

这种不改变原有函数功能,对函数进行拓展的形式,就成为装饰器

在执行@装饰器函数名时,就是将原函数传递到闭包中。然后,原函数的引用指向闭包的装饰过的内部函数的引用。

4.1装饰器的几种形式:

1.无参无返回值

demo:

def setFunc(func):
    def wrapper():
        print('start')
        func()
        print('end')
    return wrapper

@setFunc
def show():
    print('show')

show()
运行结果:
start
show
end

2.无参有返回值

demo:

def setFunc(func):
    def wrapper():
        print('start')
        return func() #return 后不再执行
        print('end')
    return wrapper

@setFunc
def show():
    return 'show'

print(show())
运行结果:
start
show

3.有参无返回值

demo:

def setFunc(func):
    def wrapper(s):
        print('start')
        func(s) 
        print('end')
    return wrapper

@setFunc
def show(s):
    print('Hello %s'%s)

show('ss')
运行结果:
start
Hello ss
end

4.有参有返回值

demo:

def setFunc(func):
    def wrapper(s,q):
        print('start')
        return func(s,q) #return 后不再执行
        print('end')
    return wrapper

@setFunc
def myadd(x,y):
    return x+y
# def show(s):
#     print('Hello %s'%s)

print(myadd(1,1))
运行结果:
start
2

4.2万能装饰器

demo:

def setFunc(func):
    def wrapper(*args, **kwargs):
        print('Wrapper context.')
        return func(*args, **kwargs)

    return wrapper


@setFunc
def func(name, age):
    print(name, age)

@setFunc
def demo(a,b,*c,**d):
    print((a,b))
    print(c)
    print(d)
demo('city','college',1991,1,1,school='sss')
func('Tom', 18)
运行结果:
Wrapper context.
('city', 'college')
(1991, 1, 1)
{'school': 'sss'}
Wrapper context.
Tom 18

4.3函数被多个装饰器所装饰

一个函数在使用时,通过一个装饰器来拓展,可能并不能达到预期,这就需要被多个装饰器装饰。

函数可以像普通变量一样,作为函数的参数或者返回值传递,函数的内部可以定义另一个函数,目的就是隐藏函数功能的实现。一个函数可以被多个装饰器所装饰,一个装饰器也可以为多个函数提供装饰功能。

闭包实际上也是函数定义的一种形式,闭包定义的规则:在外部函数内定义一个内部函数,内部函数使用外部函数的变量,并返回内部函数的引用,Python中,装饰器就是用闭包来实现的。装饰器的作用:不改变现有函数的基础上为函数增加功能。

demo:

def setFunc1(func):
    def wrapper1(*args, **kwargs):
        print('Wrapper Context 1 Start'.center(40, '-'))
        func(*args, **kwargs)
        print('Wrapper Context 1 End'.center(40, '-'))

    return wrapper1


def setFunc2(func):
    def wrapper2(*args, **kwargs):
        print('Wrapper Context 2 Start'.center(40, '-'))
        func(*args, **kwargs)
        print('Wrapper Context 2 End'.center(40, '-'))

    return wrapper2

@setFunc1                           #--->F
@setFunc2  #装饰顺序,由近及远       #--->g
def show(*args, **kwargs):          #--->f
    print('Show Run.')


show()  #F(g(f))
运行结果:
--------Wrapper Context 1 Start---------
--------Wrapper Context 2 Start---------
Show Run.
---------Wrapper Context 2 End----------
---------Wrapper Context 1 End----------

博主QQ:1031748759,欢迎批评指正!

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值