转载自我自己的 github 博客 ——> 半天钟的博客
这篇博文讲述的 python 协程是 不正式的、宽泛的协程 ,即通过客户调用 .send(…) 方法发送数据或使用 yield from 结构驱动的生成器函数, 而不是 asyncio 库采用的定义更为严格的协程。
前言
在 事件驱动型编程 中,协程常用于离散事件的仿真(在单个线程中使用一个主循环驱动协程执行并发活动)。
协程通过显式 自主地把控制权让步给中央调度程序 从而实现了 协作式多任务 。
所以, 协程是 python 事件驱动型框架和协作式多任务的基础。
那么,弄明白协程的 进化过程 、基本行为和 高效的使用方式 是很有必要的。
本博文想要解释清楚 python 协程的基本行为以及如何高效的使用协程。
在阅读本文之前,你必须要了解 python 中 yield 关键字、和生成器的基本概念。如果你还不知道这两个概念是啥,你可以看我的上一篇博文:浅析 python 迭代器与生成器 或者通过 CSDN 上冯爽朗的博文 简单了解 yield 关键字的使用方法。
从生成器到协程
协程是指一个过程,这个过程与调用方协作,即 根据调用方提供的值 产出相应的值 给调用方。
从协程的定义来看,协程的部分行为和带有 yield 关键字生成器的行为类似,因为调用方可以使用 .next() 方法 让生产器产出值给调用方。例如,这个斐波那契生成器函数:
            
              
                >>
              
              
                >
              
              
                def
              
              
                fibonacci
              
              
                (
              
              
                )
              
              
                :
              
              
        a
              
                ,
              
               b 
              
                =
              
              
                0
              
              
                ,
              
              
                1
              
              
                while
              
              
                True
              
              
                :
              
              
                yield
              
               a
            a
              
                ,
              
               b 
              
                =
              
               b
              
                ,
              
               a 
              
                +
              
               b
            
          
          调用方调用 next()函数可以 获取它的产出值 :
            
              
                >>
              
              
                >
              
               f 
              
                =
              
               fibonacci
              
                (
              
              
                )
              
              
                >>
              
              
                >
              
              
                print
              
              
                (
              
              
                next
              
              
                (
              
              f
              
                )
              
              
                )
              
              
                0
              
              
                >>
              
              
                >
              
              
                print
              
              
                (
              
              
                next
              
              
                (
              
              f
              
                )
              
              
                )
              
              
                1
              
            
          
          这么看来, 生成器的行为离协程的行为就差一步,即接收调用方提供的值。
在 python 2.5 后 yield 关键字就可以在表达式中使用了,而且生成器 API 中增加了 .send(value)方法。 生成器的调用方可以使用 .send(…) 方法给生成器发送数据。
这样一来生成器就可以接收调用方提供的值了, 其接收的数据会成为 yield 表达式的值。
例一是一个简单的例子,来说明调用方如何发送数据及生成器如何接受数据。
            
              
                >>
              
              
                >
              
              
                def
              
              
                coroutine
              
              
                (
              
              
                )
              
              
                :
              
              
                print
              
              
                (
              
              
                '-- 协程开始 --'
              
              
                )
              
              
        x 
              
                =
              
              
                yield
              
              
                'Nothing'
              
              
                print
              
              
                (
              
              
                '-- 协程接收到了数据: {!r} -- '
              
              
                .
              
              
                format
              
              
                (
              
              x
              
                )
              
              
                )
              
              
                >>
              
              
                >
              
               coro 
              
                =
              
               coroutine
              
                (
              
              
                )
              
              
                <
              
              generator 
              
                object
              
               coroutine at 
              
                0x10bbb2408
              
              
                >
              
              
                >>
              
              
                >
              
              
                next
              
              
                (
              
              coro
              
                )
              
              
                -
              
              
                -
              
               协程开始 
              
                -
              
              
                -
              
              
Nothing
              
                >>
              
              
                >
              
               coro
              
                .
              
              send
              
                (
              
              
                77
              
              
                )
              
              
                -
              
              
                -
              
               协程接收到了数据
              
                :
              
              
                77
              
              
                -
              
              
                -
              
              
Traceback 
              
                (
              
              most recent call last
              
                )
              
              
                :
              
              
                .
              
              
                .
              
              
                .
              
              
StopIteration
            
          
          上面的例子表明:
- 在协程中, yield 通常出现在表达式的右边。
 - 调用方先使用一次 .next() 执行 yield ‘Nothing’ 让协程产出字符串 “Nothing” 并 悬停至至 yield 表达式这一行
 - 调用方使用 .send() 发送数据给协程。
 - 发送的 数据代替 yield 表达式 ,并赋给变量 x。
 - 协程结束时与生成器一致,都会抛出 StopIteration 异常。
 
            需要特别注意的地方有:
            
            
              首先、调用方只有在协程停在了 yield 表达式时,才能调用 .send() 发送数据
            
            ,否则,协程会抛出 TypeError 异常,如例二:
          
            
              
                >>
              
              
                >
              
               coro 
              
                =
              
               coroutine
              
                (
              
              
                )
              
              
                >>
              
              
                >
              
               coro
              
                .
              
              send
              
                (
              
              
                77
              
              
                )
              
              
Traceback 
              
                (
              
              most recent call last
              
                )
              
              
                :
              
              
                .
              
              
                .
              
              
                .
              
              
                in
              
               coro
              
                .
              
              send
              
                (
              
              
                77
              
              
                )
              
              
TypeError
              
                :
              
               can't send non
              
                -
              
              
                None
              
               value to a just
              
                -
              
              started generator
            
          
          悬停在 yield 表达式的协程状态是
GEN_SUSPENDED,你可以使用inspect.getgeneratorstate(...)函数确定协程的状态。
其次、调用方使用 .send(y) 发送的数据会代替协程中的 yield 表达式 ,在上例中,发送的数据 y 是 77 ,77 代替了 yield 表达式,并赋给了变量 x。
最后、当赋值完毕后、协程会继续前进至下一个 yield 关键字并悬停 ,直至结束从而抛出 StopIteration 异常。
你可以把 .send( y ) 看做两个部分的结合,即:
- yield 表达式 = y
 - .next()
 
这样一来,拥有 .send()方法的生成器,完全符合了协程的定义,它可以通 过 .send() 接受调用方传递的值,并且可以通过 yield 产出值给调用方。
不过,此时我们没有办法在一创建协程时,立马使用它。
            你必须要先使用一次 .next() 让协程悬停在 yield 表达式那一行,从而使协程转变至 
            
              GEN_SUSPENDED 状态
            
            。这样的行为被称作
            
              预激协程。
            
          
预激协程
毫无疑问,预激协程是一个很容易被遗忘的步骤。
需要使用 .send() 发送数据之前还必须使用一次 .next(),这让人感到厌烦。
我们有什么办法能够自动预激协程呢?
有一种方法是使用能够提前调用一次 .next() 的装饰器,如下面这个 coroutine 装饰器:
            
              
                # BEGIN CORO_DECO
              
              
                >>
              
              
                >
              
              
                from
              
               functools 
              
                import
              
               wraps
              
                >>
              
              
                >
              
              
                def
              
               coroutine_deco
              
                (
              
              func
              
                )
              
              
                :
              
              
                """Decorator: primes `func` by advancing to first `yield`"""
              
              
        @wraps
              
                (
              
              func
              
                )
              
              
                #使用 functools.wraps 装饰器获得源 func 的所有参数 "*args,**kwargs"
              
              
                def
              
              
                primer
              
              
                (
              
              
                *
              
              args
              
                ,
              
              
                **
              
              kwargs
              
                )
              
              
                :
              
               
            gen 
              
                =
              
               func
              
                (
              
              
                *
              
              args
              
                ,
              
              
                **
              
              kwargs
              
                )
              
              
                #使用源生成器函数获取生成器
              
              
                next
              
              
                (
              
              gen
              
                )
              
              
                #调用 .next 方法
              
              
                return
              
               gen 
              
                #返回调用 .next 方法后的生成器
              
              
                return
              
               primer
    
              
                # END CORO_DECO
              
            
          
          网上有多个类似的装饰器。这个改自 ActiveState 中的一个诀窍——Pipeline made of coroutines,作者是 Chaobin Tang,而他是受到了 David Beazley 的启发。—— 《流畅的 python 》
使用这个装饰器后,现在我们再运行例二的代码就不会报 TypeError 异常,而是会正常运行了,如下:
            
              @coroutine_deco
              
                >>
              
              
                >
              
              
                def
              
              
                coroutine
              
              
                (
              
              
                )
              
              
                :
              
              
                print
              
              
                (
              
              
                '-- 协程开始 --'
              
              
                )
              
              
        x 
              
                =
              
              
                yield
              
              
                'Nothing'
              
              
                print
              
              
                (
              
              
                '协程接收到了数据: {!r}'
              
              
                .
              
              
                format
              
              
                (
              
              x
              
                )
              
              
                )
              
              
                >>
              
              
                >
              
               coro 
              
                =
              
               coroutine
              
                (
              
              
                )
              
              
                -
              
              
                -
              
               协程开始 
              
                -
              
              
                -
              
              
                >>
              
              
                >
              
              
                import
              
               inspect
              
                >>
              
              
                >
              
               inspect
              
                .
              
              getgeneratorstate
              
                (
              
              coro
              
                )
              
              
GEN_SUSPENDED
              
                >>
              
              
                >
              
               cro
              
                .
              
              send
              
                (
              
              
                77
              
              
                )
              
              
协程接收到了数据
              
                :
              
              
                77
              
              
Traceback 
              
                (
              
              most recent call last
              
                )
              
              
                :
              
              
                .
              
              
                .
              
              
                .
              
              
StopIteration
              
                >>
              
              
                >
              
              inspect
              
                .
              
              getgeneratorstate
              
                (
              
              coro
              
                )
              
              
GEN_CLOSED
            
          
          该例子有如下行为需要注意:
- 在创建协程 coro 对象后,直接输出了 “-- 协程开始 --” 字符串,这表明, 在创建协程对象后,其自动调用了一次 next() 方法。
 - 
              使用 
              
inspect.getgeneratorstate查看协程的状态,发现其已经是GEN_SUSPENDED状态, 说明协程内部已经悬停在 yield 关键字处。 - 能够直接调用 .send() 方法而不用事先使用 .next() 了。
 - 
              协程结束时的状态是 
              
GEN_CLOSED 
协程还有一个很常用的方法 —— .close() 用于提前关闭协程。使用该方法后,协程会在 yield 表达式那一行抛出 GeneratorExit 异常。
有时,我们需要协程在结束了所有工作时,返回一个值, 这在 python 3.3 之前是不可能的,因为在协程的方法体中写 return 关键字会报句法错误。
让协程在终止时返回值
我们可以在 python 3.3 及之后的版本中 让终止的协程返回想要的值 ,只是获取返回值的方法比较曲折。
下面的例三,定义了一个动态计算平均值的协程,并让其在结束工作(接受到 None 值)后 返回一个元组 ,该元组保存着目前为止收到的数据个数以及最终的平均值。
            
              
                >>
              
              
                >
              
              
                from
              
               collections 
              
                import
              
               namedtuple
              
                >>
              
              
                >
              
               Result 
              
                =
              
               namedtuple
              
                (
              
              
                'Result'
              
              
                ,
              
              
                'count average'
              
              
                )
              
              
                >>
              
              
                >
              
              
                def
              
              
                averager
              
              
                (
              
              
                )
              
              
                :
              
              
        total 
              
                =
              
              
                0.0
              
              
        count 
              
                =
              
              
                0
              
              
        average 
              
                =
              
              
                None
              
              
                while
              
              
                True
              
              
                :
              
              
            term 
              
                =
              
              
                yield
              
               average
            
              
                if
              
               term 
              
                is
              
              
                None
              
              
                :
              
              
                break
              
                
            total 
              
                +=
              
               term
            count 
              
                +=
              
              
                1
              
              
            average 
              
                =
              
               total
              
                /
              
              count
        
              
                return
              
               Result
              
                (
              
              count
              
                ,
              
               average
              
                )
              
            
          
          该函数有以下行为:
            
              
                >>
              
              
                >
              
               coro_avg 
              
                =
              
               averager
              
                (
              
              
                )
              
              
                >>
              
              
                >
              
              
                next
              
              
                (
              
              coro_avg
              
                )
              
              
                # <1>
              
              
                >>
              
              
                >
              
               coro_avg
              
                .
              
              send
              
                (
              
              
                10
              
              
                )
              
              
                # <2>
              
              
                10.0
              
              
                >>
              
              
                >
              
               coro_avg
              
                .
              
              send
              
                (
              
              
                30
              
              
                )
              
              
                20.0
              
              
                >>
              
              
                >
              
               coro_avg
              
                .
              
              send
              
                (
              
              
                6.5
              
              
                )
              
              
                15.5
              
              
                >>
              
              
                >
              
               coro_avg
              
                .
              
              send
              
                (
              
              
                None
              
              
                )
              
              
                # <3>
              
              
Traceback 
              
                (
              
              most recent call last
              
                )
              
              
                :
              
              
                .
              
              
                .
              
              
                .
              
              
StopIteration
              
                :
              
               Result
              
                (
              
              count
              
                =
              
              
                3
              
              
                ,
              
               average
              
                =
              
              
                15.5
              
              
                )
              
            
          
          
            注释:
            
             ① : 手动预激协程。
            
             ② : 调用 .send(10) 返回目前传入所有数的平均值10、之后每传入一个数都能实时计算所有数的平均值。
            
             ③ : 传入 None ,手动结束该协程。
          
注意到,和往常一样,结束后 协程抛出了 StopIteration 异常 。不一样的是, 该异常保存着返回的值 ,即 Result 对象。
return 表达式的值会偷偷传给调用方,赋值给 StopIteration 异常的一个属性。这样做有点不合常理,但是能 保留生成器对象的常规行为 ——耗尽时抛出 StopIteration 异常。
改造上面的代码,手动捕获异常,获取返回值,可以这样写:
            
              
                >>
              
              
                >
              
               coro_avg 
              
                =
              
               averager
              
                (
              
              
                )
              
              
                >>
              
              
                >
              
              
                next
              
              
                (
              
              coro_avg
              
                )
              
              
                >>
              
              
                >
              
               coro_avg
              
                .
              
              send
              
                (
              
              
                10
              
              
                )
              
              
                10.0
              
              
                >>
              
              
                >
              
               coro_avg
              
                .
              
              send
              
                (
              
              
                30
              
              
                )
              
              
                20.0
              
              
                >>
              
              
                >
              
               coro_avg
              
                .
              
              send
              
                (
              
              
                6.5
              
              
                )
              
              
                15.5
              
              
                >>
              
              
                >
              
              
                try
              
              
                :
              
              
        coro_avg
              
                .
              
              send
              
                (
              
              
                None
              
              
                )
              
              
                except
              
               StopIteration 
              
                as
              
               exc
              
                :
              
               
        result 
              
                =
              
               exc
              
                .
              
              value
              
                >>
              
              
                >
              
               result 
Result
              
                (
              
              count
              
                =
              
              
                3
              
              
                ,
              
               average
              
                =
              
              
                15.5
              
              
                )
              
            
          
          目前,我们说明了如何让 生成器接收调用方提供的值从而进化成协程 、如何 使用装饰器自动预激协程 、以及 如何从协程获取看起来很有用的返回值。
使用协程似乎太麻烦了点 !
不是吗? 为了避免麻烦,我们必须自己定义一个自动预激协程的装饰器,为了获取协程的返回值,我们还必须捕捉异常,并获取异常的 value 属性。
有什么办法能够消除这些麻烦呢?(不用自定义预激装饰器也不用捕获异常以获得返回值)
在 python 3.3 以后,有一个新的句法能够帮助我们解决这些麻烦,即 yield from
yield from 及其工作原理
使用 yield from 关键字 不仅能自动预激协程 、 自动提取异常的 value 属性返回值作为 yield from 表达式的值 ,还能够 作为调用方和协程之间的通道 。
如果将例三中的 averager() 改编成使用 yield from 关键字来实现,会是例四的代码:
            
              
                >>
              
              
                >
              
              
                from
              
               collections 
              
                import
              
               namedtuple
              
                >>
              
              
                >
              
               Result 
              
                =
              
               namedtuple
              
                (
              
              
                'Result'
              
              
                ,
              
              
                'count average'
              
              
                )
              
              
                >>
              
              
                >
              
              
                def
              
              
                averager
              
              
                (
              
              
                )
              
              
                :
              
              
        total 
              
                =
              
              
                0.0
              
              
        count 
              
                =
              
              
                0
              
              
        average 
              
                =
              
              
                None
              
              
                while
              
              
                True
              
              
                :
              
              
            term 
              
                =
              
              
                yield
              
               average
            
              
                if
              
               term 
              
                is
              
              
                None
              
              
                :
              
              
                break
              
                
            total 
              
                +=
              
               term
            count 
              
                +=
              
              
                1
              
              
            average 
              
                =
              
               total
              
                /
              
              count
        
              
                return
              
               Result
              
                (
              
              count
              
                ,
              
               average
              
                )
              
              
                >>
              
              
                >
              
               result 
              
                =
              
              
                set
              
              
                (
              
              
                )
              
              
                # <1>
              
              
                >>
              
              
                >
              
              
                def
              
              
                yf_averager
              
              
                (
              
              result
              
                )
              
              
                :
              
              
                # <2>
              
              
                while
              
              
                True
              
              
                :
              
              
                # <3>
              
              
            r 
              
                =
              
              
                yield
              
              
                from
              
               averager
              
                (
              
              
                )
              
              
                # <4>
              
              
            result
              
                .
              
              add
              
                (
              
              r
              
                )
              
              
                >>
              
              
                >
              
               yfa 
              
                =
              
               yf_averager
              
                (
              
              result
              
                )
              
              
                # <5>
              
              
                >>
              
              
                >
              
              
                next
              
              
                (
              
              yfa
              
                )
              
              
                # <6>
              
              
                >>
              
              
                >
              
               yfa
              
                .
              
              send
              
                (
              
              
                10
              
              
                )
              
              
                # <7>
              
              
                10.0
              
              
                >>
              
              
                >
              
               yfa
              
                .
              
              send
              
                (
              
              
                30
              
              
                )
              
              
                20.0
              
              
                >>
              
              
                >
              
               yfa
              
                .
              
              send
              
                (
              
              
                6.5
              
              
                )
              
              
                15.5
              
              
                >>
              
              
                >
              
               yfa
              
                .
              
              send
              
                (
              
              
                None
              
              
                )
              
              
                # <8>
              
              
                >>
              
              
                >
              
               result  
              
                # <9>
              
              
                {
              
              Result
              
                (
              
              count
              
                =
              
              
                3
              
              
                ,
              
               average
              
                =
              
              
                15.5
              
              
                )
              
              
                }
              
            
          
          
            在例四中,averager() 方法并没有做任何改变
            
             解释:
            
             ①:创建 result 集合以在调用方收集结果。
            
             ②:yield from 关键字的
            
              载体函数
            
            ,有时也叫“委派生成器” ,设立这一函数是因为
            
              在函数外部使用 yield from(以及 yield)会导致句法错误。
            
            
             ③:使用循环以保证传入 None 时 
            
              yf_averager 生成器不抛出 StopIteration 异常
            
            从而直接结束整个程序,若是如此,我们便观察不到 result 了。
            
             ④:使用 yield from 关键字后面是
            
              协程
            
            、前面是接收协程最终返回值的变量 r,这个 r 我们最终会放在全局变量 result 集合中。还有一点需要注意、
            
              当函数体重含有 yield from 那么它本身就是协程了
            
            。
            
             ⑤:新建 yf_averager 协程,以
            
              建立调用方与 averager 协程的通道
            
            
             ⑥:预激 yf_averager 协程
            
             ⑦:使用 .send()发送数据
            
             ⑧:发送 None 以结束 averager 协程
            
             ⑨:展示 result 集合中的值,确认接收到了最终的结果
          
            上面如果上面这个例子你不怎么看得懂,没关系,我会在后面解释。
            
             你现在
            
              只需要知道 yield from 有这些行为:
            
          
- 在例四中,我们没有预激 averager 协程,但是它能够正常工作。 这说明 yield from 关键字会自动预激协程。
 - 调用方使用委派生成器 yf_averager 传入的值会送到 averager 里,并且调用方可以接收到 averager 协程处理后返回的值。 这说明了使用 yield from 的委派生成器 yf_averager 可以在调用方和协程之间建立通道,传输数据。
 - 在获取 averager 结果时,我们没有捕获异常,而是在第 22 行代码中将返回值直接赋给了变量 r。 这说明了协程的最终返回值会成为 yield from 表达式的值。
 
yield from 关键字的原理
接下来这段伪码等效于 RESULT = yield from EXPR 语句 。它能够帮助你理解例四中 yield from 的行为
这并不是完整的伪代码,它去除了 .throw()和 .close()方法,只处理 StopIteration 异常。完整的伪码在这里 -> yield_from_expansion,不过在理解其功能的方面上,这足够了。
            
              _i 
              
                =
              
              
                iter
              
              
                (
              
              EXPR
              
                )
              
              
                # <1>
              
              
                try
              
              
                :
              
              
    _y 
              
                =
              
              
                next
              
              
                (
              
              _i
              
                )
              
              
                # <2>
              
              
                except
              
               StopIteration 
              
                as
              
               _e
              
                :
              
              
    _r 
              
                =
              
               _e
              
                .
              
              value  
              
                # <3>
              
              
                else
              
              
                :
              
              
                while
              
              
                1
              
              
                :
              
              
                # <4>
              
              
        _s 
              
                =
              
              
                yield
              
               _y  
              
                # <5>
              
              
                try
              
              
                :
              
              
            _y 
              
                =
              
               _i
              
                .
              
              send
              
                (
              
              _s
              
                )
              
              
                # <6>
              
              
                except
              
               StopIteration 
              
                as
              
               _e
              
                :
              
              
                # <7>
              
              
            _r 
              
                =
              
               _e
              
                .
              
              value
            
              
                break
              
              
RESULT 
              
                =
              
               _r  
              
                # <8>
              
            
          
          
            解释:
            
             ① :EXPR 可以是任何可迭代的对象,因为获取迭代器 _i(这是子生成器,例子中的 averager 协程)使用的是 iter() 函数。
            
             ② :
            
              预激子生成器(averager 协程);结果保存在 _y 中,作为产出的第一个值。
            
            
             ③ :如果抛出 StopIteration 异常,
            
              获取异常对象的 value 属性,赋值给 _r
            
            ——这是最简单情况下的返回值(RESULT)。
            
             ④ :运行这个循环时,委派生成器(yf_averager 生成器)会阻塞,
            
              只作为调用方和子生成器之间的通道
            
            。
            
             ⑤ :**产出子生成器当前产出的元素;等待调用方发送 _s 中保存的值。**因为这一个 yield 表达式和 ⑥ 中的send(),
            
              委派生成器也变成了协程。
            
            
             ⑥ :尝试让子生成器向前执行,
            
              转发调用方发送的 _s
            
            。
            
             ⑦ :如果子生成器抛出 StopIteration 异常,
            
              获取 value 属性的值,赋值给 _r
            
            ,然后退出循环,让委派生成器恢复运行。
            
             ⑧ :
            
              返回的结果(RESULT)是 _r
            
            ,即整个 yield from 表达式的值。
          
以上的伪代码和注释,几户原封不动的搬了《流程的 python 》里的解释,我只是增加了一些注释。因为我想不出如何更好的总结 yield from 关键字的原理。
注意,因为 yf_averager 是带 yield 关键字的生成器,所以在 ⑧ 结束后, 若找不到下一个 yield 关键字,那么 yf_averager 生成器会抛出 StopIteration 异常 ,这是我在例四中设立 while 循环 ③ 的直接原因。
我建议你在看懂这段伪代码的基础上再去 回顾例四 ,这下你 应该豁然开朗 了。如果还看不懂的话,我建议你多花些时间去看《流程的 python 》的第十六章,该章用了60多页的篇幅把 python 协程讲得很通透。
结语
本篇博文中,我用了四个小节叙述了我理解中的协程、及其使用技巧。在一开始,我讲述了 协程是什么 ,及 如何在 python 2.2 及以后的版本中用生成器构建协程 ;然后我讲述了 协程的必要操作(预激)的自动化方法 和 如何在 python 3.3 及以后的版本中获取协程的返回值 ;最后,我讲述了方便的 yield from 关键字的用法、行为 以及 它的主要原理 。
如果你想要知道 协程的具体用处 ,《流程的 python 》的第十六章中举了一个离散事件仿真的例子—— 出租车队运营仿真 。该仿真程序会创建几辆出租车,并模拟他们并行运作(离开仓库、寻找乘客、乘客下次、四处徘徊、回家)。对于说明如何使用协程做离散事件仿真是一个很好的例子。
这是那个出租车队运营仿真例子的源码 -> taxi_sim
我希望你看完这篇博文后能够有所收获、如果你看到了一些错误,请在评论中指出。

