For some days I've been breaking my head over some issues I'm having with a Win32Com Excel object in python. What I'm trying to do is very simple; get the sheet names from a workbook and write a value to the bottom of a column. The workbook is often opened by the users and needs to remain accessible/editable, hence I decided to use Win32Com. Any other suggestions to achieve the same are very welcome as well.
The base-code is quite straightforward:
class excelCOM():
def __init__(self, file):
self.xl = win32com.client.gencache.EnsureDispatch('Excel.Application')
self.wb = self.xl.Workbooks(os.path.basename(file))
def getSheets(self):
return [s.Name for s in self.wb.Sheets]
def dataToCell(self, sheet, column, string):
sht = self.wb.Sheets(sheet)
maxLastCell = sht.Range('{}{}'.format(column, 1048576))
lastCell = maxLastCell.End(win32com.client.constants.xlUp)
lastCell.Offset(2, 1).Value = string
Issues occur when integrated with the rest of the software. I've gone through lots of documentation and trying different things but I've been unable to get a reliable result. I'll try my best to summarize what I've tried and what the observations were.
- When I create a single instance of this class and run the methods, everything works as expected.
if __name__ == '__main__':
xlWb = excelCOM(r"TestBook.xlsx")
print(xlWb.getSheets())
xlWb.dataToCell("Sheet1", 'B', 'All Good')
- The information to write to the excel file comes from a logfile that is being written to by another (external) program. My software monitors the file and writes to excel whenever there is a new line added. All of this is handled by the 'processor'. The formatting for the text that's written to excel is user defined. Those formatting settings (and others) are imported to the processor from a pickled (settings) object (allowing the user to save settings (using a GUI) and run the processor without needing the GUI). As code (extremely simplified, just to get the idea across):
class processor():
def __init__(self, settings_object):
self.com_object = excelCOM(settings_object.excel_filepath)
self.file_monitor(settings_object.input_filepath)
self.file_monitor.start()
def dispatch():
# called whenever file_monitor registers a change to the file at input_filepath
last_line = self.get_last_line(input_filepath)
sheet, column, formatted_text = self.process(last_line)
self.com_object.dataToCell(sheet, column, formatted_text)
Now, when text is supposed to be written to the excel file by the processor, I get Exception in thread Thread-1: pywintypes.com_error: (-2147221008, 'CoInitialize has not been called.', None, None) at sht = self.wb.Sheets(sheet)
When I call
CoInitialize()beforesht = self.wb.Sheets(sheet)I getException in thread Thread-1: pywintypes.com_error: (-2147417842, 'The application called an interface that was marshalled for a different thread.', None, None). CallingCoInitialize()anywhere else in the code doesn't seem to do anything.Adding the same code as in init solves the issue from point 3, but this seems very wrong to me. Mainly because I don't exactly know why it fixes the issue and what's going on in the background with com objects in windows.
def dataToCell(self, sheet, column, string):
pythoncom.CoInitialize() # point 3.
self.xl = win32com.client.gencache.EnsureDispatch('Excel.Application') # point 4.
self.wb = self.xl.Workbooks(os.path.basename(self.file)) # point 4.
sht = self.wb.Sheets(sheet)
maxLastCell = sht.Range('{}{}'.format(column, 1048576))
lastCell = maxLastCell.End(win32com.client.constants.xlUp)
lastCell.Offset(2, 1).Value = string
- Now switching to the GUI. As mentioned earlier, the settings file for the processor is created with help from a GUI. It basically gives the functionally to build the
settings_object. In the GUI I also have a 'run' button which directly callsprocessor(settings_object)in a seperate Thread. When I do this, I don't even need to add the additional lines as described in point 3 and 4. It runs perfectly with just the base-code.
I've gone through dozens of pages of documentation, StackOverflow topics, tutorials, blogs hidden in the dark corners of the web, Python Programming on Win32 but I simply can't wrap my head around what's going on. I consider myself a half-decent programmer for 'get the job done' applications but I don't have any education in computer science (or even programming in general), which is what I suspect I'm lacking at the moment.
I'm hoping someone can point me in the right direction for understanding this behavior and maybe give some advice on the proper way of implementing this functionality for my scenario.
Please let me know if you require any more information. Best regards and many thanks in advance, RiVer