原文标题:PEP 0492 -- Coroutines with async and await syntax

原文链接:https://www.python.org/dev/peps/pep-0492/

生效于:Python 3.5

翻译参照版本:05-May-2015

翻译最后修改:2015年8月22日

翻译出处:http://www.cnblogs.com/animalize/p/4738941.html

用几句话说明这个PEP:

  1. 把协程的概念从生成器独立出来,并为之添加了新语句(async/await)。
  2. 但是在CPython的内部实现,协程仍然是一个生成器。
  3. 增加了异步迭代器(async for),异步迭代器的__aiter__、__anext__函数是协程,可以将程序挂起。
  4. 增加了异步上下文管理器(async with),异步上下文管理器的__aenter__、__aexit__函数是协程,可以将程序挂起。

PEP 492: 协程与async/await语法

>摘要

不断增多的Internet连接程序刺激了对响应性、伸缩性代码的需求。这个PEP的目标在于:制订显式的异步/并发语法,比传统的Python方法更易用、更丰富。

我们准备把协程(协同程序)的概念独立出来,并为其使用新的语法。最终目标是建立一个通用、易学的异步编程的构思模型,并尽量与同步编程的风格相似。

这个PEP假设异步任务被一个事件循环器(类似于标准库里的 asyncio.events.AbstractEventLoop)管理和调度。然而我们并不会依赖某个事件循环器的具体实现方法,从本质上说只与此相关:采用yield作为给调度器的信号,表示协程将会挂起、等待一个异步事件(如IO)的完成。

在这个异步编程不断增长的时期,我们相信这些改变将会使Python保持一定的竞争性,就像许多其它编程语言已经、将要进行的改变那样。

>API设计和实现的备注

根据Python 3.5 Beta期间的反馈,进行了重新设计,明确地把协程从生成器里独立出来了。协程现在是原生的,有明确的独立类型,而不是作为生成器的一种特殊形式。

这个改变,主要是为了解决在Tornado里使用协程出现的一些问题。

【译注:在Tornado 4.3已经可以使用新的async/await语句,详见此链接

>理论和目标

在以前,我们可以用生成器实现协程(PEP 342),后来又对其进行了改进,引入了yield from语法(PEP 380)。但仍有一些缺点:

  • 协程和普通生成器使用相同的语法,所以很容易把它们搞混,初学者更是如此。
  • 一个函数是否是一个协程,取决于它里面是否出现了yield或yield from语句。这并不明显,容易在重构函数的时候搞乱,导致出错。
  • 异步调用被yield语法限制了,我们不能获得、使用更多的语法特性,比如with和for。

这个PEP把协程从生成器独立出来,成为Python的一个原生事物。这会消除协程和生成器之间的混淆,方便编写不依赖特定库的协程代码。也为linter和IDE进行代码静态分析提供了机会。

【译注:在CPython内部,原生协程仍然是基于生成器实现的。】

使用原生协程和相应的新语法,我们可以在异步编程时使用上下文管理器(context manager)和迭代器。如下文所示,新的async with语句可以在进入、离开运行上下文(runtime context)时进行异步调用,而async for语句可以在迭代时进行异步调用。

>详细内容

请理解Python现有的协程(见PEP 342和PEP 380),这次改变的动机来自于asyncio框架(PEP 3156)和Confunctions提案(PEP 3152,此PEP已经被废弃)。

由此,在本文中,我们使用“原生协程”指用新语法声明的协程。“生成器实现的协程”指用传统方法实现的协程。“协程”则用在两个都可以使用的地方。

>>新的协程声明语法

使用以下语法声明原生协程:

async def read_data(db):
pass

协程语法的关键点:

  • async def函数必定是协程,即使里面不含有await语句。
  • 如果在async函数里面使用yield或yield from语句,会引发SyntaxError异常。
  • 在CPython内部,引入两个新的代码对象标识(code object flags):

    1, CO_COROUTINE表示这是原生协程。(由新语法定义)

    2, CO_ITERABLE_COROUTINE表示这是用生成器实现的协程,但是和原生协程兼容。(用装饰器types.coroutine()装饰过的生成器协程)
  • 调用一个普通生成器,返回一个生成器对象(generator object);相应的,调用一个协程返回一个协程对象(coroutine object)。
  • 协程不再抛出StopIteration异常,因为抛出的StopIteration异常会被包装(wrap)成一个RuntimeError异常。(在Python 3.5,对于普通生成器要想这样需要进行future import,见PEP 479)。
  • 如果一个协程从未await等待就被垃圾收集器销毁了,会引发一个RuntimeWarning异常(见“调试特性”)。
  • 更多请参考“协程对象”一节。

>>types.coroutine()

types模块添加了一个新函数coroutine(fn),使用它,“生成器实现的协程”和“原生协程”之间可以进行互操作。

【译注:这是个装饰器,能把现有代码的“用生成器实现的协程”转化为与“原生协程”兼容的形式】

@types.coroutine
def process_data(db):
data = yield from read_data(db)
...

coroutine(fn)函数给生成器的代码对象(code object)设置CO_ITERABLE_COROUTINE标识,使它返回一个协程对象。

如果fn不是一个生成器函数,它什么也不做。如果fn是一个生成器函数,则会被一个awaitable代理对象(proxy object)包装(wrapped),详见下文的“定义awaitable对象”。

注意, types.coroutine()不会设置CO_COROUTINE标识,只有用新语法定义的原生协程才会有这个标识。

【译注: @types.coroutine装饰器仅给生成器函数设置一个CO_ITERABLE_COROUTINE标识,除此之外什么也不做。但是如果生成器函数没有这个标识,await语句不会接受它的对象作为参数。】

>>await表达式

新的await表达式用于获得协程执行的结果:

async def read_data(db):
data = await db.fetch('SELECT ...')
...

await和yield from类似,它挂起read_data的执行,直到db.fetch执行完毕并返回结果。

以CPython内部,await使用了yield from的实现,但加入了一个额外步骤——验证它的参数类型。await只接受awaitable对象,awaitable对象是以下的其中一个:

  • 一个原生协程对象(由一个原生协程函数返回)

  • 用装饰器types.coroutine()装饰的一个“生成器实现的协程”对象

  • 一个有__await__方法的对象(__await__方法返回的一个迭代器)

    每个yield from调用链条都会追溯到一个最终的yield语句,这是Future实现的基本机制。在Python内部,由于协程是生成器的一种特殊形式,所以每个await最终会被await调用链条上的某个yield语句挂起。(详情请参考PEP 3156)

    【译注:Future对象用来表示在未来完成的某项任务。】

    为了让协程也有这样的行为,添加了一个新的魔术方法__await__。【译注:一系列递归调用必终结于某个return具体结果的语句;一个yield from调用链条必终结于某个yield语句;类似的,一个await调用链条必终结于某个有__await__方法的对象。】例如,在asyncio模块,要想在await语句里使用Future对象,唯一的修改是给asyncio.Future加一行:__await__ = __iter__

    在本文中,有__await__方法的对象被称为Future-like对象。

    【译注:协程会被await语句挂起,直到await语句右边的Future-like对象的__await__执行完毕、返回结果。】

    另外,请注意__aiter__方法(见下文)不能被用于此目的。那是另一套东西,这样做的话,类似于callable对象使用__iter__代替__call__。【译注:意思是__await__和__aiter__的关系有点像callable对象里__call__和__iter__的关系】

    如果__await__返回的不是一个迭代器,则引发TypeError异常。

  • 在CPython C API,有tp_as_async.am_await函数的对象,该函数返回一个迭代器(类似__await__方法)

如果在async def函数之外使用await语句,会引发SyntaxError异常。这和在def函数之外使用yield语句一样。

如果await右边不是一个awaitable对象,会引发TypeError异常。

>>>新的操作符优先级列表

【译注:总体略去不译。】

await语句和yield、yield from的一个区别是:await语句多数情况下不需要被圆括号包围。

>>>await表达式使用示例

有效用法:

表达式 被解析为
if await fut: pass if (await fut): pass
if await fut + 1: pass if (await fut) + 1: pass
pair = await fut, 'spam' pair = (await fut), 'spam'
with await fut, open(): pass with (await fut), open(): pass
await foo()['spam'].baz()() await ( foo()['spam'].baz()() )
return await coro() return ( await coro() )
res = await coro() ** 2 res = (await coro()) ** 2
func(a1=await coro(), a2=0) func(a1=(await coro()), a2=0)
await foo() + await bar() (await foo()) + (await bar())
-await foo() -(await foo())

无效用法:

表达式 应该写为
await await coro() await (await coro())
await -coro() await (-coro())

>>异步上下文管理器和“async with”

异步上下文管理器(asynchronous context manager),可以在它的enter和exit方法里挂起、调用异步代码。

为此,我们设计了一套方案,添加了两个新的魔术方法:__aenter__和__aexit__,它们必须返回一个awaitable。

异步上下文管理器的一个示例:

class AsyncContextManager:
async def __aenter__(self):
await log('entering context') async def __aexit__(self, exc_type, exc, tb):
await log('exiting context')

>>>新语法

采纳了一个异步上下文管理器的新语法:

async with EXPR as VAR:
BLOCK

在语义上等同于:

mgr = (EXPR)
aexit = type(mgr).__aexit__
aenter = type(mgr).__aenter__(mgr)
exc = True VAR = await aenter
try:
BLOCK
except:
if not await aexit(mgr, *sys.exc_info()):
raise
else:
await aexit(mgr, None, None, None)

和普通的with语句一样,可以在单个async with语句里指定多个上下文管理器。

在使用async with时,如果上下文管理器没有__aenter__和__aexit__方法,则会引发错误。在async def函数之外使用async with则会引发SyntaxError异常。

>>>示例

有了异步上下文管理器,协程很容易实现对数据库处理的恰当管理。

async def commit(session, data):
... async with session.transaction():
...
await session.update(data)
...

再比如,加锁时看着更简洁:

async with lock:
...

而不是:

with (yield from lock):
...

>>异步迭代器和“async for”

异步迭代器可以在它的iter实现里挂起、调用异步代码,也可以在它的__next__方法里挂起、调用异步代码。要支持异步迭代,需要:

  1. 对象必须实现一个__aiter__方法(或者,如果使用CPython C API,需要定义tp_as_async.am_aiter),返回一个异步迭代器对象,这个异步迭代器对象在每次迭代时会返回一个awaitable。
  2. 一个异步迭代器必须实现一个__anext__方法(或者,如果使用CPython C API,需要定义tp_as_async.am_anext),在每次迭代时返回一个awaitable。
  3. 要停止迭代,__anext__必须抛出一个StopAsyncIteration异常。

异步迭代的一个示例:

class AsyncIterable:
async def __aiter__(self):
return self async def __anext__(self):
data = await self.fetch_data()
if data:
return data
else:
raise StopAsyncIteration async def fetch_data(self):
...

>>>新语法

采纳了一个迭代异步迭代器的新语法:

async for TARGET in ITER:
BLOCK
else:
BLOCK2

在语义上等同于:

iter = (ITER)
iter = await type(iter).__aiter__(iter)
running = True
while running:
try:
TARGET = await type(iter).__anext__(iter)
except StopAsyncIteration:
running = False
else:
BLOCK
else:
BLOCK2

如果async for的迭代器不支持__aiter__方法,则引发TypeError异常。如果在async def函数外使用async for,则引发SyntaxError异常。

和普通的for语句一样,async for有一个可选的else分句。

>>>示例1

有了异步迭代,我们可以在迭代时异步缓冲(buffer)数据:

async for data in cursor:
...

Cursor是一个异步迭代器,可以从数据库预读4行数据并缓存。见以下代码:

# 【译注:此代码已被修改,望更易理解】
class Cursor:
def __init__(self):
self.buffer = collections.deque() async def _prefetch(self):
row1, row2, row3, row4 = await fetch_from_db()
self.buffer.append(row1)
self.buffer.append(row2)
self.buffer.append(row3)
self.buffer.append(row4) async def __aiter__(self):
return self async def __anext__(self):
if not self.buffer:
self.buffer = await self._prefetch()
if not self.buffer:
raise StopAsyncIteration
return self.buffer.popleft()

然后,可以这样使用Cursor类:

async for row in Cursor():
print(row)

与下述代码相同:

i = await Cursor().__aiter__()
while True:
try:
row = await i.__anext__()
except StopAsyncIteration:
break
else:
print(row)

>>>示例2

这是一个便利类,用于把普通的迭代对象转变为一个异步迭代对象。虽然这个类没什么实际用处,但它演示了普通迭代器和异步迭代器的关系:

class AsyncIteratorWrapper:
def __init__(self, obj):
self._it = iter(obj) async def __aiter__(self):
return self async def __anext__(self):
try:
value = next(self._it)
except StopIteration:
raise StopAsyncIteration
return value async for letter in AsyncIteratorWrapper("abc"):
print(letter)

>>>为什么是StopAsyncIteration?

在CPython内部,协程的实现仍然是基于生成器的。所以,在PEP 479生效之前【译注:将在Python 3.7正式生效,在3.5、3.6需要from __future__ import generator_stop】,以下两个代码是完全一样的,最终都是给外部代码抛出一个StopIteration('spam')异常:

def g1():
yield from fut
return 'spam'

def g2():
yield from fut
raise StopIteration('spam')

由于PEP 479已被正式采纳,并作用于协程,以下代码的StopIteration会被包装(wrapp)成一个RuntimeError。

async def a1():
await fut
raise StopIteration('spam')

所以,要想通知外部代码迭代已经结束,抛出一个StopIteration异常的方法不行了。因此,添加了一个新的内置异常StopAsyncIteration,用于表示迭代结束。

此外,根据PEP 479,协程抛出的所有StopIteration异常都会被包装成RuntimeError异常。

【译注:如果函数生成器内部的代码出现StopIteration异常、且未被捕获,则外部代码会误认为生成器已经迭代结束。为了消除这样的误会,PEP 479的规定,Python会把生成器内部抛出的StopIteration包装成RuntimeError。

在以后,如果想主动结束一个函数生成器的迭代,用return语句即可(这时函数生成器仍然会给外部代码抛出一个StopIteration异常),而不是以前的使用raise StopIteration语句(这样的话,StopIteration会被包装成一个RuntimeError)。】

>>协程对象

>>>和生成器的不同之处

这一小节只对原生协程有效(用async def语法定义的、有CO_COROUTINE标识的)。对于asyncio模块里现有的“基于生成器的协程”,仍然保持不变。

为了在概念上把协程和生成器区分开来,做了以下规定:

  1. 原生协程对象不实现__iter__和__next__方法,因此,不能对其进行迭代(如for...in循环),也不能传递给iter(),list(),tuple()及其它内置函数。如果尝试对其使用__iter__或__next__方法,会引发TypeError异常。
  2. 未装饰的生成器不能yield from一个原生协程,这样做会引发TypeError异常。
  3. “基于生成器的协程”在经过 @asyncio.coroutine装饰后,可以yield from原生协程对象。
  4. 对于原生协程对象和原生协程函数,调用inspect.isgenerator()和inspect.isgeneratorfunction()会返回False。

【译注: @asyncio.coroutine装饰器,在Python 3.4,用于把一个函数装饰为一个协程。有些函数并不是生成器函数(不含yield或yield from语句),也可以用 @asyncio.coroutine装饰为一个协程。

在Python 3.5中, @asyncio.coroutine也会有 @types.coroutine的效果——使函数的对象可以被await语句接受。】

>>>协程对象的方法

在CPython内部,协程是基于生成器实现的,因此它们有共同的代码。像生成器对象那样,协程也有throw(),send()和close()方法。

对于协程,StopIteration和GeneratorExit起着同样的作用(虽然PEP 479已经应用于协程)。详见PEP 342、PEP 380,以及Python文档。

对于协程,send(),throw()方法用于往Future-like对象发送内容、抛出异常。

>>调试特性

新手在使用协程时可能忘记使用yield from语句,比如:

@asyncio.coroutine
def useful():
asyncio.sleep(1) # 前面忘写yield from,所以程序在这里不会挂起1秒

在asyncio里,对于此类错误,有一个特定的调试方法。装饰器 @coroutine用一个特定的对象包装(wrap)所有函数,这个对象有一个析构函数(destructor)用于记录警告信息。无论何时,一旦被装饰过的生成器被垃圾回收,会生成一个详细的记录信息(具体哪个函数、回收时的stack trace等等)。包装对象提供一个__repr__方法用来输出关于生成器的详细信息。

唯一的问题是如何启用这些调试工具,由于这些调试工具在生产模式里什么也不做,比如 @coroutine必须是在系统变量PYTHONASYNCIODEBUG出现时才具有调试功能。这时可以给asyncio程序进行如下设置:EventLoop.set_debug(true),这时使用另一套调试工具,对 @coroutine的行为没有影响。

根据本文,协程是原生的,已经在概念上和生成器进行了区分。一个从未await的协程会抛出一个RuntimeWarning,除此之外,给sys模块增加了两个新函数set_coroutine_wrapper和get_coroutine_wrapper,它们会为asyncio和其它框架启用高级调试工具,比如显示协程在何处被创建、协程在何处被垃圾回收的详细stack trace。

>>新的标准库函数

  • types.coroutine(gen) 详见types.coroutine()一节。
  • inspect.iscoroutine(obj) 如果obj是原生协程对象,返回True。
  • inspect.iscoroutinefunction(obj) 如果obj是原生协程函数,返回True。
  • inspect.isawaitable(obj) 如果obj是awaitable返回True。
  • inspect.getcoroutinestate(coro) 返回原生协程对象的当前状态(inspect.getfgeneratorstate(gen)的镜像)。
  • inspect.getcoroutinelocals(coro) 返回一个原生协程对象的局部变量的映射【译注:变量名->值】(inspect.getgeneratorlocals(gen) 的镜像)。
  • sys.set_coroutine_wrapper(wrapper) 允许拦截原生协程对象的创建。wrapper必须是一个接受一个参数callable(一个协程对象),或者是None。None会重置(reset)这个wrapper。如果再次调用,新的wrapper会取代旧的。这个函数是线程专有的(thread-specific)。详见“调度特性”一节。
  • sys.get_coroutine_wrapper() 返回当前的包装对象(wrapper object)。如果没有则返回None。这个函数是线程专有的(thread-specific)。详见“调度特性”一节。

>>新的抽象基类

为了能更好的与现有框架(如Tornado)和其它编译器(如Cython)相整合,增加了两个新的抽象基类(Abstract Base Classes):

  1. collections.abc.Awaitable,Future-like类的抽象基类,实现__await__方法。
  2. collections.abc.Coroutine,协程对象的抽象基类,实现send(value),throw(type, exc, tb),close()和__await__()方法。

注意,“基于生成器的协程”(有CO_ITERABLE_COROUTINE标识)并不实现__await__方法,因此它们不是collections.abc.Coroutine和collections.abc.Awaitable的实例:

@types.coroutine
def gencoro():
yield assert not isinstance(gencoro(), collections.abc.Coroutine) # however:
assert inspect.isawaitable(gencoro())

为了更容易地对异步迭代进行调试,又增加了两个抽象基类:

  1. collections.abc.AsyncIterable --用于测试__aiter__方法。
  2. collections.abc.AsyncIterator --用于测试__aiter__和__anext__方法。

>词汇表

原生协程函数 Native coroutine function

由async def定义的协程函数,可以使用await和return value语句。见“新的协程声明语法”一节。

原生协程 Native coroutine

原生协程函数返回的对象。见“await表达式”一节。

基于生成器的协程函数 Generator-based coroutine function

基于生成器语法的协程,最常见的是用 @asyncio.coroutine装饰过的函数。

基于生成器的协程 Generator-based coroutine

基于生成器的协程函数返回的对象。

协程 Coroutine

“原生协程”和“基于生成器的协程”都是协程。

协程对象 Coroutine object

“原生协程对象”和“基于生成器的协程对象”都是协程对象。

Future-like对象 Future-like object

一个有__await__方法的对象,或一个有tp_as_async->am_await函数的C语言对象,它们返回一个迭代器。Future-like对象可以在协程里被一条await语句消费(consume)。协程会被await语句挂起,直到await语句右边的Future-like对象的__await__执行完毕、返回结果。见“await表达式”一节。

Awaitable

一个Future-like对象或一个协程对象。见“await表达式”一节。

异步上下文管理器 Asynchronous context manager

有__aenter__和__aexit__方法的对象,可以被async with语句使用。见“异步上下文管理器和‘async with’”一节。

可异步迭代对象 Asynchronous iterable

有__aiter__方法的对象, 该方法返回一个异步迭代器对象。可以被async for语句使用。见“异步迭代器和‘async for’”一节。

异步迭代器 Asynchronous iterator

有__anext__方法的对象。见“异步迭代器和‘async for’”一节。

【译注:感觉余下大部分内容不必翻译,如有需要请参看原文。这里只挑选部分内容翻译。】

>>向后兼容性

本PEP保持100%向后兼容。

>>>asyncio

asyncio模块已经可以使用新语法,并经过测试,100%与async/await兼容。现有的使用asyncio的代码在使用新语法时可以保持不变。

为此,对asyncio模块主要做了如下修改:

  1. 在 @asyncio.coroutine装饰器内部,调用types.coroutine为函数设置一个CO_ITERABLE_COROUTINE标识。
  2. 给asyncio.Future类添加一行代码: __await__ = __iter__。
  3. 把async()函数改名为ensure_future(),以防该函数名和新关键字冲突。

>>>asyncio迁移策略

由于未经装饰的生成器不能yield from原生协程对象(详见“和生成器的不同之处”一节),因此在使用新语法前,请确保所有“基于生成器的协程”都被 @asyncio.coroutine装饰器装饰。

>>启用关键字的计划

async和await在CPython 3.5、3.6里暂时不是正式的关键字,在CPython 3.7它们将变成正式的关键字。如果不这样,恐怕对现有代码的迁移造成困难。

【译注:在某些现有代码里,可能使用了async和await作为变量名/函数名。然而Python不允许把关键字当作变量名/函数名,所以3.5、3.6给程序员留了一些迁移时间。】

Python PEP 492 中文翻译——协程与async/await语法的更多相关文章

  1. Python的异步编程[0] -> 协程[0] -> 协程和 async / await

    协程 / Coroutine 目录 生产者消费者模型 从生成器到异步协程– async/await 协程是在一个线程执行过程中可以在一个子程序的预定或者随机位置中断,然后转而执行别的子程序,在适当的时 ...

  2. 协程 和 async await

    协程, 是 为了 避免 闭包传递变量 的 性能损耗 而产生  . 如果不是 为了 避免 闭包传递变量 的 性能损耗 ,    线程池 和 Task 已经够了,  不需要 再设计 出 协程 来  . 闭 ...

  3. Python的异步编程[0] -> 协程[1] -> 使用协程建立自己的异步非阻塞模型

    使用协程建立自己的异步非阻塞模型 接下来例子中,将使用纯粹的Python编码搭建一个异步模型,相当于自己构建的一个asyncio模块,这也许能对asyncio模块底层实现的理解有更大的帮助.主要参考为 ...

  4. python wsgi PEP333 中文翻译

    PEP 333 中文翻译 首先说明一下,本人不是专门翻译的,英文水平也不敢拿来献丑.只是这是两年前用python的时候为了自己学习方便而翻译的,记录着笔记自己看看而已.最近翻出来看看觉得还是放出来吧. ...

  5. python单线程,多线程和协程速度对比

    在某些应用场景下,想要提高python的并发能力,可以使用多线程,或者协程.比如网络爬虫,数据库操作等一些IO密集型的操作.下面对比python单线程,多线程和协程在网络爬虫场景下的速度. 一,单线程 ...

  6. Python 线程和进程和协程总结

    Python 线程和进程和协程总结 线程和进程和协程 进程 进程是程序执行时的一个实例,是担当分配系统资源(CPU时间.内存等)的基本单位: 进程有独立的地址空间,一个进程崩溃后,在保护模式下不会对其 ...

  7. Python并发编程系列之协程

    1 引言 协程是近几年并发编程的一个热门话题,与Python多进程.多线程相比,协程在很多方面优势明显.本文从协程的定义和意义出发,结合asyncio模块详细讲述协程的使用. 2 协程的意义 2.1 ...

  8. python并发编程之gevent协程(四)

    协程的含义就不再提,在py2和py3的早期版本中,python协程的主流实现方法是使用gevent模块.由于协程对于操作系统是无感知的,所以其切换需要程序员自己去完成. 系列文章 python并发编程 ...

  9. python并发编程之asyncio协程(三)

    协程实现了在单线程下的并发,每个协程共享线程的几乎所有的资源,除了协程自己私有的上下文栈:协程的切换属于程序级别的切换,对于操作系统来说是无感知的,因此切换速度更快.开销更小.效率更高,在有多IO操作 ...

随机推荐

  1. Linux下手动获取当前调用栈

    被问到如何手动获取当前的调用栈,之前碰到过一时没记起来,现在回头整理一下. 其原理是:使用backtrace()从栈中获取当前调用各层函数调用的返回地址,backtrace_symbols()将对应地 ...

  2. js isnull 赋值方法

    <script>var b = 'test';var a = b || {};alert(a)</script> 结果:test <script>var b;var ...

  3. Jenkins Job 自杀 groovy

    下面的groovy可以加在post groovy script里面在job跑完的时候自杀(把本Job删掉) suicide_url="http://[USER]:[PASSWORD]@[JE ...

  4. sass&compass&grunt

    1. compass compile path/to/project//编译scss compass watch path/to/project//自动监视文件变化 2.mixin @include ...

  5. 网易开发工程师编程题 比较重量 Java

    比较重量 小明陪小红去看钻石,他们从一堆钻石中随机抽取两颗并比较她们的重量.这些钻石的重量各不相同.在他们们比较了一段时间后,它们看中了两颗钻石g1和g2.现在请你根据之前比较的信息判断这两颗钻石的哪 ...

  6. 修改使用phpstorm创建的模板的默认注释

     

  7. [转]UDP穿透NAT的原理与实现(UDP“打洞”原理)

    NAT(The IP Network Address Translator) 的概念和意义是什么? NAT, 中文翻译为网络地址转换.具体的详细信息可以访问RFC 1631 - http://www. ...

  8. 干货:结合Scikit-learn介绍几种常用的特征选择方法

    原文  http://dataunion.org/14072.html 主题 特征选择 scikit-learn 作者: Edwin Jarvis 特征选择(排序)对于数据科学家.机器学习从业者来说非 ...

  9. 卫星地图下载软件WebImageDowns

    卫星地图下载软件WebImageDowns一款基于网络服务器的多线程卫星地图下载软件.支持多种网络地图.软件可以高速下载您所指定的任意经纬度范围的卫星地图,并可将所下载的卫星地图进行无缝拼接,使您可以 ...

  10. shell终极操作

    1.安装zsh Mac : 直接看下一节 Redhat/centos :sudo yum install zsh Ubuntu :sudo apt-get install zsh 2.安装oh my ...