2017-02-23 79 views
-1

我对Python很新颖,并且有想法如何优化这个程序以避免内存错误。优化数据转换程序以避免内存错误

我想从两个工作簿中读取数据:raw_data和映射。我想使用映射文档将raw_data转换为新的电子表格来转换数据。所以我加载工作簿,从映射数据中创建数据字典并开始转换。但是,我遇到了内存错误。

有什么办法来优化下面的代码来避免这个错误?

import openpyxl 
from openpyxl.utils import get_column_letter 

mapping = openpyxl.load_workbook(r'C:...\mapping.xlsx') #load mapping doc 
wb = openpyxl.load_workbook(r'C:...\raw_data.xlsx') #load raw data 


sheet = wb.active #look at the active sheet in the raw data file 
user_map_raw = mapping.get_sheet_by_name('User ID Mapping') #for user ids 
item_map_raw = mapping.get_sheet_by_name('Item ID Mapping') #for item ids 
...other mappings here 

def load(sheet): 

    user_dict = {} 
    print "creating user dictionary..." 
    for row in range(1, user_map_raw.max_row+1): 
     old_name = user_map_raw['A' + str(row)].value #old user name 
     new_name = user_map_raw['B' + str(row)].value #new user name 
     user_dict[old_name] = new_name #old name is key for the new name 

    item_dict = {} 
    print "creating item id dictionary..." 
    for row in range(1, item_map_raw.max_row+1): 
     old_item = item_map_raw['A' + str(row)].value #old item id 
     new_item = item_map_raw['B' + str(row)].value #new item id 
     item_dict[old_item] = new_item #old item id is key for new item id 

    raw = [] #empty list to store data before writing to new file 
    for row in range(2, sheet.max_row+1): #loop thru raw data and map 
     print "loading row %s" % row 
     user_ID = user_dict[sheet['A' + str(row)].value] 
     item_type = sheet['B' + str(row)].value 
     item_ID = item_dict[sheet['C' + str(row)].value] 
     ...other transformations here 
     add = [user_ID, item_type, item_ID, ...] 
     raw.append(add) #add transformed data to list 

    new = openpyxl.Workbook() #create new workbook 
    output = new.active #select the active sheet 
    for i in range(len(raw)): #loop through transformed data list 
     "print writing row %s" %i 
     for j in range(len(raw[i])): #write to new sheet 
      output[get_column_letter(j+1) + str(i+1)] = raw[i][j] 
    new.save('new_doc.xlsx') 

load(sheet) 

回答

1

主要优化将是,以避免加载整个主工作簿到内存中,以及避免存储整个结果在存储器中写入之前。有write_onlyread_only模式for openpyxl工作簿可以在运行时通过实现具有减少功能的优化表示和对迭代器的支持来节省大量内存。既然你正在写一个新文件而不是编辑到位,这些模式可以产生很大的不同。

wb = openpyxl.load_workbook(r'C:...\raw_data.xlsx', read_only=True) 
sheet = wb.active 

# mapping related code... 

from openpyxl.writer.write_only import WriteOnlyCell 
wb = openpyxl.Workbook(write_only=True) #create new workbook 
ws = new.create_sheet() 

for row in sheet.iter_rows(row_offset=1): 
    for i, cell in enumerate(row): 
     if i = 0: #A 
      user_ID = WriteOnlyCell(ws, user_dict[cell.value]) 
     elif i = 1: #B 
      item_type = WriteOnlyCell(ws, cell.value) 
     elif i = 2: #C 
      item_ID = WriteOnlyCell(ws, item_dict[cell.value]) 
     else: 
      break 
    ws.append([user_ID, item_type, item_ID]) 

wb.save('new_doc.xlsx') 

必须遍历单元格,因为它是一个生成器,所以不能使用下标。看起来很笨重,但我很累。

如果您使用的是Python 2.x,那么无论何时使用range函数,都会在内存中创建一个与范围一样大的列表,如果您有一个非常大的电子表格,可以填满您的内存。在你的情况下,你可以使用xrange来动态生成每个迭代以节省一些内存。

+0

嘿谢谢 - 这个作品。我唯一的问题:你为什么使用row_offset = 2? – kbball

+0

在问题示例中,您将通过主表跳过几行并从第2行开始循环:'range(2,sheet.max_row + 1):'。希望'row_offset'做同样的事情。很难从openpyxl文档中知道。您可能需要验证您是否获得了所需的所有行。 – systemjack

+0

对,我认为它应该是row_offset = 1。但无论如何现在工作。感谢你在这方面的所有努力。关键要点:将文件读取为只读并将其写入为只写。使用iter_rows提取单元格值 – kbball

0

读你的源文件和write-only模式时,您可以使用read-only模式来写的结果。这将尽量减少内存使用。