使用Python和comtypes使用数组设置Excel范围?

使用comtypes来驱动Python,似乎有一些魔法发生在幕后,而不是将元组和列表转换为VARIANTtypes:

 # RANGE(“C14:D21”) has values # Setting the Value on the Range with a Variant should work, but # list or tuple is not getting converted properly it seems >>>from comtypes.client import CreateObject >>>xl = CreateObject("Excel.application") >>>xl.Workbooks.Open(r'C:\temp\my_file.xlsx') >>>xl.Visible = True >>>vals=tuple([(x,y) for x,y in zip('abcdefgh',xrange(8))]) # creates: #(('a', 0), ('b', 1), ('c', 2), ('d', 3), ('e', 4), ('f', 5), ('g', 6), ('h', 7)) >>>sheet = xl.Workbooks[1].Sheets["Sheet1"] >>>sheet.Range["C14","D21"].Value() (('foo',1),('foo',2),('foo',3),('foo',4),('foo',6),('foo',6),('foo',7),('foo',8)) >>>sheet.Range["C14","D21"].Value[()] = vals # no error, this blanks out the cells in the Range 

根据comtypes 文件 :

将简单序列(列表或元组)作为VARIANTparameter passing时,COM服务器将接收一个VARIANT其中包含VARIANTSAFEARRAY ,其types为VT_ARRAY | VT_VARIANT

这似乎与MSDN有关将数组传递给Range的值的内联。 我也发现这个页面在C#中显示了类似的东西。 谁能告诉我我做错了什么?

编辑

我想出了一个更简单的例子,它以相同的方式执行(在这种情况下,它不起作用):

 >>>from comtypes.client import CreateObject >>>xl = CreateObject("Excel.application") >>>xl.Workbooks.Add() >>>sheet = xl.Workbooks[1].Sheets["Sheet1"] # at this point, I manually typed into the range A1:B3 >>> sheet.Range("A1","B3").Value() ((u'AAA', 1.0), (u'BBB', 2.0), (u'CCC', 3.0)) >>>sheet.Range("A1","B3").Value[()] = [(x,y) for x,y in zip('xyz',xrange(3))] # Using a generator expression, per @Mike's comment # However, this still blanks out my range :( 

我花了很多时间试图找出这个问题的解决scheme,能够完全替代python for matlab,阅读各种不同的论坛,没有真正的直接答案。

这是我强大的解决scheme,工作得很好。 我必须编写每天/每周/每月/每季度的报告写入xlsx很多,这个function比一些关于使用python&com写入xlsx的信息更好。

 from numpy import * from win32com.client import DispatchEx # DispatchEx opens up an independent instance of Excel so writing to a document won't interfere with any other instances you have running def xlsxwrite(filename, sheet, data, cellstr, screenupdating = False, direction = 'h', visible = 0): ''' Write to an excel document by setting ranges equal to arrays. ''' xl = DispatchEx("Excel.Application") xl.ScreenUpdating = screenupdating xl.Visible = visible try: excel_type = get_exceltype(filename) # Check to see if workbook exists, if it doesn't create workbook try: xlBook = xl.Workbooks.Open(filename) except: print '\nFile Doesnt Exist, Writing File...\n\n\n' xlBook = xl.Workbooks.Add() try: xlBook.SaveAs(filename, excel_type) except: xl.Quit() raise NameError('Error writing file: %s, check to make sure path exists' % filename) # Get wksht names wksht_names = [xlBook.Sheets(i).Name for i in range(1,xlBook.Sheets.Count+1)] # If 'sheet' variable is an integer, get sheet by index number, else get it by name, or add new one try: int(sheet) try: xlSheet = xlBook.Sheets(int(sheet)) except: raise NameError('Error, referencing an invalid sheet') except: # If sheet input not in wksht names, add it if sheet not in wksht_names: print 'Worksheet, "%s", not found, Adding Worksheet' % sheet xlBook.Sheets.Add(After=xlBook.Sheets(xlBook.Sheets.Count)).Name = sheet xlSheet = xlBook.Sheets(sheet) # Convert Excel Range to Python Range row,col = getcell(cellstr) # Write out data output_dict, shp = data_export_cleaner(data, direction) a,b = shp start_cells = [(row,col+i) for i in range(b)] end_cells = [(row + a -1,col+i) for i in range(b)] for i in output_dict.keys(): cell_range = eval('xlSheet.Range(xlSheet.Cells%s,xlSheet.Cells%s)' % (start_cells[i],end_cells[i])) cell_range.Value = output_dict[i] # Save and close document, Quit Excel App xlBook.Close(True) xl.Quit() return except: xlBook.Close(False) xl.Quit() raise NameError('Error occurred while trying to write file') def data_export_cleaner(data,direction): """ Summary: Return data in a format that works with Excel Com (Numpy int32 for some reason was causing an error, have to turn it into a string, doesn't affect any formatting possibilities). Defaults: Going to set the default for writing data with len(shape(array(data))) == 1, such as a list, to horizontal, if you want to write it vertically, specify 'v', only applicable for lists. """ darray = array(data) shp = shape(darray) if len(shp) == 0: darray = array([data]) darray = darray.reshape(1,1) if len(shp) == 1: darray = array([data]) if direction.lower() == 'v': darray = darray.transpose() shp = shape(darray) tempdict = dict() for i in range(shp[1]): tempdict[i] = [(str(darray[j,i]),) for j in range(shp[0])] return tempdict, shp def get_exceltype(filename): format_dict = {'xlsx':51,'xlsm':52,'xlsb':50,'xls':56} temp = character_count(filename) if (temp['.'] > 1 or temp['.'] == 0): raise NameError('Error: Incorrect File Path Name, multiple or no periods') f_type = filename.split('.') f_type = f_type[len(f_type)-1] if f_type not in format_dict.keys(): raise NameError('Error: Incorrect File Path, No excel file specified') else: return format_dict[f_type] def character_count(a_string): temp = dict() for c in a_string: temp[c] = temp.get(c,0) + 1 return temp def getcell(cell): '''Take a cell such as 'A1' and return the corresponding numerical row and column in excel''' a = len(cell) temp_column = [] row = [] temp_row = [] if a < 2: raise NameError('Error, the cell you entered is not valid') for i in range(a): if str.isdigit(cell[i])==False: temp_column.append(cell[i]) else: temp_row.append(cell[i]) row.append(string.join(temp_row,'')) row = int(row[0]) column = getnumericalcolumn(temp_column) return row, column def getnumericalcolumn(column): '''Take an excel column specification such as 'A' and return its numerical equivalent in excel''' alpha = str(string.ascii_uppercase) alphadict = dict(zip(alpha,range(1,len(alpha)+1))) if len(column) == 1: numcol = alphadict[column[0]] elif len(column) == 2: numcol = alphadict[column[0]]*26 + alphadict[column[1]] elif len(column) == 3: numcol = 26**2 + alphadict[column[1]]*26 + alphadict[column[2]] return numcol 

注:我使用Numpy很多,因为它是非常有用的创build我想写他们的格式的表,所以这是一个需要的库下面的function工作。 我知道所有这些函数都可以组合起来创build一个类,但是因为这个函数是在一个脚本中调用的,而且它们并不是真正将它作为一个类创build的重要好处,所以我没有这样做。

试试sheet.Range("C14", "D21").Value = vals 。 我不太清楚API是如何构build的,但是对我来说很有用。

(另外, tuple([(x,y) for x,y in zip('abcdefgh',xrange(8))])可以用一个生成器expression式tuple((x, y) for x, y in zip('abcdefgh', xrange(8))) ,这似乎是一个更清洁的习惯,在这个特殊情况下,列表的理解[(x, y) for x, y in zip('abcdefgh', xrange(8))]也会这样做。)