Python: 在运行时更改方法名称

8

我有很多使用以下语法的类文件:

o = module.CreateObject()
a = o.get_Field

现在实现方式已经从'get_XXX'和'set_XXX'变为了'XXX':

o = module.CreateObject()
a = o.Field

这个实现是一个外部包,我不想改变它。是否有可能编写一个包装器,动态拦截所有对“get_XXX”的调用,并将其替换为对新名称“XXX”的调用?
o = MyRenamer(module.CreateObject())
a = o.get_Field   # works as before, o.Field is called
a = o.DoIt()      # works as before, o.DoIt is called

需要拦截所有调用,而不仅限于有限的一组字段。根据方法名决定是否修改它,并导致调用修改后名称的方法。


o.get_Field = o.Field,你可以为此创建一个新的包装类。 - theAlse
你可以这样做,但你仍然可能需要改变你对 set_xxx 函数的使用,因为 o.set_field(val) 现在将变成 o.field = val --- 也就是说,不仅名称会改变,语法也会改变。 - BrenBarn
.get_Field().set_Field()也许是方法吗?你需要调用它们来获取值,并传入一个新值来设置它们吗?例如:o.set_Field('新值') - Martijn Pieters
- Uri Cohen
[Martijn Pieters]:是的,get_Field和set_Field都是方法。 - Uri Cohen
啊,如果 get_Fieldset_Field 方法合并为一个 Field 属性,那么仅仅包装属性访问是不够的。你还需要使它们可调用。 - Blckknght
2个回答

5
如果您想继续在已切换为使用属性(其中您只需访问或分配给Field)的对象上使用get_Fieldset_Field,则可以使用包装器对象:
class NoPropertyAdaptor(object):
    def __init__(self, obj):
        self.obj = obj

    def __getattr__(self, name):
        if name.startswith("get_"):
            return lambda: getattr(self.obj, name[4:])
        elif name.startswith("set_"):
            return lambda value: setattr(self.obj, name[4:], value)
        else:
            return getattr(self.obj, name)

如果您在对象上使用额外的语法,如索引或迭代,或者需要使用 isinstance 来识别对象类型,则会出现问题。
更为复杂的解决方案是创建一个子类来执行名称重写,并强制对象使用它。这并不完全是包装,因为外部代码仍将直接处理对象(因此魔术方法和isinstance将按预期工作)。这种方法适用于大多数对象,但对于具有高级元类魔法和某些内置类型的类型可能无效:
def no_property_adaptor(obj):
    class wrapper(obj.__class__):
        def __getattr__(self, name):
            if name.startswith("get_"):
                return lambda: getattr(self, name[4:])
            elif name.startswith("set_"):
                return lambda value: setattr(self, name[4:], value)
            else:
                return super(wrapper, self).__getattr__(name)

    obj.__class__ = wrapper
    return obj

使用包装器的缺点很多,包括破坏内部使用isinstance()的API。 - Martijn Pieters
@MartijnPieters:是的,上面的简单代码只适用于对包装对象进行相当有限的工作(例如仅访问属性和方法)的情况。可以通过创建子类并将包装对象的__class___更改为它来包装更复杂的行为,但在不使用复杂行为的情况下,这可能会过度设计。 - Blckknght
1
您真的不想更改__class__,特别是在您已经可以钩入isinstance()测试时。我的观点是:包装器可能会很快变得复杂。 - Martijn Pieters
1
@MartijnPieters:我不确定在编写包装器时__instancecheck__是否有帮助,因为它需要在您要检查的类型的元类中实现(这是obj的原始类型)。您无法将其添加到包装器类型!请参阅我的更新答案,以获取创建一次性子类并黑客攻击__class__属性以使用它的实现。 - Blckknght
哎呀,你当然是正确的。今天早上我还需要我的咖啡因。 - Martijn Pieters

2
你可以对任何Python类进行“猴子补丁”(monkey patch),直接导入该类并添加属性即可:
import original_module

@property
def get_Field(self):
    return self.Field

original_module.OriginalClass.get_Field = get_Field

你需要列举出想要以这种方式访问的字段:
def addField(fieldname, class):
    @property
    def get_Field(self):
        return getattr(self, fieldname)

    setattr(original_module.OriginalClass, 'get_{}'.format(fieldname), get_Field)

for fieldname in ('Foo', 'Bar', 'Baz'):
    addField(fieldname, original_module.OriginalClass)

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接