views:

225

answers:

1

Using comtypes to drive Python, it seems some magic is happening behind the scenes that is not converting tuples and lists to VARIANT types:

# 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

According to the comtypes docs:

When you pass simple sequences (lists or tuples) as VARIANT parameters, the COM server will receive a VARIANT containing a SAFEARRAY of VARIANTs with the typecode VT_ARRAY | VT_VARIANT.

This seems to be inline with what MSDN says about passing an array to a Range's Value. I also found this page showing something similar in C#. Can anybody tell me what I'm doing wrong?

EDIT

I've come up with a simpler example that performs the same way (in that, it does not work):

>>>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 :(
A: 

Try sheet.Range("C14", "D21").Value = vals. I'm not quite sure how the API is structured, but it works for me.

(Also, tuple([(x,y) for x,y in zip('abcdefgh',xrange(8))]) can be replaced with a generator expression tuple((x, y) for x, y in zip('abcdefgh', xrange(8))), which is seems a bit cleaner habit to have. In this particular case, just the list comprehension [(x, y) for x, y in zip('abcdefgh', xrange(8))] would also do.)

Mike Graham
I'm not sure how you can say "I'm not quite sure how the API is structured" and then say "it works for me" :) What works for you? When I run your example, it gives me "TypeError: __set__() takes exactly 2 arguments (3 given)" The comtypes documentation says you have to use the empty tuple notation (Value[()]) See the documentation on "Properties with optional arguments".
technomalogical
Since your example wasn't self-contained I went of to reproduce it with win32com, rather than comtypes. I was confusing comtypes and pythoncom, the latter of which is the foundation for win32com. `sheet.Range("C14", "D21").Value = vals` works in with win32com.
Mike Graham
I tried tracing this call with pdb to try to make sense of how comtypes decides how to interact with the underlying COM object, but especially since I don't know the API I don't know what I'm looking for. Even the comtypes people recommend win32com for Dispatch-based stuff like automating Excel, so you might consider using it if you can.
Mike Graham