1.1. twisted

1.1.1. twised简介

scrapy是基于twised的,我们需要对scrapy有一定了解才能更好学习scrapy。

twisted和asyncio类比,都是为了支持携程而诞生的,但是twisted是老牌的,asyncio是python3.4之后才出现的。

1.1.2. schedule

定义多少秒后执行一个任务

from twisted.internet import reactor

def my_print(s):
    print(f"my_print: {s}")

# 这里是定义, 没有实际运行
reactor.callLater(5, my_print, "hello,wolrd")

# 5秒后执行my_print
reactor.run()

reactor.callLater会将某个任务加入到事件循环中,并设置好多少秒后开始执行,当然了,这个需要reactor.run()来启动。

定义一个周期任务

from twisted.internet import reactor, task 

def my_print(s):
    print(f"my_print: {s}")

# 这里是定义, 没有实际运行,周期性调用任务
loop = task.LoopingCall(my_print, "hello")
# 每2秒执行一次
loop.start(2)
# 开始执行
reactor.run()

1.1.3. Deferred

deferred表示任务未来会产生结果,当任务产生结果时,deferred会调用注册过来的回调函数。并将结果传递给它。

from twisted.internet import defer, reactor

def my_print(s):
    msg = f"my_print: {s}"
    print(msg)
    return msg 


def get_data(input_data): 
    deferred = defer.Deferred()
    # 注册一下,一秒后执行callback 将input_data作为参数
    reactor.callLater(1, deferred.callback, input_data*3)
    return deferred

deferred = get_data(10)
deferred.addCallback(my_print)

reactor.run()

get_data方法返回了一个 defer.Deferred 对象。 表示这个函数是一个异步任务,会在未来的某个时间产生结果,这里通过 reactor.callLater(1, deferred.callback, input_data*3) 来表示将来的1秒执行这个callback方法, 将 input_data*3 的结果作为参数传递给callback方法。到 my_print 接收的参数就是30了, 执行了简单的打印工作。

上面是成功回调的案例,异常回调同理。

1.1.4. 回调链

如果需要注册多个回调函数,那就需要使用回调链, 回调链是按照顺序执行的。

../_images/01.twisted_callback_chain.png
  • 成功回调链: 上一个的输出作为下一个的输入。 比如cb1的输出作为cb2的输入。

  • 异常回调链: 如果上一个回调链执行失败,则执行下一个的异常回调。 比如cb1执行失败,则执行eb2的回调,将异常传递给eb2。

  • 交互: 如果eb1 处理了异常,那么cb2会被调用,并将eb1的返回值作为参数。

总结: 当前级别的回调,只根据上一个级别的回调执行情况进行触发。

1.1.5. DeferredList

当需要等待多个deferred的执行完毕的时候,我们可以使用deferedlist。

from twisted.internet import defer

def print_result(result): 
    for ok,value in result: 
        if ok: 
            print(f'success: {value}')
        else: 
            print(f'fail: {value.getErrorMessage()}')

d1 = defer.Deferred()
d2 = defer.Deferred()
d3 = defer.Deferred()

dl = defer.DeferredList([d1,d2,d3],consumeErrors=True)

dl.addCallback(print_result)
d1.callback(1)
d2.callback(2)
d3.errback(Exception('this has a error'))

# success: 1
# success: 2
# fail: this has a error


    

通过deferredlist,就可以将多个deferred的执行结果,合并到一起。

1.1.6. defer.inlineCallbacks

这个是在scrapy中看到比较多的。 这个是一个装饰器, 我们对比看看使用装饰器和不使用装饰器的区别。


from twisted.internet import defer, reactor

def my_print(s):
    msg = f"my_print: {s}"
    print(msg)
    return msg 


def get_data(input_data): 
    deferred = defer.Deferred()
    # 注册一下,一秒后执行callback 将input_data作为参数
    reactor.callLater(1, deferred.callback, input_data*3)
    return deferred

def method_1():
    deferred = get_data(10)
    deferred.addCallback(my_print)
    reactor.run()

def method_2():
    method_3()
    reactor.run()

@defer.inlineCallbacks
def method_3():
    result = yield get_data(10).addCallback(my_print)
    print(result)

#method_1()
method_2()

# my_print: 30
# my_print: 30

通过@defer.inlineCallbacks, 我们将回调函数注册后,可以直接yield等待回调结果。