win32com中重写Visio Shape属性setter失败问题求助
I've run into this exact issue with win32com and Visio before—here's what's going on and how to fix it:
The Context
You're using Python's win32com library to control Visio, and you want cleaner syntax for accessing shape sheet values. Your standard approach works fine:
print(shp.CellsU('PinX').ResultStr('')) # Get value shp.CellsU('PinX').FormulaU = '1' # Set value
But when you tried adding a PinX property to the Shape class to enable shp.PinX (get) and shp.PinX = '1' (set), the getter worked but the setter threw this error:
KeyError Traceback (most recent call last) ~\AppData\Local\conda\conda\envs\YG_Package_1\lib\site-packages\win32com\client\__init__.py in __setattr__(self, attr, value) 478 try: ---> 479 args, defArgs=self._prop_map_put_[attr] 480 except KeyError: KeyError: 'PinX' During handling of the above exception, another exception occurred: AttributeError Traceback (most recent call last) <ipython-input-28-23f68b65624d> in <module>() ----> 1 shp.PinX= '1' ~\AppData\Local\conda\conda\envs\YG_Package_1\lib\site-packages\win32com\client\__init__.py in __setattr__(self, attr, value) 479 args, defArgs=self._prop_map_put_[attr] 480 except KeyError: ---> 481 raise AttributeError("'%s' object has no attribute '%s'" % (repr(self), attr)) 482 self._oleobj_.Invoke(*(args + (value,) + defArgs)) 483 AttributeError: '<win32com.gen_py.Microsoft Visio 15.0 Type Library.IVShape instance at 0x85710888>' object has no attribute 'PinX'
Why This Happens
The root issue is how win32com handles attribute assignment for COM proxy objects:
- For getters: The proxy's
__getattr__checks Python class-level properties first, then falls back to COM native properties. That's why yourPinXgetter worked. - For setters: The proxy's
__setattr__prioritizes the COM object's native property map (_prop_map_put_) over Python properties. When you try to assignshp.PinX, it looks for a native COM setter (which doesn't exist) and throws the error before ever hitting your Python property setter.
Solutions
1. Use a Wrapper Class (Recommended)
This is the most reliable approach, as it avoids messing with win32com's internal logic. We'll create a wrapper that delegates most functionality to the original shape but adds our custom properties:
class VisioShapeWrapper: def __init__(self, shape): self.shape = shape @property def PinX(self): return self.shape.CellsU('PinX').ResultStr('') @PinX.setter def PinX(self, value): self.shape.CellsU('PinX').FormulaU = value # Add more properties like PinY, Width, Height here as needed # Delegate other attributes/methods to the original shape def __getattr__(self, attr): return getattr(self.shape, attr) def __setattr__(self, attr, value): # Handle our custom properties first if attr in ['PinX', 'PinY']: # Add your custom props to this list object.__setattr__(self, attr, value) elif attr == 'shape': object.__setattr__(self, attr, value) else: # Pass through to the original shape for all other attributes setattr(self.shape, attr, value)
Usage:
# Wrap your existing shape object shp_wrapper = VisioShapeWrapper(shp) # Now use the clean syntax print(shp_wrapper.PinX) # Returns the correct value shp_wrapper.PinX = '2' # Sets the value successfully # You can still access all native shape features shp_wrapper.Name = "Updated Shape Name"
2. Monkey-Patch the Shape Class (Quick Scripts Only)
If you want to modify the original class directly (not ideal for large projects), override the __setattr__ method to check for your custom properties first:
ShapeClass = type(shp) original_setattr = ShapeClass.__setattr__ def custom_setattr(self, attr, value): # Handle our custom properties if attr == 'PinX': self.CellsU('PinX').FormulaU = value elif attr == 'PinY': self.CellsU('PinY').FormulaU = value # Add other custom properties here else: # Fall back to the original setter for native attributes original_setattr(self, attr, value) # Override the class's __setattr__ ShapeClass.__setattr__ = custom_setattr # Add the getter property as before def GetPinX(self): return self.CellsU('PinX').ResultStr('') ShapeClass.PinX = property(GetPinX)
Usage:
print(shp.PinX) # Works as before shp.PinX = '1' # Now works without errors!
内容的提问来源于stack exchange,提问作者y4cine

