1. 程式人生 > >【詳解】Python處理大量資料與DICT遍歷的優化問題

【詳解】Python處理大量資料與DICT遍歷的優化問題

前言:本例我們的需求是寫一個每天0點執行的指令碼。這個指令碼從一個實時更新的資料庫中提取資料。

每天跑一個Excel表出來,表裡是當天零點與昨天零點時的差異的資料展示。

其實是很簡單的需求,遇到的關鍵問題是資料量。該例的資料量太大,每次都能從資料庫中拿出20多萬條資料。

資料量大的話遇到的問題有這麼幾個:

1. 資料無法裝入Excel表,因為使用Python處理Excel資料,最多插入65536行資料,多了就會報錯;

2. 遍歷篩選問題。我們拿到兩天的資料進行對比,然後生成一個差異對比表。就需要遍歷對比兩張表的資料,資料量太大,遍歷所用時間過長。

對這兩個關鍵的問題,我們現作闡述。

【問題一:Excel表改為Csv表】

我們發現,Csv格式的表,是沒有行數限制的,我們可以把20多萬條資料直接插入csv表中。

【問題二:DICT型別資料的遍歷】

按我們以往的經驗,生成對比資訊的字典程式碼如下:

def getCurrentCompareMessageDict0(dict0, dict1):
    '''未被優化的獲取當前對比資訊字典'''
dlist0=list(dict0.keys())
    dlist1=list(dict1.keys())
    dict2={}
    for i in range(len(dlist1)):
        if dlist1[i] not in dlist0:
            key=dlist1[i]
            value=[0
, dict1[dlist1[i]]] dict2[key]=value else: if dict1[dlist1[i]]/100.0 != dict0[dlist1[i]]: key=dlist1[i] value=[dict0[dlist1[i]], dict1[dlist1[i]]] dict2[key]=value return dict2
即,先構建兩個dict的key列表。

然後,以key列表的長度為上限,進行for迴圈,採用DICT[KEY]的方式來進行列表資料的篩選。

這個方法的執行是超級慢的。

經過研究我們將該方法改進如下:

def getCurrentCompareMessageDict(dict0, dict1):
    '''優化的獲取當前對比資訊字典'''
dict2={}
    i=0
for d, x in dict1.items():
        if dict0.has_key(str(d)):
            if x/100.0 != string.atof(dict0[str(d)]):
                key=d
                value=[string.atof(dict0[str(d)]), x]
                dict2[key] = value
        else:
            key=d
            value=[0, x]
            dict2[key]=value
    return dict2
採用該方法後,兩組20多萬條資料的比對篩選,在1秒內就完成了。

經測試,優化方法後速度提高了大約400倍!

這個方法優化了哪裡呢?

首先,遍歷dict的方法改為了

for d, x in dict1.items():
其中,d為key,x為value。其實也可以這樣寫
for (d, x) in dict1.items():
網上找到的資料稱,加括號的在200次以內的遍歷效率較高,不加括號的在200次以上的遍歷效率較高。(參考連結:python兩種遍歷方式的比較

我們沒有去測試,採用了不加括號的方式。

其次,檢測某key是否存在於dict中的方法改為了

if dict0.has_key(str(d)):
這個has_key函式返回的是布林值True或False。

原先的檢測方法:

if dlist1[i] not in dlist0:
捨棄!

其實提高了效率的部分就兩步,遍歷和檢測!至於到底是哪一步提高了,……應該是都提高了。

因為這兩步的程式碼不是分開的,是聯絡在一起的。

只有採用了for d,x in dict.items()這種遍歷方法,才能夠直接使用d和x這兩個引數,而不用取值。

關鍵問題就是如上兩個。還有過程中遇到的幾個問題需要闡述一下:

1. python比較兩個陣列中的元素是否完全相等的問題。

>>> a = [(1,1),(2,2),(3,3),(4,4)]
>>> b = [(4,4),(1,1),(2,2),(3,3)]
>>> a.sort()
>>> b.sort()
>>> a==b
True
即,先排序後比較。只檢驗其中的元素是否一致,不考慮順序的影響。

2.python如何將字串轉為數字?

最終程式碼中我們用到了

string.atof(浮點數字符串)
string.atoi(整數字符串)

注意:需要

import string

3.讀取csv檔案

我們之前都是寫csv檔案。這裡需要讀,並將其中的資料裝入dict中,方便使用。

方法如下:

def getHandleDataDict(fileName):
    '''獲取昨天零點資料字典'''
dict={}
    csvfile=file(fileName, 'rb')
    reader=csv.reader(csvfile)
    for i in reader:
        key=i[0]
        value=i[1]
        dict[key]=value
    return dict
關鍵程式碼兩行:
    csvfile=file(fileName, 'rb')
    reader=csv.reader(csvfile)
    for i in reader:
i 就是dict中每條資料。每個i是個列表,i[0]是key,i[1]是value。

4.Python的KeyError

這個錯誤我們不是第一次遇到,這裡著重說明,以示重視

KeyError的意思是:dict中不存在這個鍵。這種情況,我們如果dict[key]去取這個key對應的value,就會報KeyError的錯誤。

有可能是key的資料型別出錯,也有可能就是不存在這個鍵,兩種情況都要考慮。

我們在本例中遇到了資料型別出錯的情況,所以才會有2問題,將字串轉為數字blabala。。。。

【指令碼撰寫思想闡述】

還有一個指令碼的撰寫思想,先貼出最終版程式碼如下。

#!/usr/bin/python
# -*- coding: UTF-8 -*-
__author__ = "$Author: wangxin.xie$"
__version__ = "$Revision: 1.0 $"
__date__ = "$Date: 2015-01-05 10:01$"
###############################################################
#功能: 當前0點與昨天0點餘額資訊對比表,每天00:00執行
###############################################################
import sys
import datetime
import xlwt
import csv
import string
from myyutil.DBUtil import DBUtil

#######################全域性變數####################################
memberDBUtil = DBUtil('moyoyo_member')

today = datetime.datetime.today()
todayStr = datetime.datetime.strftime(today, "%Y-%m-%d")
handleDate = today - datetime.timedelta(1)
handleDateStr = datetime.datetime.strftime(handleDate, "%Y-%m-%d")

fileDir = 'D://'
handleCsvFileName= fileDir+handleDateStr+'_balance_data.csv'
currentCsvfileName = fileDir+todayStr+'_balance_data.csv'
currentexcelFileName= fileDir+todayStr+'_balance_compare_message.xls'
style1 = xlwt.XFStyle()
font1 = xlwt.Font()
font1.height = 220
font1.name = 'SimSun'
style1.font = font1

csvfile1=file(currentCsvfileName, 'wb')
writer1 = csv.writer(csvfile1, dialect='excel')
##################################################################
def genCurrentBalanceData():
    '''獲取當前餘額資料'''
sql = '''
        SELECT MEMBER_ID,
        (TEMP_BALANCE_AMOUNT + TEMP_FROZEN_AMOUNT)
        FROM moyoyo_member.MONEY_INFO
        WHERE (TEMP_BALANCE_AMOUNT + TEMP_FROZEN_AMOUNT) != 0
    '''
rs = memberDBUtil.queryList(sql, ())
    if not rs: return None
return rs

def getCurrentDataDict(rs):
    '''將當前資料組裝為字典'''
dict={}
    for i in range(len(rs)):
        key=rs[i][0]
        value=rs[i][1]
        dict[key]=value
    return dict

def writeCsv(x,writer):
    '''csv資料寫入函式'''
writer.writerow(x)

def writeCurrentCsvFile():
    '''寫包含當前資料的csv檔案'''
rs=genCurrentBalanceData()
    dict=getCurrentDataDict(rs)
    for d, x in dict.items():
        writeCsv([d, x/100.0], writer1)
    csvfile1.close()
    return dict

def getHandleDataDict(fileName):
    '''獲取昨天零點資料字典'''
dict={}
    csvfile=file(fileName, 'rb')
    reader=csv.reader(csvfile)
    for i in reader:
        key=i[0]
        value=i[1]
        dict[key]=value
    return dict

def getCurrentCompareMessageDict(dict0, dict1):
    '''獲取當前對比資訊字典'''
dict2={}
    for d, x in dict1.items():
        if dict0.has_key(str(d)):
            if x/100.0 != string.atof(dict0[str(d)]):
                key=d
                value=[string.atof(dict0[str(d)]), x]
                dict2[key] = value
        else:
            key=d
            value=[0, x]
            dict2[key]=value
    return dict2

def writeExcelHeader():
    '''Excel表表頭'''
wb = xlwt.Workbook(encoding = "UTF-8", style_compression = True)
    sht0 = wb.add_sheet("餘額資訊對比列表", cell_overwrite_ok = True)
    sht0.col(0).width=3000
sht0.col(1).width=4000
sht0.col(2).width=4000
num=today.day
    sht0.write(0, 0, '使用者ID', style1)
    sht0.write(0, 1, str(num-1)+'日零點餘額', style1)
    sht0.write(0, 2, str(num)+'日零點餘額', style1)
    return wb

def writeCurrentCompareMessageInfo(sht,dict):
    '''寫當前對比資訊資料'''
dlist=list(dict.keys())
    for i in range(len(dlist)):
        sht.write(i+1, 0, dlist[i], style1)
        sht.write(i+1, 1, dict[dlist[i]][0], style1)
        sht.write(i+1, 2, dict[dlist[i]][1]/100.0, style1)

def writeCurrentCompareMessageExcel(dict):
    '''寫當前對比資訊Excel'''
wb = writeExcelHeader()
    sheet0 = wb.get_sheet(0)
    writeCurrentCompareMessageInfo(sheet0, dict)
    wb.save(currentexcelFileName)

def main():
    print "===%s start===%s"%(sys.argv[0], datetime.datetime.strftime(datetime.datetime.now(), "%Y-%m-%d %H:%M:%S"))
    currentDataDict=writeCurrentCsvFile()
    handleDataDict = getHandleDataDict(handleCsvFileName)
    currentCompareMessageDict = getCurrentCompareMessageDict(handleDataDict, currentDataDict)
    writeCurrentCompareMessageExcel(currentCompareMessageDict)
    print "===%s end===%s"%(sys.argv[0], datetime.datetime.strftime(datetime.datetime.now(), "%Y-%m-%d %H:%M:%S"))

if __name__ == '__main__':
    try:
        main()
    finally:
        if memberDBUtil: memberDBUtil.close()
之所以要說,指令碼撰寫思想。

是因為我們在寫這個指令碼時,需要注意的很多問題,沒有加以重視。

尤其是流程方面。先做什麼後做什麼,拿到的資料如何處理。有沒有可以省去的步驟之類的。

都是在寫各個方法時需要注意的。

思想一:指令碼執行時間的指導作用

我們這個指令碼需求裡說,指令碼需要在每日零點取資料。資料庫中的資料是實時改變的。

所以既然要求了0點取資料,所以取資料的方法肯定是要放在最前面的。

即,指令碼的方法排列,與指令碼要求的執行時間是有密切關係的。

指令碼為什麼要選在0點執行,0點的時候幹了些什麼,是需要我們多加考慮的。

因為,最終影響的是資料的準確性。

即,如果我們先運行了別的方法,比如讀取昨天0點的csv檔案之類的方法。

讀了20多秒後,才執行這個取資料的方法。這時候取的資料就不是零點資料了。

思想二:不要重複勞動。

我們來分析一下本例中的資料流向。

Dict0--------昨天0點的資料在csv中。

Dict1--------該指令碼於當日0點執行時從資料庫中取的資料。先寫入csv中。

Dict2--------昨天的資料與剛跑出來的資料,經過對比篩選出來的差異資料字典。

需要注意的是生成Dict2時程式碼的操作。Dict0的資料自然是直接取,Dict1的資料存在於程式碼中的Dict1,可以直接return。

但是之前我們犯了一個錯誤,Dict1的資料我們從剛生成的csv檔案中提取。

這樣是沒有必要的。我們直接從程式碼中取就可以。這個資料程式碼中就有,不需要到檔案中提取了。

會因為這個無端延長指令碼的執行時間的。屬於基本的邏輯疏漏。

所以最終版程式碼中的這個方法。

def writeCurrentCsvFile():
    '''寫包含當前資料的csv檔案'''
rs=genCurrentBalanceData()
    dict=getCurrentDataDict(rs)
    for d, x in dict.items():
        writeCsv([d, x/100.0], writer1)
    csvfile1.close()
    return dict
在寫完csv檔案後,用過的dict就直接return了,因為後面還要用。

生成的csv檔案只是為了與明天的資料作對比。

思想三:資料產生的意義。

犯了上述錯誤。我們可以反思一下,資料的作用。。還有檔案的作用。

我們生成dict是為了什麼,當然資料可能不止一個作用,這個要注意。

csv0是為了提供dict0,dict0是為了與當天資料對比。

dict1是為了生成明天的csv,還有生成當天的dict2。

即,csv1根本不是為了dict1而存在的。只是為了為明天而做準備。

明白了這一點,就不會做出從csv1中取dict1的傻事了。

相關推薦

Python處理大量資料DICT優化問題

前言:本例我們的需求是寫一個每天0點執行的指令碼。這個指令碼從一個實時更新的資料庫中提取資料。 每天跑一個Excel表出來,表裡是當天零點與昨天零點時的差異的資料展示。 其實是很簡單的需求,遇到的關鍵問題是資料量。該例的資料量太大,每次都能從資料庫中拿出20多萬條資料。

Python專題開發

使用Python從指令碼到專題開發,才知道為什麼會有人說Python大法好,別的都去死! 因為相較於JAVA,開發起來要爽太多,方方面面…… 【需求】 1. 本例,因為著急上線一個新的活動版塊,所以使用Python來開發管理後臺。 2. 功能很簡單,增刪改查都實現即可。

Python下載圖片

我們已經可以熟練的利用Python抓取網頁上的字串和數字資訊了 本例,我們來介紹使用Python下載圖片的簡單方法! 因為簡單,我們先貼出程式碼如下: #!/usr/bin/python # -*- coding: utf-8 -*- __author__ = "$Au

銀行信用評分卡中的WOE在幹什麼?WOE的意義?為什麼可以使用WOE值代替原來的特徵值來做LR的訓練輸入資料

其實我是帶著這個問題發現這篇帖子的 為什麼可以使用WOE值代替原來的特徵值來做LR的訓練輸入資料 以下為原文 https://zhuanlan.zhihu.com/p/30026040 WOE & IV woe全稱叫Weight of Evidence,常用在風險評估、授

原創Python處理海量資料的實戰研究

感謝July ^_^ 他用的是Java的Hash Map等方法做了處理,講解的非常深刻入骨 我也一時興起,想拿Python試試刀,看看Python對於海量資料的處理能力如何。無奈在百度和Google輸入“Python 海量資料”都無果。可能是國內使用python的不多,

記錄Python寫Excel預約資訊表併發送郵件

類似於本例我們寫過兩個指令碼了,但還是遇到了一些問題。 本例是比較基礎且標準的一個版本。 實現的操作是:從資料庫中取出資料,寫入Excel表,併發送郵件。相當簡單的一個Excel表。 【傳送給單人版】 #!/usr/bin/python # -*- coding: UT

(轉載)--SG函數和SG定理

nbsp 發現 方式 spa 賦值 problem eve 查詢 mex 在介紹SG函數和SG定理之前我們先介紹介紹必勝點與必敗點吧. 必勝點和必敗點的概念: P點:必敗點,換而言之,就是誰處於此位置,則在雙方操作正確的情況下必敗。 N

KMP算法

是不是 代碼 ++ 大牛 bilibili 開始 最長 [] 分別是 前言 KMP算法是學習數據結構 中的一大難點,不是說它有多難,而是它這個東西真的很難理解(反正我是這麽感覺的,這兩天我一直在研究KMP算法,總算感覺比較理解了這套算法, 在這裏我將自己的思路分享給大家

快速冪 二進位制 取模

本來想昨天寫的   看到了cod:ww2  我:我就玩一把,真的,就一把 然後就到了12點 真香~ 程式碼如下 不想理解可以直接拿來用 時間複雜度 logn typedef long long ll; ll quickmod(ll n) {

C++ MFC程序間通訊之剪貼簿

Windows剪貼簿是一種比較簡單的程序間通訊機制,同時它的開銷相對較小。它的實現原理很簡單,其實就是由由作業系統維護的一塊記憶體區域, 這塊記憶體區域不屬於任何單獨的程序,但是每一個程序又都可以訪問這塊記憶體區域,當一個程序將資料放到該記憶體區域中,而另一個

HTTP協議——經典面試題(轉載)

http請求由三部分組成,分別是:請求行、訊息報頭、請求正文 HTTP(超文字傳輸協議)是一個基於請求與響應模式的、無狀態的、應用層的協議,常基於TCP的連線方式,HTTP1.1版本中給出一種持續連線的機制,絕大多數的Web開發,都是構建在HTTP協議之上的Web應用。

js網站國際化,多國語言切換

JS網站國際化,多國語言切換【詳解】 作者:Anmbition 在web開發過程中通常會碰到需要多國語言支援的需求,我也看過一些文件,但寫的都不盡人意,最終我整理了一套通過js程式碼完成解決方案,並對程式碼進行了很大程度的優化,在使用過程中只需極少的程式碼即可完成。 第一步:核心JS

快速冪&龜速乘&快速乘

我相信進來看的人都會快速冪,對吧(和善的眼神) 如果不會。。。。那我們現在開始講吧(要不然為什麼叫詳解2333 ) 如果已經知道,就跳到下面去看吧~ 1. 快速冪 1.0 快速冪的誕生——最初的思路 我們通常需要求解形如 ab mod c 的式子,當b比較小的時

CS231n assignment1KNN中不使用迴圈計算距離:從原理到程式

本文主要講述不使用迴圈結構來計算兩個矩陣的歐氏距離, 設訓練集矩陣為train,size為num_train * num_features,設驗證集矩陣為validate,size為num_test,num_features。 因此我們計算每一個驗證集樣本到訓練集樣本的距離,就是將訓練集

JNI(Java Native Interface)

前言: 一提到JNI,多數程式設計者會下意識地感受到一種無法言喻的恐懼。它給人的第一感覺就是"難",因為它不是單純地在JVM環境內操作Java程式碼,而是跳出虛擬機器與其他程式語言進行互動。   你可能至今還沒聽說過這個技術,但是如果你是一個原始碼愛好者,或者有翻閱過JDK的一些原始碼,那你一定有接觸過nat

CopyOnWriteArrayList

前言   之前看《Java併發程式設計》這本書的時候,有看到這個,只記得"讀多寫少"、"寫入時複製"。書中沒有過多講述,只是一筆帶過(不過現在回頭看,發現講的都是精髓。老外的書大多重理論,喜歡花大篇幅講概念,這點我非常喜歡)記得當時是覺得可能有點難,先跳過了,結果就忘記回頭看了。今天突然想起來,就看了一下,整

以銀行零售業務為例,一個案例說清楚視覺化微服務架構_Kubernetes中文社群

Part 1: API設計和策略 軟體系統的複雜性是一個很痛苦的問題,而且無法避免。Fred Brooks將複雜性描述為,軟體系統解決業務問題所固有的本質複雜性,以及實施該解決方案所帶來的偶發複雜性。 隨著與採用“API優先”工程實踐和微服務架構的組織進行更密切的合作,我發現這種描述越來

必須知道的八大種排序演算法java實現(二) 選擇排序,插入排序,希爾演算法

一、選擇排序   1、基本思想:在要排序的一組數中,選出最小的一個數與第一個位置的數交換;然後在剩下的數當中再找最小的與第二個位置的數交換,如此迴圈到倒數第二個數和最後一個數比較為止。   2、例項   3、演算法實現    /** * 選擇排序演算法 * 在未

HTTP協議——經典面試題

http請求由三部分組成,分別是:請求行、訊息報頭、請求正文 HTTP(超文字傳輸協議)是一個基於請求與響應模式的、無狀態的、應用層的協議,常基於TCP的連線方式,HTTP1.1版本中給出一種持續連線的機制,絕大多數的Web開發,都是構建在HTTP協議之上的Web應用。 1、常用的HTTP方法有哪些?GET:

WebSocket相關知識整理

前言   記得大概半年前就產生了疑惑,即後臺如何主動向前端推送資料。問了下專業老師,知道了原來有一個叫WebSocket的技術可以用於推送資料。於是,當時我就找了個教程,用的是Spring WebSocket。照著敲了一遍,也就搭起來了,依葫蘆畫瓢而已。當時有其他東西要學,也沒有相關的需求,就沒再接觸過。前