Uri Cohen
Uri Cohen

Reputation: 3608

Python: renaming method names on-the-fly

I have many files using classes with the following syntax:

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

and now the implementation has changed from 'get_XXX' and 'set_XXX' to just 'XXX':

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

This implementation is an external package, which I don't want to change. Is it possible to write a wrapper which will on-the-fly intercept all calls to 'get_XXX' and replace then with calls to the new name '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

It needs to intercept all calls, not just to a finite-set of fields, decide based on the method name if to modify it and cause a method with a modified name to be called.

Upvotes: 8

Views: 9161

Answers (2)

Blckknght
Blckknght

Reputation: 104752

If you want to continue to use get_Field and set_Field on an object that has switched to using properties (where you simply access or assign to Field), it's possible to use an wrapper object:

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)

This will have problems if you are using extra syntax, like indexing or iteration on the object, or if you need to recognize the type of the object using isinstance.

A more sophisticated solution would be to create a subclass that does the name rewriting and force the object to use it. This isn't exactly a wrapping, since outside code will still deal with the object directly (and so magic methods and isinstance) will work as expected. This approach will work for most objects, but it might fail for types that have fancy metaclass magic going on and for some builtin types:

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

Upvotes: 5

Martijn Pieters
Martijn Pieters

Reputation: 1123260

You can 'monkey patch' any python class; import the class directly and add a property:

import original_module

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

original_module.OriginalClass.get_Field = get_Field

You'd need to enumerate what fields you wanted to access this way:

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)

Upvotes: 2

Related Questions