关于python:Python基础|一文讲透-Python-协程

32次阅读

共计 11552 个字符,预计需要花费 29 分钟才能阅读完成。

迭代

1.1 迭代的概念

应用 for 循环遍历取值的 过程 叫做迭代,比方:应用 for 循环遍历列表获取值的过程

for value in [2, 3, 4]:
    print(value)

1.2 可迭代对象

规范概念:在类外面定义 __iter__ 办法,并应用该类创立的对象就是可迭代对象

简略记忆:应用 for 循环遍历取值的 对象 叫做可迭代对象, 比方:列表、元组、字典、汇合、range、字符串

1.3 判断对象是否是可迭代对象

from collections import Iterable

result = isinstance((3, 5), Iterable)
print("元组是否是可迭代对象:", result)

result = isinstance([3, 5], Iterable)
print("列表是否是可迭代对象:", result)

result = isinstance({"name": "张三"}, Iterable)
print("字典是否是可迭代对象:", result)

result = isinstance("hello", Iterable)
print("字符串是否是可迭代对象:", result)

result = isinstance({3, 5}, Iterable)
print("汇合是否是可迭代对象:", result)

result = isinstance(range(5), Iterable)
print("range 是否是可迭代对象:", result)

result = isinstance(5, Iterable)
print("整数是否是可迭代对象:", result)


result = isinstance(5, int)
print("整数是否是 int 类型对象:", result)

class Student(object):
    pass

stu = Student()
result = isinstance(stu, Iterable)

print("stu 是否是可迭代对象:", result)

result = isinstance(stu, Student)

print("stu 是否是 Student 类型的对象:", result)

1.4 自定义可迭代对象

在类中实现 __iter__ 办法

自定义可迭代类型代码

from collections import Iterable
class MyList(object):

    def __init__(self):
        self.my_list = list()

    
    def append_item(self, item):
        self.my_list.append(item)

    def __iter__(self):
        
        pass

my_list = MyList()
my_list.append_item(1)
my_list.append_item(2)
result = isinstance(my_list, Iterable)

print(result)

for value in my_list:
    print(value)

执行后果:

Traceback (most recent call last):
True
  File "/Users/hbin/Desktop/untitled/aa.py", line 24, in <module>
    for value in my_list:
TypeError: iter() returned non-iterator of type 'NoneType'

通过执行后果能够看进去,遍历可迭代对象顺次获取数据须要迭代器

总结

在类外面提供一个 __iter__ 创立的对象是可迭代对象,可迭代对象是须要迭代器实现数据迭代的

2、迭代器

2.1 自定义迭代器对象

自定义迭代器对象: 在类外面定义 __iter____next__办法创立的对象就是迭代器对象

from collections import Iterable
from collections import Iterator

class MyList(object):

    def __init__(self):
        self.my_list = list()
    
    def append_item(self, item):
        self.my_list.append(item)

    def __iter__(self):     
        my_iterator = MyIterator(self.my_list)
        return my_iterator

class MyIterator(object):

    def __init__(self, my_list):
        self.my_list = my_list  
        self.current_index = 0
        result = isinstance(self, Iterator)
        print("MyIterator 创立的对象是否是迭代器:", result)

    def __iter__(self):
        return self

    def __next__(self):
        if self.current_index < len(self.my_list):
            self.current_index += 1
            return self.my_list[self.current_index - 1]
        else:           
            raise StopIteration

my_list = MyList()
my_list.append_item(1)
my_list.append_item(2)
result = isinstance(my_list, Iterable)
print(result)

for value in my_list:
    print(value)

运行后果:

True
MyIterator 创立的对象是否是迭代器: True
1
2

2.2 iter()函数与 next()函数

iter 函数 : 获取可迭代对象的迭代器,会调用可迭代对象身上的__iter__ 办法

next 函数 : 获取迭代器中下一个值,会调用迭代器对象身上的__next__ 办法

class MyList(object):

    def __init__(self):
        self.my_list = list()
    
    def append_item(self, item):
        self.my_list.append(item)

    def __iter__(self):      
        my_iterator = MyIterator(self.my_list)
        return my_iterator

class MyIterator(object):

    def __init__(self, my_list):
        self.my_list = my_list
        self.current_index = 0

    def __iter__(self):
        return self
    
    def __next__(self):
        if self.current_index < len(self.my_list):
            self.current_index += 1
            return self.my_list[self.current_index - 1]
        else:           
            raise StopIteration

my_list = MyList()
my_list.append_item(1)
my_list.append_item(2)
my_iterator = iter(my_list)
print(my_iterator)

while True:
    try:
        value = next(my_iterator)
        print(value)
    except StopIteration as e:
        break

2.3 for 循环的实质

遍历的是可迭代对象

for item in Iterable 循环的实质就是先通过 iter()函数获取可迭代对象 Iterable 的迭代器,而后对获取到的迭代器一直调用 next()办法来获取下一个值并将其赋值给 item,当遇到 StopIteration 的异样后循环完结。

遍历的是迭代器

for item in Iterator 循环的迭代器,一直调用 next()办法来获取下一个值并将其赋值给 item,当遇到 StopIteration 的异样后循环完结。

2.4 迭代器的利用场景

咱们发现迭代器最外围的性能就是能够通过 next()函数的调用来返回下一个数据值。如果每次返回的数据值不是在一个已有的数据汇合中读取的,而是通过程序依照肯定的法则计算生成的,那么也就意味着能够不必再依赖一个已有的数据汇合,也就是说不必再将所有要迭代的数据都一次性缓存下来供后续顺次读取,这样能够节俭大量的存储(内存)空间。

举个例子,比方,数学中有个驰名的斐波拉契数列(Fibonacci),数列中第一个数为 0,第二个数为 1,其后的每一个数都可由前两个数相加失去:

0, 1, 1, 2, 3, 5, 8, 13, 21, 34, …

当初咱们想要通过 for…in… 循环来遍历迭代斐波那契数列中的前 n 个数。那么这个斐波那契数列咱们就能够用迭代器来实现,每次迭代都通过数学计算来生成下一个数。

class Fibonacci(object):

    def __init__(self, num):   
        self.num = num   
        self.a = 0
        self.b = 1   
        self.current_index = 0

    def __iter__(self):
        return self

    def __next__(self):
        if self.current_index < self.num:
            result = self.a
            self.a, self.b = self.b, self.a + self.b
            self.current_index += 1
            return result
        else:
            raise StopIteration

fib = Fibonacci(5)

for value in fib:
    print(value)

执行后果:

0
1
1
2
3

小结

迭代器的作用就是是记录以后数据的地位以便获取下一个地位的值

3、生成器

3.1 生成器的概念

生成器是一类非凡的迭代器, 它不须要再像下面的类一样写 __iter__() 和__next__()办法了, 应用更加不便, 它仍然能够应用 next 函数和 for 循环取值

3.2 创立生成器办法 1

第一种办法很简略,只有把一个列表生成式的 [] 改成 ()

my_list = [i * 2 for i in range(5)]
print(my_list)

my_generator = (i * 2 for i in range(5))
print(my_generator)

for value in my_generator:
    print(value)

执行后果:

[0, 2, 4, 6, 8]
<generator object <genexpr> at 0x101367048>
0
2
4
6
8

3.3 创立生成器办法 2

在 def 函数外面看到有 yield 关键字那么就是生成器

def fibonacci(num):
    a = 0
    b = 1
    
    current_index = 0
    print("--11---")
    while current_index < num:
        result = a
        a, b = b, a + b
        current_index += 1
        print("--22---")
        
        yield result
        print("--33---")

fib = fibonacci(5)
value = next(fib)
print(value)
value = next(fib)
print(value)

value = next(fib)
print(value)

在应用生成器实现的形式中,咱们将本来在迭代器 __next__ 办法中实现的根本逻辑放到一个函数中来实现,然而将每次迭代返回数值的 return 换成了 yield,此时新定义的函数便不再是函数,而是一个 生成器 了。

简略来说:只有在 def 中有 yield 关键字的 就称为 生成器

3.4 生成器应用 return 关键字

def fibonacci(num):
    a = 0
    b = 1
    
    current_index = 0
    print("--11---")
    while current_index < num:
        result = a
        a, b = b, a + b
        current_index += 1
        print("--22---")
        
        yield result
        print("--33---")
        return "嘻嘻"

fib = fibonacci(5)
value = next(fib)
print(value)

try:
    value = next(fib)
    print(value)
except StopIteration as e:
    
    print(e.value)

提醒:

生成器外面应用 return 关键字语法上没有问题,然而代码执行到 return 语句会进行迭代,抛出进行迭代异样。

3.5 yield 和 return 的比照

应用了 yield 关键字的函数不再是函数,而是生成器。(应用了 yield 的函数就是生成器)

代码执行到 yield 会暂停,而后把后果返回进来,下次启动生成器会在暂停的地位持续往下执行

每次启动生成器都会返回一个值,屡次启动能够返回多个值,也就是 yield 能够返回多个值

return 只能返回一次值,代码执行到 return 语句就进行迭代, 抛出进行迭代异样

3.6 应用 send 办法启动生成器并传参

send 办法启动生成器的时候能够传参数

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

执行后果:

In [43]: f = gen()

In [44]: next(f)
Out[44]: 0

In [45]: f.send('haha')
haha
Out[45]: 1

In [46]: next(f)
None
Out[46]: 2

In [47]: f.send('haha')
haha
Out[47]: 3

In [48]:

留神: 如果第一次启动生成器应用 send 办法,那么参数只能传入 None, 个别第一次启动生成器应用 next 函数

小结

  • 生成器创立有两种形式,个别都应用 yield 关键字办法创立生成器
  • yield 特点是代码执行到 yield 会暂停,把后果返回进来,再次启动生成器在暂停的地位持续往下执行

4、协程

4.1 协程的概念

协程,又称微线程,纤程, 也称为用户级线程,在不开拓线程的根底上实现多任务,也就是在单线程的状况下实现多任务,多个工作依照肯定程序交替执行 艰深了解只有在 def 外面只看到一个 yield 关键字示意就是协程

协程是也是实现多任务的一种形式

协程 yield 的代码实现

简略实现协程

import time

def work1():
    while True:
        print("----work1---")
        yield
        time.sleep(0.5)

def work2():
    while True:
        print("----work2---")
        yield
        time.sleep(0.5)

def main():
    w1 = work1()
    w2 = work2()
    while True:
        next(w1)
        next(w2)

if __name__ == "__main__":
    main()

运行后果:

----work1---
----work2---
----work1---
----work2---
----work1---
----work2---
----work1---
----work2---
----work1---
----work2---
----work1---
----work2---
... 省略...

小结

协程之间执行工作依照肯定程序交替执行

5、greenlet

5.1 greentlet 的介绍

为了更好应用协程来实现多任务,python 中的 greenlet 模块对其封装,从而使得切换工作变的更加简略

应用如下命令装置 greenlet 模块:

pip3 install greenlet

应用协程实现多任务

import time
import greenlet

# 工作 1
def work1():
    for i in range(5):
        print("work1...")
        time.sleep(0.2)
        # 切换到协程 2 外面执行对应的工作
        g2.switch()

# 工作 2
def work2():
    for i in range(5):
        print("work2...")
        time.sleep(0.2)
        # 切换到第一个协程执行对应的工作
        g1.switch()

if __name__ == '__main__':
    # 创立协程指定对应的工作
    g1 = greenlet.greenlet(work1)
    g2 = greenlet.greenlet(work2)

    # 切换到第一个协程执行对应的工作
    g1.switch()

运行成果

work1...
work2...
work1...
work2...
work1...
work2...
work1...
work2...
work1...
work2...

6、gevent

6.1 gevent 的介绍

greenlet 曾经实现了协程,然而这个还要人工切换,这里介绍一个比 greenlet 更弱小而且可能主动切换工作的第三方库,那就是 gevent。

gevent 外部封装的 greenlet,其原理是当一个 greenlet 遇到 IO(指的是 input output 输入输出,比方网络、文件操作等)操作时,比方拜访网络,就主动切换到其余的 greenlet,等到 IO 操作实现,再在适当的时候切换回来继续执行。

因为 IO 操作十分耗时,常常使程序处于期待状态,有了 gevent 为咱们主动切换协程,就保障总有 greenlet 在运行,而不是期待 IO

装置

pip3 install gevent

6.2 gevent 的应用

import gevent

def work(n):
    for i in range(n):
        
        print(gevent.getcurrent(), i)

g1 = gevent.spawn(work, 5)
g2 = gevent.spawn(work, 5)
g3 = gevent.spawn(work, 5)
g1.join()
g2.join()
g3.join()

运行后果

<Greenlet "Greenlet-0" at 0x26d8c970488: work(5)> 0
<Greenlet "Greenlet-1" at 0x26d8c970598: work(5)> 0
<Greenlet "Greenlet-2" at 0x26d8c9706a8: work(5)> 0
<Greenlet "Greenlet-0" at 0x26d8c970488: work(5)> 1
<Greenlet "Greenlet-1" at 0x26d8c970598: work(5)> 1
<Greenlet "Greenlet-2" at 0x26d8c9706a8: work(5)> 1
<Greenlet "Greenlet-0" at 0x26d8c970488: work(5)> 2
<Greenlet "Greenlet-1" at 0x26d8c970598: work(5)> 2
<Greenlet "Greenlet-2" at 0x26d8c9706a8: work(5)> 2
<Greenlet "Greenlet-0" at 0x26d8c970488: work(5)> 3
<Greenlet "Greenlet-1" at 0x26d8c970598: work(5)> 3
<Greenlet "Greenlet-2" at 0x26d8c9706a8: work(5)> 3
<Greenlet "Greenlet-0" at 0x26d8c970488: work(5)> 4
<Greenlet "Greenlet-1" at 0x26d8c970598: work(5)> 4
<Greenlet "Greenlet-2" at 0x26d8c9706a8: work(5)> 4

能够看到,3 个 greenlet 是顺次运行而不是交替运行

6.3 gevent 切换执行

import gevent

def work(n):
    for i in range(n):
        
        print(gevent.getcurrent(), i)
        
        gevent.sleep(1)

g1 = gevent.spawn(work, 5)
g2 = gevent.spawn(work, 5)
g3 = gevent.spawn(work, 5)
g1.join()
g2.join()
g3.join()

运行后果

<Greenlet at 0x7fa70ffa1c30: f(5)> 0
<Greenlet at 0x7fa70ffa1870: f(5)> 0
<Greenlet at 0x7fa70ffa1eb0: f(5)> 0
<Greenlet at 0x7fa70ffa1c30: f(5)> 1
<Greenlet at 0x7fa70ffa1870: f(5)> 1
<Greenlet at 0x7fa70ffa1eb0: f(5)> 1
<Greenlet at 0x7fa70ffa1c30: f(5)> 2
<Greenlet at 0x7fa70ffa1870: f(5)> 2
<Greenlet at 0x7fa70ffa1eb0: f(5)> 2
<Greenlet at 0x7fa70ffa1c30: f(5)> 3
<Greenlet at 0x7fa70ffa1870: f(5)> 3
<Greenlet at 0x7fa70ffa1eb0: f(5)> 3
<Greenlet at 0x7fa70ffa1c30: f(5)> 4
<Greenlet at 0x7fa70ffa1870: f(5)> 4
<Greenlet at 0x7fa70ffa1eb0: f(5)> 4

6.4 给程序打补丁

import gevent
import time
from gevent import monkey

monkey.patch_all()

def work1(num):
    for i in range(num):
        print("work1....")
        time.sleep(0.2)

def work2(num):
    for i in range(num):
        print("work2....")
        time.sleep(0.2)
        
if __name__ == '__main__':
    
    g1 = gevent.spawn(work1, 3)
    g2 = gevent.spawn(work2, 3) 
    g1.join()
    g2.join()

运行后果

work1....
work2....
work1....
work2....
work1....
work2....

6.5 留神

以后程序是一个死循环并且还能有耗时操作,就不须要加上 join 办法了, 因为程序须要始终运行不会退出

示例代码

import gevent
import time
from gevent import monkey

monkey.patch_all()

def work1(num):
    for i in range(num):
        print("work1....")
        time.sleep(0.2)
        
def work2(num):
    for i in range(num):
        print("work2....")
        time.sleep(0.2)
        
if __name__ == '__main__':
    
    g1 = gevent.spawn(work1, 3)
    g2 = gevent.spawn(work2, 3)

    while True:
        print("主线程中执行")
        time.sleep(0.5)

执行后果:

主线程中执行
work1....
work2....
work1....
work2....
work1....
work2....
主线程中执行主线程中执行主线程中执行
.. 省略..

如果应用的协程过多,如果想启动它们就须要一个一个的去应用 join()办法去阻塞主线程,这样代码会过于冗余,能够应用 gevent.joinall()办法启动须要应用的协程

实例代码

 import time
import gevent

def work1():
    for i in range(5):
        print("work1 工作了{}".format(i))
        gevent.sleep(1)

def work2():
    for i in range(5):
        print("work2 工作了{}".format(i))
        gevent.sleep(1)

if __name__ == '__main__':
    w1 = gevent.spawn(work1)
    w2 = gevent.spawn(work2)
    gevent.joinall([w1, w2])  

7、过程、线程、协程比照

7.1 过程、线程、协程之间的关系

  • 一个过程至多有一个线程,过程外面能够有多个线程
  • 一个线程外面能够有多个协程

7.2 过程、线程、线程的比照

  1. 过程是资源分配的单位
  2. 线程是操作系统调度的单位
  3. 过程切换须要的资源最大,效率很低
  4. 线程切换须要的资源个别,效率个别(当然了在不思考 GIL 的状况下)
  5. 协程切换工作资源很小,效率高
  6. 多过程、多线程依据 cpu 核数不一样可能是并行的,然而协程是在一个线程中 所以是并发

小结

  1.  过程、线程、协程都是能够实现多任务的,能够依据本人理论开发的须要抉择应用

  2. 因为线程、协程须要的资源很少,所以应用线程和协程的几率最大

  3. 开拓协程须要的资源起码

正文完
 0