多任务--协程

迭代器:

常用的可以迭代的类型:

1 from collections import Iterable
2 print(issubclass(int,Iterable))    #False
3 print(issubclass(float,Iterable))  #False
4 print(issubclass(list,Iterable))   #True
5 print(issubclass(tuple,Iterable))  #True
6 print(issubclass(dict,Iterable))   #True
7 print(issubclass(str,Iterable))    #True
8 print(issubclass(set,Iterable))    #True

下面尝试自己写个可以迭代的类出来:

 1 class ClassMate:
 2     def __init__(self):
 3         self.names = list()
 4     
 5     def add(self,name):
 6         self.names.append(name)
 7 
 8 if __name__ == '__main__':
 9     classmate = ClassMate()
10     classmate.add("tom")
11     classmate.add("jane")
12     classmate.add("egon")
13     
14     for name in classmate:   #报错,此时是不可以迭代的
15         print(name)   
此时的报错: TypeError: 'ClassMate' object is not iterable

继续

 1 class ClassMate:
 2     def __init__(self):
 3         self.names = list()
 4 
 5     def add(self,name):
 6         self.names.append(name)
 7 
 8     def __iter__(self):
 9         pass
10 
11 if __name__ == '__main__':
12     classmate = ClassMate()
13     classmate.add("tom")
14     classmate.add("jane")
15     classmate.add("egon")
16 
17     for name in classmate: #此时是不可以迭代的
18         print(name)
19     
20     #此时的报错是:TypeError: iter() returned non-iterator of type 'NoneType'

但是,此时它已经是个可迭代的对象了,使用如下代码验证:

print(issubclass(ClassMate,Iterable))

所以:

但是,还是用不了for 循环。

继续看:

只要使__iter__() 方法返回一个有iter 和next 方法的对象就行了!

for 循环的执行过程,

for 循环得到的是返回的对象里的__next__() 返回的值!

 1 from collections import Iterable,Iterator
 2 
 3 class ClassMate:
 4     def __init__(self):
 5         self.names = list()
 6 
 7     def add(self,name):
 8         self.names.append(name)
 9 
10     def __iter__(self):
11         # pass  #必须要返回一个具有 iter 和 next 方法的对象
12         return MyIterator()
13 
14 
15 class MyIterator:
16     def __iter__(self):
17         pass
18     def __next__(self):
19         pass
20 
21 
22 if __name__ == '__main__':
23     classmate = ClassMate()
24     classmate.add("tom")
25     classmate.add("jane")
26     classmate.add("egon")
27 
28     #判断 classmate 是否是可迭代对象
29     # print(isinstance(classmate,Iterable))  #只要是有对象iter() 方法就行,就是可迭代对象
30 
31 
32     myiterator = iter(classmate) #它返回的是MyIterator 的对象 ,它是个迭代器
33     #判断myiterator 是否是迭代器
34     # print(isinstance(myiterator,Iterator)) true  #迭代器要满足iter() 和next() 都有

理论上,此时已经可以运行,但是,迭代器中的next 中还需要一些处理:

 1 from collections import Iterable,Iterator
 2 import time
 3 
 4 class ClassMate:
 5     def __init__(self):
 6         self.names = list()
 7 
 8     def add(self,name):
 9         self.names.append(name)
10 
11     def __iter__(self):
12         # pass  #必须要返回一个具有 iter 和 next 方法的对象
13         return MyIterator()
14 
15 
16 class MyIterator:
17     def __iter__(self):
18         pass
19     def __next__(self):
20         return 11
21 
22 
23 
24 if __name__ == '__main__':
25     classmate = ClassMate()
26     classmate.add("tom")
27     classmate.add("jane")
28     classmate.add("egon")
29 
30     for name in classmate:
31         print(name)
32         time.sleep(1)  #这时的输出是 每1s 打印一遍11
33         

每秒打印11  ,也验证了上面的说法,for name in classmate 时,

首先判断classmate 是否可迭代(__iter__())

继续,判断classmate 中的__iter__() 的返回值是否是个迭代器(对象有 __iter__() __next__())

最后,得到的name 就是 迭代器对象中的__next__() 方法的返回值 !

继续改进

 1 from collections import Iterable,Iterator
 2 import time
 3 
 4 class ClassMate:
 5     def __init__(self):
 6         self.names = list()
 7 
 8     def add(self,name):
 9         self.names.append(name)
10 
11     def __iter__(self):
12         # pass  #必须要返回一个具有 iter 和 next 方法的对象
13         return MyIterator(self.names)
14 
15 
16 class MyIterator:
17     def __init__(self,args):
18         self.args = args
19         self.current_num = 0
20     def __iter__(self):
21         pass
22     def __next__(self):
23         if self.current_num<len(self.args):
24             ret = self.args[self.current_num]
25             self.current_num +=1
26             return ret
27         else:
28             raise StopIteration  #结束for 循环  
29 
30 if __name__ == '__main__':
31     classmate = ClassMate()
32     classmate.add("tom")
33     classmate.add("jane")
34     classmate.add("egon")
35 
36     for name in classmate:
37         print(name)
38         time.sleep(1)

不过这时的问题是:有个多余的类,所以我们考虑在一个类里就搞定事情:

最终如下:

from collections import Iterable,Iterator
import time

class ClassMate:
    def __init__(self):
        self.names = list()
        self.current_num = 0

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

    def __iter__(self):
        # pass  #必须要返回一个具有 iter 和 next 方法的对象
        return self   #返回的是个具有 next 和iter 的迭代器

    def __next__(self):
        if self.current_num<len(self.names):
            ret = self.names[self.current_num]
            self.current_num +=1
            return ret  #它就是for 循环的name
        else:
            raise StopIteration  #for in  结构会默认处理这个异常

if __name__ == '__main__':
    classmate = ClassMate()
    classmate.add("tom")
    classmate.add("jane")
    classmate.add("egon")

    for name in classmate:
        time.sleep(1)
        print(name)

'''
    这也说明了一点:
        一个可以迭代的对象 不一定  是个迭代器
        一个迭代器  一定  是可迭代的对象  
'''

迭代器的应用:

迭代器的优点:

占用极小的内存空间,它存储的是生成数据的方式,而不是真实的数据本身!

斐波那契数列案例:

第一种:用列表放这个数列:

 1 nums = list()
 2 a = 0
 3 b = 1
 4 
 5 for _ in range(10):
 6     nums.append(a)
 7     a,b = b,a+b
 8 
 9 for num in nums:
10     print(num)

第二种:用迭代器放这个数列:(占用很小的内存)

 1 class Fib:
 2     def __init__(self,all_num):
 3         self.current_num = 0
 4         self.all_num = all_num
 5         self.a = 0
 6         self.b = 1
 7 
 8     def __iter__(self):
 9         return self
10 
11     def __next__(self):
12         if self.current_num <self.all_num:
13             ret = self.a
14 
15             self.a ,self.b = self.b ,self.a +self.b
16 
17             self.current_num +=1
18             return ret
19         else:
20             raise StopIteration
21 if __name__ == '__main__':
22     fib =Fib(10)
23 
24     for i in fib:
25         print(i)

并不是只有for循环能接收可迭代对象

除了for循环能接收可迭代对象,list、tuple等也能接收。

它们并不是进行简单的类型转换,

例如,列表->  tuple   

它先是创建一个元组,然后利用迭代器取出每个值,然后放入新元组中!

1 def test():
2     li = list([1,2,3,4])
3     tu = tuple(li)
4     

生成器:

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

创建生成器方法1:

第一种方法很简单,只要把一个列表生成式的 [ ] 改成 ( )

创建生成器方法2:

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

使用函数生成器实现斐波那契数列:

 1 def fib(sum_num):
 2     a,b = 0,1
 3     current_num  = 0
 4     while current_num <sum_num:
 5         yield a   #如果一个函数中有了yield 语句,那么函数就变成了生成器
 6         a,b = b,a+b
 7         current_num += 1
 8 
 9 if __name__ == '__main__':
10     ret = fib(10)
11     print(ret) #<generator object fib at 0x0000027E4705E4C0>
12 
13     # for i in ret:
14     #     print(i)
15 
16     # print(ret.__next__())  #next(ret)
17     # print(ret.__next__())  #next(ret)

生成器扩展:

此时的函数可以直接当做是个模板(像类一样),

可以创建多个生成器对象:

 1 def fib(sum_num):
 2     a,b = 0,1
 3     current_num  = 0
 4     while current_num <sum_num:
 5         yield a   #如果一个函数中有了yield 语句,那么函数就变成了生成器
 6         a,b = b,a+b
 7         current_num += 1
 8 
 9 if __name__ == '__main__':
10     ret = fib(10)
11     ret2 = fib(10)
12 
13     print(ret.__next__())
14     print(ret2.__next__())

捕获StopIteration 异常以及获取return 返回的内容:

 1 def fib(sum_num):
 2     a, b = 0, 1
 3     current_num = 0
 4     while current_num < sum_num:
 5         yield a  # 如果一个函数中有了yield 语句,那么函数就变成了生成器
 6         a, b = b, a + b
 7         current_num += 1
 8     return "ok..."
 9 
10 
11 if __name__ == '__main__':
12     obj = fib(10)
13 
14     while True:
15         try:
16             ret = obj.__next__()
17             print(ret)
18         except Exception as e:
19             print(e.value)  #通过捕获异常来获取迭代器的返回的内容。
20             break
21     '''
22     输出:
23         0
24         1
25         1
26         2
27         3
28         5
29         8
30         13
31         21
32         34
33         ok...
34     '''

生成器--之send方式:

上面的是用next/__next__()  来产生下个值,用send也可以 ,send 可以向里面传入参数

 1 def fib(sum_num):
 2     a, b = 0, 1
 3     current_num = 0
 4     while current_num < sum_num:
 5         test = yield a  # 如果一个函数中有了yield 语句,那么函数就变成了生成器
 6         print(test)
 7         a, b = b, a + b
 8         current_num += 1
 9     return "ok..."
10 
11 
12 if __name__ == '__main__':
13     obj = fib(10)
14 
15     ret = obj.__next__()
16     print(ret)
17 
18     ret = obj.send("hahahaha")
19     print(ret)

注意:如果生成器  第一次启动时,如果用send() 的话,send 只能传入None 参数,不能传入非None 参数,从第二次开始,就可以随便传入了。

一般来说第一次都是用next ,需要向里面传值的时候才会使用send  .

注:c.next()等价c.send(None)

yield 和return 的区别:

yield 可以暂停,然后后面继续执行它。 return 是直接结束!

协程-yield(使用yield 完成多任务):

 1 import time
 2 def task_1():
 3     while True:
 4         print("====1===")
 5         time.sleep(0.1)
 6         yield
 7 
 8 def task_2():
 9     while True:
10         print("====2===")
11         time.sleep(0.1)
12         yield
13 
14 if __name__ == '__main__':
15     t1 = task_1()  #得到是对象
16     t2 = task_2()
17     while True:
18         t1.__next__()
19         t2.__next__()

不过,这个多任务是假的多任务,它是并发,而不是真正的并行! 

它其实是一个进程中的一个线程在工作。

协程-greenlet 和 gevent :

greenlet :  

from greenlet import greenlet  # 使用greenlet 中的switch 就可以完成切换任务。
import time

def test01():
    while True:
        print("====1====")
        grlet2.switch()
        time.sleep(0.1)

def test02():
    while True:
        print("====2====")
        grlet1.switch()
        time.sleep(0.1)


if __name__ == '__main__':
    grlet1 = greenlet(test01)
    grlet2 = greenlet(test02)

    grlet1.switch()   
#它的效果和上面的效果一样!

gevent:

greenlet 是对yield 的封装, gevent 是对greenlet 的封装!

它还是用的最多的,主要是它遇到阻塞(time.sleep())切换!

greenlet已经实现了协程,但是这个还的人工切换,是不是觉得太麻烦了,不要捉急,python还有一个比greenlet更强大的并且能够自动切换任务的模块gevent

其原理是当一个greenlet遇到IO(指的是input output 输入输出,比如网络、文件操作等)操作时,比如访问网络,就自动切换到其他的greenlet,等到IO操作完成,再在适当的时候切换回来继续执行。

由于IO操作非常耗时,经常使程序处于等待状态,有了gevent为我们自动切换协程,就保证总有greenlet在运行,而不是等待IO

没有阻塞的时候(不切换):

 1 import gevent
 2 
 3 
 4 def f1(n):
 5     for i in range(n):
 6         print(gevent.getcurrent(),i)
 7 
 8 def f2(n):
 9     for i in range(n):
10         print(gevent.getcurrent(),i)
11 
12 def f3(n):
13     for i in range(n):
14         print(gevent.getcurrent(),i)
15 
16 if __name__ == '__main__':
17     print("=======1=========")
18     g1 = gevent.spawn(f1,5)    #此时不会执行f1
19     print("=======2=========")
20     g2 = gevent.spawn(f2,5)
21     print("=======3=========")
22     g3 = gevent.spawn(f3,5)
23     print("=======4=========")
24 
25     g1.join()
26     g2.join()
27     g3.join()
28     '''
29     输出:
30         =======1=========
31         =======2=========
32         =======3=========
33         =======4=========
34         <Greenlet at 0x36f0c90: f1(5)> 0
35         <Greenlet at 0x36f0c90: f1(5)> 1
36         <Greenlet at 0x36f0c90: f1(5)> 2
37         <Greenlet at 0x36f0c90: f1(5)> 3
38         <Greenlet at 0x36f0c90: f1(5)> 4
39         <Greenlet at 0x397c270: f2(5)> 0
40         <Greenlet at 0x397c270: f2(5)> 1
41         <Greenlet at 0x397c270: f2(5)> 2
42         <Greenlet at 0x397c270: f2(5)> 3
43         <Greenlet at 0x397c270: f2(5)> 4
44         <Greenlet at 0x397c300: f3(5)> 0
45         <Greenlet at 0x397c300: f3(5)> 1
46         <Greenlet at 0x397c300: f3(5)> 2
47         <Greenlet at 0x397c300: f3(5)> 3
48         <Greenlet at 0x397c300: f3(5)> 4
49     '''

有阻塞的时候(切换):

 1 import gevent
 2 import time
 3 
 4 def f1(n):
 5     for i in range(n):
 6         print(gevent.getcurrent(),i)
 7         # time.sleep(0.5) #这种阻塞不行,需要专门的gevent.sleep()
 8         gevent.sleep(0.5)
 9 
10 def f2(n):
11     for i in range(n):
12         print(gevent.getcurrent(),i)
13         gevent.sleep(0.5)
14 
15 def f3(n):
16     for i in range(n):
17         print(gevent.getcurrent(),i)
18         gevent.sleep(0.5)
19 
20 if __name__ == '__main__':
21     print("=======1=========")
22     g1 = gevent.spawn(f1,5)    #此时不会执行f1
23     print("=======2=========")
24     g2 = gevent.spawn(f2,5)
25     print("=======3=========")
26     g3 = gevent.spawn(f3,5)
27     print("=======4=========")
28 
29     g1.join()
30     g2.join()
31     g3.join()
32     '''
33     输出:
34         =======1=========
35         =======2=========
36         =======3=========
37         =======4=========
38         <Greenlet at 0x3bafdb0: f1(5)> 0
39         <Greenlet at 0x3cea270: f2(5)> 0
40         <Greenlet at 0x3cea300: f3(5)> 0
41         <Greenlet at 0x3bafdb0: f1(5)> 1
42         <Greenlet at 0x3cea270: f2(5)> 1
43         <Greenlet at 0x3cea300: f3(5)> 1
44         <Greenlet at 0x3bafdb0: f1(5)> 2
45         <Greenlet at 0x3cea270: f2(5)> 2
46         <Greenlet at 0x3cea300: f3(5)> 2
47         <Greenlet at 0x3bafdb0: f1(5)> 3
48         <Greenlet at 0x3cea270: f2(5)> 3
49         <Greenlet at 0x3cea300: f3(5)> 3
50         <Greenlet at 0x3bafdb0: f1(5)> 4
51         <Greenlet at 0x3cea270: f2(5)> 4
52         <Greenlet at 0x3cea300: f3(5)> 4
53     '''

它主要的思想是利用当一个任务在浪费时间的时候,将这个时间给利用上!

这就是协程!

协程依赖线程!协程使用的资源是最小的!

:不仅仅time.sleep() 阻塞要用gevent 中的专门函数,对于网络编程中的recv   accept 等阻塞也要换!但是如果对于已经有现成项目的代码,已经用了很多的time.sleep() ,如果每个都要改就麻烦,

下面的方法可以不用改为gevent ():

打个补丁:

monkey.patch_all()   # 要导入  from gevent import monkey  

from gevent import monkey
import gevent
import time

monkey.patch_all()

def f1(n):
    for i in range(n):
        print(gevent.getcurrent(),i)
        time.sleep(0.5)

def f2(n):
    for i in range(n):
        print(gevent.getcurrent(),i)
        time.sleep(0.5)

def f3(n):
    for i in range(n):
        print(gevent.getcurrent(),i)
        time.sleep(0.5)

if __name__ == '__main__':
    print("=======1=========")
    g1 = gevent.spawn(f1,5)    #此时不会执行f1
    print("=======2=========")
    g2 = gevent.spawn(f2,5)
    print("=======3=========")
    g3 = gevent.spawn(f3,5)
    print("=======4=========")

    g1.join()
    g2.join()
    g3.join()
    '''
    输出:
        =======1=========
        =======2=========
        =======3=========
        =======4=========
        <Greenlet at 0x3bafdb0: f1(5)> 0
        <Greenlet at 0x3cea270: f2(5)> 0
        <Greenlet at 0x3cea300: f3(5)> 0
        <Greenlet at 0x3bafdb0: f1(5)> 1
        <Greenlet at 0x3cea270: f2(5)> 1
        <Greenlet at 0x3cea300: f3(5)> 1
        <Greenlet at 0x3bafdb0: f1(5)> 2
        <Greenlet at 0x3cea270: f2(5)> 2
        <Greenlet at 0x3cea300: f3(5)> 2
        <Greenlet at 0x3bafdb0: f1(5)> 3
        <Greenlet at 0x3cea270: f2(5)> 3
        <Greenlet at 0x3cea300: f3(5)> 3
        <Greenlet at 0x3bafdb0: f1(5)> 4
        <Greenlet at 0x3cea270: f2(5)> 4
        <Greenlet at 0x3cea300: f3(5)> 4
    '''

gevent.joinall([ ])  的写法:

gevent 的标准用法模板:

 1 from gevent import monkey
 2 import gevent
 3 import time
 4 
 5 monkey.patch_all()  #  它会自动的将所有代码中的 要阻塞的函数换成gevent 专门的函数
 6 
 7 def f1(n):
 8     for i in range(n):
 9         print(gevent.getcurrent(),i)
10         time.sleep(0.5)
11 
12 def f2(n):
13     for i in range(n):
14         print(gevent.getcurrent(),i)
15         time.sleep(0.5)
16 
17 def f3(n):
18     for i in range(n):
19         print(gevent.getcurrent(),i)
20         time.sleep(0.5)
21 
22 
23 if __name__ == '__main__':
24     gevent.joinall([   #joinall 的写法,将所有的对象放到一个数组中!  方便
25         gevent.spawn(f1, 5),
26         gevent.spawn(f2, 5),
27         gevent.spawn(f3, 5)
28     ])
29 
30     '''
31     输出:
32         <Greenlet at 0x3bafdb0: f1(5)> 0
33         <Greenlet at 0x3cea270: f2(5)> 0
34         <Greenlet at 0x3cea300: f3(5)> 0
35         <Greenlet at 0x3bafdb0: f1(5)> 1
36         <Greenlet at 0x3cea270: f2(5)> 1
37         <Greenlet at 0x3cea300: f3(5)> 1
38         <Greenlet at 0x3bafdb0: f1(5)> 2
39         <Greenlet at 0x3cea270: f2(5)> 2
40         <Greenlet at 0x3cea300: f3(5)> 2
41         <Greenlet at 0x3bafdb0: f1(5)> 3
42         <Greenlet at 0x3cea270: f2(5)> 3
43         <Greenlet at 0x3cea300: f3(5)> 3
44         <Greenlet at 0x3bafdb0: f1(5)> 4
45         <Greenlet at 0x3cea270: f2(5)> 4
46         <Greenlet at 0x3cea300: f3(5)> 4
47     '''

进程,线程,协程区别:

通俗描述

  • 有一个老板想要开个工厂进行生产某件商品(例如剪子)
  • 他需要花一些财力物力制作一条生产线,这个生产线上有很多的器件以及材料这些所有的 为了能够生产剪子而准备的资源称之为:进程
  • 只有生产线是不能够进行生产的,所以老板的找个工人来进行生产,这个工人能够利用这些材料最终一步步的将剪子做出来,这个来做事情的工人称之为:线程
  • 这个老板为了提高生产率,想到3种办法:
    1. 在这条生产线上多招些工人,一起来做剪子,这样效率是成倍増长,即单进程 多线程方式
    2. 老板发现这条生产线上的工人不是越多越好,因为一条生产线的资源以及材料毕竟有限,所以老板又花了些财力物力购置了另外一条生产线,然后再招些工人这样效率又再一步提高了,即多进程 多线程方式
    3. 老板发现,现在已经有了很多条生产线,并且每条生产线上已经有很多工人了(即程序是多进程的,每个进程中又有多个线程),为了再次提高效率,老板想了个损招,规定:如果某个员工在上班时临时没事或者再等待某些条件(比如等待另一个工人生产完谋道工序 之后他才能再次工作) ,那么这个员工就利用这个时间去做其它的事情,那么也就是说:如果一个线程等待某些条件,可以充分利用这个时间去做其它事情,其实这就是:协程方式

简单总结

  1. 进程是资源分配的单位
  2. 线程是操作系统调度的单位
  3. 进程切换需要的资源很最大,效率很低
  4. 线程切换需要的资源一般,效率一般(当然了在不考虑GIL的情况下)
  5. 协程切换任务资源很小,效率高
  6. 多进程、多线程根据cpu核数不一样可能是并行的,但是协程是在一个线程中 所以是并发

效率最高的是协程!

案例:并发下载器:

通过程序上网,可以通过 import  urllib.request ,使用里面的urlopen() 来打开url 并获取结果! 

一次爬取一张图片

 1 import urllib.request
 2 
 3 def main():
 4     req = urllib.request.urlopen("https://img02.sogoucdn.com/v2/thumb/resize/w/120/h/135/zi/on/iw/90.0/ih/101.0?t=2&url=http%3A%2F%2Fpic.baike.soso.com%2Fugc%2Fbaikepic2%2F19951%2Fcut-20190523135122-1670915825_jpg_298_373_11703.jpg%2F300&appid=200524&referer=http%3A%2F%2Fbaike.sogou.com%2Fv6234.htm%3FfromTitle%3D%25E7%2599%25BE%25E5%25BA%25A6")
 5 
 6     img_content = req.read()
 7     with open("e:/1.jpg","wb") as f:
 8         f.write(img_content)
 9 
10 if __name__ == '__main__':
11     main()

因为网络下载是个阻塞的过程,所以可以使用gevent 利用协程来下载!

 1 import urllib.request
 2 import gevent
 3 from gevent import monkey
 4 import time
 5 
 6 monkey.patch_all()
 7 
 8 
 9 
10 def downloader(img_url):
11     req = urllib.request.urlopen(img_url)
12 
13     img_content = req.read()
14     timestamp =time.time()   #时间戳 
15     with open("e:/{}.jpg".format(timestamp),"wb") as f:
16         f.write(img_content)
17 
18 def main():
19     gevent.joinall([
20         gevent.spawn(downloader,"http://img0.dili360.com//ga/M02/49/A2/wKgBy1nPDXGAWgvpAADIK-c2-PQ056.jpg"),
21         gevent.spawn(downloader, "http://img0.dili360.com/ga/M01/02/64/wKgBy1Q22liAYjlPAA0ov-3Wnvs953.jpg@!rw9")
22     ])
23 
24 
25 if __name__ == '__main__':
26     main()

补:

这上面说的是迭代器,生成器,以后还会说装饰器,先看一个装饰器的小例子:

import time
def decorator(func):
    def wrapper(name):
        t1=time.perf_counter()
        func(name)
        time.sleep(1)
        print("总时间为:",time.perf_counter() - t1)

    return wrapper


@decorator
def test(name):
    print("Hello World!",name)

if __name__ == '__main__':
    test("tom")
View Code

它们三个(迭代器,生成器和装饰器)是Python 高级语法中重要的内容!

原文地址:https://www.cnblogs.com/zach0812/p/11443985.html