I'm working with pywin32 and win32com to manipulate COM objects from a third-party application.
One of my objects is a list, and I would like to iterate over the values in a list comprehension.
With some of the COM objects I am able to successfully do things like:
>>> [obj.Name for obj in myCom.someList if 'Test' in obj.Name]
And, as expected, I get a list of results:
['Test1', 'Test2', 'Test3']
However, on other similar list objects I try the same thing:
>>> [obj.Name for obj in myCom.anotherList]
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "c:\Python27\lib\site-packages\win32com\gen_py\<class_string>.py", line 6575, in __iter__
raise TypeError("This object does not support enumeration")
TypeError: This object does not support enumeration
So I try to add the function after defining my_iter
:
>>> myCom.anotherList.__iter__ = my_iter
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "c:\Python27\lib\site-packages\win32com\client\__init__.py", line 473, in __setattr__
raise AttributeError("'%s' object has no attribute '%s'" % (repr(self), attr))
AttributeError: '<win32com.gen_py.Thingy instance at 0xDEADBEEF>' object has no attribute '__iter__'
I tried the same thing with __getitem__
and next
and I get the same error. I did some reading and I assumed that this doesn't work because of something to do with __slots__
... Not sure.
After examining .py shown in the first stack trace, I looked at the definition of class ISomeList.__iter__
- since that worked perfectly in the first example in the post.
from win32com.client import DispatchBaseClass
LCID = 0x0
class ISomeList(DispatchBaseClass):
def __iter__(self):
"Return a Python iterator for this object"
try:
ob = self._oleobj_.InvokeTypes(-4,LCID,2,(13, 10),())
except pythoncom.error:
raise TypeError("This object does not support enumeration")
return win32com.client.util.Iterator(ob, '{<<<guid>>>}')
And compare that to class IAnotherList.__init__
:
class IAnotherList(DispatchBaseClass):
def __iter__(self):
"Return a Python iterator for this object"
try:
ob = self._oleobj_.InvokeTypes(-4,LCID,3,(13, 10),())
except pythoncom.error:
raise TypeError("This object does not support enumeration")
return win32com.client.util.Iterator(ob, None)
So, to me, it looks like assigning to ob
fails and then we raise an error.
I can modify the generated source to add the __iter__
method, but that seems like a very "works on my machine" solution. What can I do?
I'm very open to any alternative approaches, but this is what I came up with on my own.
I discovered that there is a win32com helper function called GetModuleForProgID
- as you might expect from the name, it will take the COM Program ID and return the pywin32 module that was generated to wrap that object.
What ended up working best for me was:
""" define a method to do my iteration """
def com_generic_iter(self):
current = 1 """ All the com objects I'm working with are 1-based indeces """
while current <= self.Count:
yield self.Item(current)
current += 1
""" I also wanted to support __getitem__ indexing: """
def generic_getitem(self, k):
return self.Item(k)
""" Then dynamically add the method to the application wrapper when I generate my object """
mod = win32com.client.gencache.GetModuleForProgID("MyProgram.Application")
mod.IAnotherList.__iter__ = com_generic_iter
mod.IAnotherList.__getitem__ = generic_getitem
app = win32com.client.Dispatch("MyProgram.Application")
print [x.Name for x in app.AnotherList]
"""
[<win32com.gen_py.MyProgram.IItem instance at 0x12345678>,
<win32com.gen_py.MyProgram.IItem instance at 0x87654321>,
<win32com.gen_py.MyProgram.IDifferentItem instance at 0x99999999>]
"""
print app.AnotherList[1]
"""
<win32com.gen_py.MyProgram.IItem instance at 0x12345678>
"""
You can also add __iter__
and __getitem__
with setattr
like this:
mod = win32com.client.gencache.GetModuleForProgID("MyProgram.Application")
IAnotherList = mod.IAnotherList
setattr(IAnotherList, '__getitem__', generic_getitem)
setattr(IAnotherList, '__iter__', com_generic_iter)
I used win32com documentation to find details about GetModuleForProgID
.