RF可用的多線程裝飾器(改進版)

之前的文章裏,我的多線程裝飾器雖然在我們的RF框架上用上了,但那是因爲我們裝飾的方法是靜態方法。如果裝飾的方法是寫在類中,由於第一個參數是self,這個參數在被裝飾時並不會傳入,只有調用時纔會在第一個位置傳入,導致解析參數時報錯。
定位發現問題是在

def multiThread(func):  # 實現多線程
    def infunc(*args):
        print "診斷", args  # 這裏加了這一行,然後發現後面的每個參數都是兩兩成對的元組,即每個線程的(*args,**kwargs),而第一個參數是孤零零的實例object,即self
        global resset
        resset = []  # 每次調用前,清除結果集
        tli = []  # 線程對象列表
        tid = 0  # 初始線程編號

定位後,解決這個問題不難,把代碼改變爲如下便可:

在迭代參數開始前先把第一個參數(self)拿走並保存

        tli = []  # 線程對象列表
        tid = 0  # 初始線程編號
		
		# 在這裏新增一個判斷 #########
        if type(args[0])!=tuple:  # 說明原來的函數是類裏面的,第一個參數是self
            sarg=args[0]
            args=tuple(args[1:])
        else:
            sarg=None
        ###########################

        for arg in args:  # 處理傳入的參數列表
            if len(arg) == 1:

接着在迭代結束後,執行前加上參數,也就是說原來的targ = tuple(list(targ) + [tid])改爲額外判斷,如果原來的類裏有self傳入,就把self拼在*args的最開頭,變成tuple([sarg] + list(targ) + [tid])

            except:
                raise ValueError, 'arguement format error'
            targ = tuple(list(targ) + [tid]) if not sarg else tuple([sarg] + list(targ) + [tid])  # 在位置參數的最後一個位置,加上線程編號傳進去。getResult裏會把這個參數pop掉,並記錄到結果裏
            tli.append(Thread(target=func, args=targ, kwargs=darg))  # 線程列表填充
            tid = tid + 1  # 線程編號遞增
        for th in tli:
            th.setDaemon(True)

這裏是完整的腳本和測試代碼

# coding: utf-8
from threading import Thread

def getResult(func):  # 重定向函數返回的結果
    def infunc(*args, **kwargs):
        args = list(args)
        tid = args.pop()  # 去掉最後一個代表線程編號的參數
        args = tuple(args)
        try:
            resset.append((func(*args, **kwargs), tid))  # 結果是一個由二元元組組成的列表,每個線程一個元組,元組內第一個元素是線程運行的結果,第二個是線程編號
        except Exception, e:
            resset.append((e, tid))  # 如果線程內報錯,就返回錯誤對象

    return infunc


def multiThread(func):  # 實現多線程
    def infunc(*args):
        # print "診斷", args
        global resset
        resset = []  # 每次調用前,清除結果集
        tli = []  # 線程對象列表
        tid = 0  # 初始線程編號

        if type(args[0])!=tuple:  # 說明原來的函數是類裏面的,第一個參數是self
            sarg=args[0]
            args=tuple(args[1:])
        else:
            sarg=None

        for arg in args:  # 處理傳入的參數列表
            if len(arg) == 1:
                if type(arg[0]) == type({}):
                    targ = ()  # 設置位置參數
                    darg = arg[0]  # 設置命名參數
                else:
                    targ = arg[0]
                    darg = {}
            elif len(arg) == 0 or arg == None:
                targ = ()
                darg = {}
            else:
                targ = arg[0]  # (args,kwargs) [0]
                darg = arg[1]  # (args,kwargs) [1]
            try:
                assert type(targ) == type(()) and type(darg) == type({})  # 確定傳來的參數格式正確
            except:
                raise ValueError, 'arguement format error'
            targ = tuple(list(targ) + [tid]) if not sarg else tuple([sarg] + list(targ) + [tid])  # 在位置參數的最後一個位置,加上線程編號傳進去。getResult裏會把這個參數pop掉,並記錄到結果裏
            tli.append(Thread(target=func, args=targ, kwargs=darg))  # 線程列表填充
            tid = tid + 1  # 線程編號遞增
        for th in tli:
            th.setDaemon(True)
            th.start()
        for th in tli:
            th.join()  # 大家都開始了才join阻塞
        return {x[1]: x[0] for x in resset}  # 結果字典,resset的結果是二元元組,0號元素是函數返回值,1號元素是線程編號tid

    return infunc


def parseArg(*args, **kwargs):  # 打包單個線程的參數
    return (args, kwargs)


def multipleArg(gtuple, times):  # 複製某個線程的參數n次,形成參數元組
    return (gtuple,) * times


if __name__=="__main__":
    ############################################## 演示常規用法 #####################################
    from selenium import webdriver
    import time, re, random


    @multiThread
    @getResult
    def baidusearch(statement):
        driver = webdriver.Chrome()
        driver.get(r'http://www.baidu.com')
        driver.maximize_window()
        driver.implicitly_wait(10)
        kw = driver.find_element_by_id('kw')
        kw.clear()
        kw.send_keys(statement)
        driver.find_element_by_id('su').click()
        time.sleep(2)
        resNumber = driver.find_element_by_xpath("//span[@class='nums_text']").text
        resNumber = re.findall('[0-9,]+', resNumber)[0]
        driver.close()
        return resNumber


    li1 = parseArg('mi')
    li2 = parseArg('wo')
    li3 = parseArg(u'故宮喵')
    print(baidusearch(li1, li2, li3))

    lis = multipleArg(parseArg('python'), 3)  # 或者可以像下面這樣,連續生成多個同樣的關鍵字
    print(baidusearch(*lis))  # 這種方式不要忘記解包

    lit = tuple([parseArg('html' + str(x)) for x in range(1, 6)])  # 使用推導式,分別搜索html1 html2 ... html5
    print(baidusearch(*lit))


    ################################# 可以使用路由的方式,讓不同的函數併發執行 #############################

    def plus(x, y):
        return x + y


    def minus(x, y):
        return abs(x - y)


    @multiThread
    @getResult
    def union(func_name, *args, **kwargs):  # 可以用這種方法來實現路由,讓不同的函數同時執行在一個線程裏
        return eval(func_name)(*args, **kwargs)


    li1 = parseArg('plus', 1, 2)
    li2 = parseArg('minus', 3, y=5)
    li3 = parseArg('plus', x=4, y=6)
    print(union(li1, li2, li3))

    #################################### 類裏面的函數也支持這樣處理了 ################################################

    class testLibrary(object):
        @multiThread
        @getResult
        def printer(self, tno):
            '''
            :param unicode tno: 線程編號
            :return:
            '''
            startTime = time.time()
            for i in range(50):
                print tno, i
                time.sleep(random.random())
            return time.time() - startTime

        @staticmethod
        def parseArg(*args, **kwargs):  # 打包單個線程的參數
            return (args, kwargs)

        @staticmethod
        def multipleArg(gtuple, times):  # 複製某個線程的參數n次,形成參數元組
            return (gtuple,) * times

    t=[parseArg(x,) for x in range(4)]
    lib=testLibrary()
    print lib.printer(*t)
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章