python协程--yield和yield from
字典为动词“to yield”给出了两个释义:产出和让步。对于 Python 生成器中的 yield 来说,这两个含义都成立。yield item 这行代码会产出一个值,提供给 next(...) 的调用方;此外,还会作出让步,暂停执行生成器,让调用方继续工作,直到需要使用另一个值时再调用 next()。调用方会从生成器中拉取值。
从句法上看,协程与生成器类似,都是定义体中包含 yield 关键字的函数。可是,在协程中,yield 通常出现在表达式的右边(例如,datum = yield),可以产出值,也可以不产出----yield 关键字后面没有表达式。协程可能会从调用方接收数据,调用方使用 .send(datum) 方法把数据提供给协程。
一:生成器如何进化成协程
自python中加入yield关键字后,又经过了一系列的演化:
yield 关键字可以在表达式中使用(a = yield b);
生成器 API 中增加了.send(value) 方法(生成器的调用方可以使用 .send(...) 方法发送数据,发送的数据会成为生成器函数中 yield 表达式的值);
PEP 342 添加了 .throw(...) 和 .close() 方法(前者的作用是让调用方抛出异常,在生成器中处理;后者的作用是终止生成器);
因此,生成器可以作为协程使用。协程是指一个过程,这个过程与调用方协作,产出由调用方提供的值。
协程最近的演进来自 Python 3.3实现的“PEP 380—Syntax for Delegating to a Subgenerator”(https://www.python.org/dev/peps/pep-0380/)。PEP 380 对生成器函数的句法做了两处改动:
生成器可以返回一个值;以前如果在生成器中给 return 语句提供值,会抛出 SyntaxError 异常;
新引入了 yield from 句法,使用它可以把复杂的生成器重构成小型的嵌套生成器,省去了之前把生成器的工作委托给子生成器所需的大量样板代码。
二:用作协程的生成器的基本行为
协程可以身处四个状态中的一个。当前状态可以使用inspect.getgeneratorstate(...) 函数确定,该函数会返回下述字符串中的一个。
GEN_CREATED:等待开始执行;
GEN_RUNNING:解释器正在执行(只有在多线程应用中才能看到这个状态);
GEN_SUSPENDED:在 yield 表达式处暂停;
GEN_CLOSED:执行结束;
一个简单的例子如下;
>>> def simple_coro2(a): ... print('-> Started: a =', a) ... b = yield a ... print('-> Received: b =', b) ... c = yield a + b ... print('-> Received: c =', c) ... >>> my_coro2 = simple_coro2(14) >>> from inspect import getgeneratorstate >>> getgeneratorstate(my_coro2) 'GEN_CREATED' >>> next(my_coro2) -> Started: a = 14 14 >>> getgeneratorstate(my_coro2) 'GEN_SUSPENDED' >>> my_coro2.send(28) -> Received: b = 28 42 >>> my_coro2.send(99) -> Received: c = 99 Traceback (most recent call last): File "<stdin>", line 1, in <module> StopIteration >>> getgeneratorstate(my_coro2) 'GEN_CLOSED'
最先调用 next(my_coro2) 函数这一步通常称为“预激”(prime)协程(即,让协程向前执行到第一个 yield 表达式,准备好作为活跃的协程使用)。
关键的一点是,协程在 yield 关键字所在的位置暂停执行。在赋值语句中,=右边的代码在赋值之前执行。因此,对于 b = yield a 这行代码来说,等到客户端代码再激活协程时才会设定 b 的值。
simple_coro2 协程的执行过程分为 3 个阶段,如下图所示:
三:使用协程计算移动平均值
下面是一个计算移动平均值的协程:
def averager():total = 0.0count = 0average = Nonewhile True:term = yield averagetotal += termcount += 1average = total/count>>> coro_avg = averager() >>> next(coro_avg) #调用 next 函数,预激协程 >>> coro_avg.send(10) 10.0 >>> coro_avg.send(30) 20.0 >>> coro_avg.send(5) 15.0
这个无限循环表明,只要调用方不断把值发给这个协程,它就会一直接收值,然后生成结果。仅当调用方在协程上调用 .close() 方法,或者没有对协程的引用而被垃圾回收程序回收时,这个协程才会终止。
调用 next(coro_avg) 函数后,协程会向前执行到yield 表达式,产出 average 变量的初始值——None,因此不会出现在控制台中。此时,协程在 yield 表达式处暂停,等到调用方发送值。coro_avg.send(10) 那一行发送一个值,激活协程,把发送的值赋给 term,并更新 total、count 和 average 三个变量的值,然后开始 while 循环的下一次迭代,产出 average 变量的值,等待下一次为term 变量赋值。
四:预激协程的装饰器
如果不预激,那么协程没什么用。调用 my_coro.send(x) 之前,记住一定要调用next(my_coro)。为了简化协程的用法,有时会使用一个预激装饰器。
下面就是一个预激装饰器的例子(Python3):
from functools import wrapsdef coroutine(func):@wraps(func)def primer(*args,**kwargs):gen = func(*args,**kwargs)next(gen)return genreturn primer @coroutine def averager2():total = 0.0count = 0average = Nonewhile True:term = yield averagetotal += termcount += 1average = total/count>>> coro_avg = averager() >>> from inspect import getgeneratorstate >>> getgeneratorstate(coro_avg) 'GEN_SUSPENDED' >>> coro_avg.send(10) 10.0 >>> coro_avg.send(30) 20.0 >>> coro_avg.send(5) 15.0
注意,使用 yield from 句法调用协程时,会自动预激。
五:终止协程和异常处理
协程中未处理的异常会向上冒泡,传给 next 函数或 send 方法的调用方(即触发协程的对象)。
>>> from coroaverager1 import averager >>> coro_avg = averager() >>> coro_avg.send(40) 40.0 >>> coro_avg.send(50) 45.0 >>> coro_avg.send('spam') Traceback (most recent call last): ... TypeError: unsupported operand type(s) for +=: 'float' and 'str' >>> coro_avg.send(60) Traceback (most recent call last): File "<stdin>", line 1, in <module> StopIteration
由于在协程内没有处理异常,协程会终止。如果试图重新激活协程,会抛出StopIteration 异常。
从 Python 2.5 开始,客户代码可以在生成器对象上调用两个方法:throw 和 close,显式地把异常发给协程。
1:generator.throw(exc_type[, exc_value[, traceback]])
使生成器在暂停的 yield 表达式处抛出指定的异常。如果生成器处理了抛出的异常,代码会向前执行到下一个 yield 表达式,而产出的值会成为调用 generator.throw方法得到的返回值。如果生成器没有处理抛出的异常,异常会向上冒泡,传到调用方的上下文中。
2:generator.close()
使生成器在暂停的 yield 表达式处抛出 GeneratorExit 异常。如果生成器没有处理这个异常,或者抛出了 StopIteration 异常(通常是指运行到结尾),调用方不会报错。如果收到 GeneratorExit 异常,生成器一定不能产出值,否则解释器会抛出RuntimeError 异常。生成器抛出的其他异常会向上冒泡,传给调用方。
示例如下:
class DemoException(Exception):"""为这次演示定义的异常类型。"""def demo_exc_handling():print('-> coroutine started')while True:try:x = yieldexcept DemoException:print('*** DemoException handled. Continuing...')else:print('-> coroutine received: {!r}'.format(x))raise RuntimeError('This line should never run.')>>> exc_coro = demo_exc_handling() >>> next(exc_coro) -> coroutine started >>> exc_coro.send(11) -> coroutine received: 11 >>> exc_coro.send(22) -> coroutine received: 22>>> exc_coro.throw(DemoException) *** DemoException handled. Continuing... >>> getgeneratorstate(exc_coro) 'GEN_SUSPENDED'>>> exc_coro.close() >>> from inspect import getgeneratorstate >>> getgeneratorstate(exc_coro) 'GEN_CLOSED'
六:让协程返回值
在Python2中,生成器函数中的return不允许返回附带返回值。在Python3中取消了这一限制,因而允许协程可以返回值:
from collections import namedtuple Result = namedtuple('Result', 'count average')def averager():total = 0.0count = 0average = Nonewhile True:term = yieldif term is None:breaktotal += termcount += 1average = total/countreturn Result(count, average)>>> coro_avg = averager() >>> next(coro_avg) >>> coro_avg.send(10) >>> coro_avg.send(30) >>> coro_avg.send(6.5) >>> coro_avg.send(None) Traceback (most recent call last): ... StopIteration: Result(count=3, average=15.5)
发送 None 会终止循环,导致协程结束,返回结果。一如既往,生成器对象会抛出StopIteration 异常。异常对象的 value 属性保存着返回的值。
注意,return 表达式的值会偷偷传给调用方,赋值给 StopIteration 异常的一个属性。这样做有点不合常理,但是能保留生成器对象的常规行为——耗尽时抛出StopIteration 异常。如果需要接收返回值,可以这样:
>>> try: ... coro_avg.send(None) ... except StopIteration as exc: ... result = exc.value ... >>> result Result(count=3, average=15.5)
获取协程的返回值要绕个圈子,可以使用Python3.3引入的yield from获取返回值。yield from 结构会在内部自动捕获 StopIteration 异常。这种处理方式与 for 循环处理 StopIteration 异常的方式一样。对 yield from 结构来说,解释器不仅会捕获 StopIteration 异常,还会把value 属性的值变成 yield from 表达式的值。
七:使用yield from
yield from 是 Python3.3 后新加的语言结构。在其他语言中,类似的结构使用 await 关键字,这个名称好多了,因为它传达了至关重要的一点:在生成器 gen 中使用 yield from subgen() 时,subgen 会获得控制权,把产出的值传给 gen 的调用方,即调用方可以直接控制 subgen。与此同时,gen 会阻塞,等待 subgen 终止。
yield from 可用于简化 for 循环中的 yield 表达式。例如:
>>> def gen(): ... for c in 'AB': ... yield c ... for i in range(1, 3): ... yield i ... >>> list(gen()) ['A', 'B', 1, 2]
可以改为
>>> def gen(): ... yield from 'AB' ... yield from range(1, 3) ... >>> list(gen()) ['A', 'B', 1, 2]
yield from x 表达式对 x 对象所做的第一件事是,调用 iter(x),从中获取迭代器。因此,x 可以是任何可迭代的对象。
如果 yield from 结构唯一的作用是替代产出值的嵌套 for 循环,这个结构很有可能不会添加到 Python 语言中。
yield from 的主要功能是打开双向通道,把最外层的调用方与最内层的子生成器连接起来,这样二者可以直接发送和产出值,还可以直接传入异常,而不用在位于中间的协程中添加大量处理异常的样板代码。有了这个结构,协程可以通过以前不可能的方式委托职责。
PEP 380 使用了一些yield from使用的专门术语:
委派生成器:包含 yield from <iterable> 表达式的生成器函数;
子生成器:从 yield from 表达式中 <iterable> 部分获取的生成器;
调用方:调用委派生成器的客户端代码;
下图是这三者之间的交互关系:
委派生成器在 yield from 表达式处暂停时,调用方可以直接把数据发给子生成器,子生成器再把产出的值发给调用方。子生成器返回之后,解释器会抛出StopIteration 异常,并把返回值附加到异常对象上,此时委派生成器会恢复。
下面是一个求平均身高和体重的示例代码:
from collections import namedtupleResult = namedtuple('Result', 'count average')# 子生成器 def averager():total = 0.0count = 0average = Nonewhile True:# main 函数发送数据到这里 print("in averager, before yield")term = yieldif term is None: # 终止条件breaktotal += termcount += 1average = total/countprint("in averager, return result")return Result(count, average) # 返回的Result 会成为grouper函数中yield from表达式的值# 委派生成器 def grouper(results, key):# 这个循环每次都会新建一个averager 实例,每个实例都是作为协程使用的生成器对象while True:print("in grouper, before yield from averager, key is ", key)results[key] = yield from averager()print("in grouper, after yield from, key is ", key)# 调用方 def main(data):results = {}for key, values in data.items():# group 是调用grouper函数得到的生成器对象group = grouper(results, key)print("\ncreate group: ", group)next(group) #预激 group 协程。print("pre active group ok")for value in values:# 把各个value传给grouper 传入的值最终到达averager函数中;# grouper并不知道传入的是什么,同时grouper实例在yield from处暂停print("send to %r value %f now"%(group, value))group.send(value)# 把None传入groupper,传入的值最终到达averager函数中,导致当前实例终止。然后继续创建下一个实例。# 如果没有group.send(None),那么averager子生成器永远不会终止,委派生成器也永远不会在此激活,也就不会为result[key]赋值print("send to %r none"%group)group.send(None)print("report result: ")report(results)# 输出报告 def report(results):for key, result in sorted(results.items()):group, unit = key.split(';')print('{:2} {:5} averaging {:.2f}{}'.format(result.count, group, result.average, unit))data = {'girls;kg':[40, 41, 42, 43, 44, 54],'girls;m': [1.5, 1.6, 1.8, 1.5, 1.45, 1.6],'boys;kg':[50, 51, 62, 53, 54, 54],'boys;m': [1.6, 1.8, 1.8, 1.7, 1.55, 1.6], }if __name__ == '__main__':main(data)
grouper 发送的每个值都会经由 yield from 处理,通过管道传给 averager 实例。grouper 会在 yield from 表达式处暂停,等待 averager 实例处理客户端发来的值。averager 实例运行完毕后,返回的值绑定到 results[key] 上。while 循环会不断创建 averager 实例,处理更多的值。
外层 for 循环重新迭代时会新建一个 grouper 实例,然后绑定到 group 变量上。前一个 grouper 实例(以及它创建的尚未终止的 averager 子生成器实例)被垃圾回收程序回收。
代码结果如下:
create group: <generator object grouper at 0x7f34ce8458e0> in grouper, before yield from averager, key is girls;kg in averager, before yield pre active group ok send to <generator object grouper at 0x7f34ce8458e0> value 40.000000 now in averager, before yield send to <generator object grouper at 0x7f34ce8458e0> value 41.000000 now in averager, before yield send to <generator object grouper at 0x7f34ce8458e0> value 42.000000 now in averager, before yield send to <generator object grouper at 0x7f34ce8458e0> value 43.000000 now in averager, before yield send to <generator object grouper at 0x7f34ce8458e0> value 44.000000 now in averager, before yield send to <generator object grouper at 0x7f34ce8458e0> value 54.000000 now in averager, before yield send to <generator object grouper at 0x7f34ce8458e0> none in averager, return result in grouper, after yield from, key is girls;kg in grouper, before yield from averager, key is girls;kg in averager, before yieldcreate group: <generator object grouper at 0x7f34ce845678> in grouper, before yield from averager, key is girls;m in averager, before yield pre active group ok send to <generator object grouper at 0x7f34ce845678> value 1.500000 now in averager, before yield send to <generator object grouper at 0x7f34ce845678> value 1.600000 now in averager, before yield send to <generator object grouper at 0x7f34ce845678> value 1.800000 now in averager, before yield send to <generator object grouper at 0x7f34ce845678> value 1.500000 now in averager, before yield send to <generator object grouper at 0x7f34ce845678> value 1.450000 now in averager, before yield send to <generator object grouper at 0x7f34ce845678> value 1.600000 now in averager, before yield send to <generator object grouper at 0x7f34ce845678> none in averager, return result in grouper, after yield from, key is girls;m in grouper, before yield from averager, key is girls;m in averager, before yieldcreate group: <generator object grouper at 0x7f34ce845620> in grouper, before yield from averager, key is boys;kg in averager, before yield pre active group ok send to <generator object grouper at 0x7f34ce845620> value 50.000000 now in averager, before yield send to <generator object grouper at 0x7f34ce845620> value 51.000000 now in averager, before yield send to <generator object grouper at 0x7f34ce845620> value 62.000000 now in averager, before yield send to <generator object grouper at 0x7f34ce845620> value 53.000000 now in averager, before yield send to <generator object grouper at 0x7f34ce845620> value 54.000000 now in averager, before yield send to <generator object grouper at 0x7f34ce845620> value 54.000000 now in averager, before yield send to <generator object grouper at 0x7f34ce845620> none in averager, return result in grouper, after yield from, key is boys;kg in grouper, before yield from averager, key is boys;kg in averager, before yieldcreate group: <generator object grouper at 0x7f34ce8458e0> in grouper, before yield from averager, key is boys;m in averager, before yield pre active group ok send to <generator object grouper at 0x7f34ce8458e0> value 1.600000 now in averager, before yield send to <generator object grouper at 0x7f34ce8458e0> value 1.800000 now in averager, before yield send to <generator object grouper at 0x7f34ce8458e0> value 1.800000 now in averager, before yield send to <generator object grouper at 0x7f34ce8458e0> value 1.700000 now in averager, before yield send to <generator object grouper at 0x7f34ce8458e0> value 1.550000 now in averager, before yield send to <generator object grouper at 0x7f34ce8458e0> value 1.600000 now in averager, before yield send to <generator object grouper at 0x7f34ce8458e0> none in averager, return result in grouper, after yield from, key is boys;m in grouper, before yield from averager, key is boys;m in averager, before yield report result: 6 boys averaging 54.00kg6 boys averaging 1.68m6 girls averaging 44.00kg6 girls averaging 1.58m
这个试验想表明的关键一点是,如果子生成器不终止,委派生成器会在yield from 表达式处永远暂停。如果是这样,程序不会向前执行,因为 yield from(与 yield 一样)把控制权转交给客户代码(即,委派生成器的调用方)了。
八:yield from的意义
把迭代器当作生成器使用,相当于把子生成器的定义体内联在 yield from 表达式中。此外,子生成器可以执行 return 语句,返回一个值,而返回的值会成为 yield from 表达式的值。
PEP 380 在“Proposal”一节(https://www.python.org/dev/peps/pep-0380/#proposal)分六点说明了 yield from 的行为。这里几乎原封不动地引述,不过把有歧义的“迭代器”一词都换成了“子生成器”,还做了进一步说明。上面的示例阐明了下述四点:
子生成器产出的值都直接传给委派生成器的调用方(即客户端代码);
使用 send() 方法发给委派生成器的值都直接传给子生成器。如果发送的值是None,那么会调用子生成器的 __next__() 方法。如果发送的值不是 None,那么会调用子生成器的 send() 方法。如果子生成器抛出 StopIteration 异常,那么委派生成器恢复运行。任何其他异常都会向上冒泡,传给委派生成器;
生成器退出时,生成器(或子生成器)中的 return expr 表达式会触发StopIteration(expr) 异常抛出;
yield from 表达式的值是子生成器终止时传给 StopIteration 异常的第一个参数。
yield from 的具体语义很难理解,尤其是处理异常的那两点。在PEP 380 中阐述了 yield from 的语义。还使用伪代码(使用 Python 句法)演示了 yield from 的行为。
若想研究那段伪代码,最好将其简化,只涵盖 yield from 最基本且最常见的用法:yield from 出现在委派生成器中,客户端代码驱动着委派生成器,而委派生成器驱动着子生成器。为了简化涉及到的逻辑,假设客户端没有在委派生成器上调用throw(...) 或 close() 方法。而且假设子生成器不会抛出异常,而是一直运行到终止,让解释器抛出 StopIteration 异常。上面示例中的脚本就做了这些简化逻辑的假设。
下面的伪代码,等效于委派生成器中的 RESULT = yield from EXPR 语句(这里针对的是最简单的情况:不支持 .throw(...) 和 .close() 方法,而且只处理 StopIteration 异常):
_i = iter(EXPR) try:_y = next(_i) except StopIteration as _e:_r = _e.value else:while 1:_s = yield _ytry:_y = _i.send(_s)except StopIteration as _e:_r = _e.valuebreak RESULT = _r
但是,现实情况要复杂一些,因为要处理客户对 throw(...) 和 close() 方法的调用,而这两个方法执行的操作必须传入子生成器。此外,子生成器可能只是纯粹的迭代器,不支持 throw(...) 和 close() 方法,因此 yield from 结构的逻辑必须处理这种情况。如果子生成器实现了这两个方法,而在子生成器内部,这两个方法都会触发异常抛出,这种情况也必须由 yield from 机制处理。调用方可能会无缘无故地让子生成器自己抛出异常,实现 yield from 结构时也必须处理这种情况。最后,为了优化,如果调用方调用 next(...) 函数或 .send(None) 方法,都要转交职责,在子生成器上调用next(...) 函数;仅当调用方发送的值不是 None 时,才使用子生成器的 .send(...) 方法。
下面的伪代码,是考虑了上述情况之后,语句:RESULT = yield from EXPR的等效代码:
_i = iter(EXPR) try:_y = next(_i) except StopIteration as _e:_r = _e.value else:while 1:try:_s = yield _yexcept GeneratorExit as _e:try:_m = _i.closeexcept AttributeError:passelse:_m()raise _eexcept BaseException as _e:_x = sys.exc_info()try:_m = _i.throwexcept AttributeError:raise _eelse:try:_y = _m(*_x)except StopIteration as _e:_r = _e.valuebreakelse:try:if _s is None:_y = next(_i)else:_y = _i.send(_s)except StopIteration as _e:_r = _e.valuebreak RESULT = _r
上面的伪代码中,会预激子生成器。这表明,用于自动预激的装饰器与 yield from 结构不兼容。
from https://www.cnblogs.com/gqtcgq/p/8126124.html
转载于:https://www.cnblogs.com/zxmbky/p/10410941.html
python协程--yield和yield from相关推荐
- (转)python协程2:yield from 从入门到精通
原文:http://blog.gusibi.com/post/python-coroutine-yield-from/ https://mp.weixin.qq.com/s?__biz=MzAwNjI ...
- Python协程:从yield/send到async/await
这个文章理好了脉落. http://python.jobbole.com/86069/ 我练 习了一番,感受好了很多... Python由于众所周知的GIL的原因,导致其线程无法发挥多核的并行计算能力 ...
- 从yield 到yield from再到python协程
yield 关键字 def fib():a, b = 0, 1while 1:yield ba, b = b, a+b yield 是在:PEP 255 -- Simple Generators 这个 ...
- python协程系列(三)——yield from原理详解
声明:本文将详细讲解python协程的实现机理,为了彻底的弄明白它到底是怎么一回事,鉴于篇幅较长,将彻底从最简单的yield说起从最简单的生成器开始说起,因为很多看到这样一句话的时候很懵,即" ...
- python yield 协程_用yield实现python协程
刚刚介绍了pythonyield关键字,趁热打铁,现在来了解一下yield实现协程. 引用官方的说法: 与线程相比,协程更轻量.一个python线程大概占用8M内存,而一个协程只占用1KB不到内存.协 ...
- c++ 协程_用yield实现协程
上一篇 理解python中的yield关键字 介绍了使用yeld实现生成器函数,这一篇我们来继续深入的了解一下yield,用yield实现协程. 先来解答一下上一篇留下的问题:下面的代码为什么第二次调 ...
- python 协程_Python 协程与 Go 协程的区别(一)
? "Python猫" ,一个值得加星标的公众号 花下猫语:年关将近,不知各位过得怎样?我最近有些忙,收获也挺多,以后有机会分享下.吃饭时间,追了两部剧<了不起的麦瑟尔夫人& ...
- python中协程与函数的区别_深入浅析python 协程与go协程的区别
进程.线程和协程 进程的定义: 进程,是计算机中已运行程序的实体.程序本身只是指令.数据及其组织形式的描述,进程才是程序的真正运行实例. 线程的定义: 操作系统能够进行运算调度的最小单位.它被包含在进 ...
- c++ 协程_理解Python协程(Coroutine)
由于GIL的存在,导致Python多线程性能甚至比单线程更糟. GIL: 全局解释器锁(英语:Global Interpreter Lock,缩写GIL),是计算机程序设计语言解释器用于同步线程的一种 ...
最新文章
- Python3 中实现MATLAB中的点乘 即两列表对应元素相乘
- Cookie的简单实用
- Python编程基础:第六十节 多进程Multiprocessing
- 「leetcode」486. 预测赢家:【三种递归+动态规划】由浅入深,步步到位
- javascript窗口属性示意图
- 2012最新网上购物排行榜
- SQLMap常用命令介绍
- 最适合管理的计算机语言,PLC 编程语言的优劣,哪种语言更适合编程
- 时间状语:(现在完成时)/ 固定语句(现在完成时)/29主动表被动/23过去完成时:又 名 过去的过去;/过去将来时/(过去完成时)/25 过去完成时,固定搭配/26 时间状语 (将来完成时)
- 普渡大学的计算机工程,普渡大学(Purdue University)计算机工程专业解读 (2016-03-11 11:27:11)转载▼...
- 华科师兄快40岁才明白的道理
- INF=0x3f3f3f3f是 什么意思?
- 申宝证券-个股分化严重
- html表达式 %3c,避开XSS过滤常用方法
- AI幼儿园教育火了,人人都是深度学习界的明日之星?
- 请问在C#中如何实现声音报警?
- C# Excel操作之读,写,追加
- msm android机型适配,小米系统MIUI10适配全机型了,高通机型通用适配,高通通刷包...
- 基于ARDUINO汽车智能防碰撞控制系统设计(毕业设计)
- 记一次Android全流程开发体验经历以及Android Studio使用【从环境搭建到项目打包发布 + 真实踩坑总结】
热门文章
- 平板电脑必装十大软件_电脑越用越卡?这5个必装软件,分分钟帮你恢复火箭般的速度...
- linux系统安装如何设置raid,在RAID磁盘阵列下如何搭建Linux系统
- cmos和ttl_【转】CMOS与TTL电路的区别
- java gc 可达性_JAVA--GC 垃圾回收机制----可达性分析算法
- Apple任意代码执行漏洞
- BurpSuite使用——HTTP
- Spring入门(四):使用Maven管理Spring项目
- oracle的sid
- oracle备份恢复之rman恢复到异机(二)
- nginx与lighttpd性能简单对比