You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

win32com中重写Visio Shape属性setter失败问题求助

Problem with Adding Custom Property Setter to Visio Shape via win32com in Python

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 your PinX getter 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 assign shp.PinX, it looks for a native COM setter (which doesn't exist) and throws the error before ever hitting your Python property setter.

Solutions

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.12 04:43:39