1

I have a module in some path. I want to create a class with the same attributes as the module (so that it can be used the same way as the module) but perform some custom actions before accessing the attributes - such as reloading the module.

def get_method_with_extra(method_name, module):
    def method_with_extra(self, *args):
        imp.reload(module)
        func_to_call = getattr(module, method_name)
        func_to_call(*args)

    return method_with_extra

class tester():
    def __init__(self, module_path):
        self.module = imp.load_source('module', module_path)
        method_list = [func for func in dir(self.module) if 
callable(getattr(self.module, func))]
        for method_name in method_list:
            method_with_extra = get_method_with_extra(method_name, 
self.module)
            setattr(type(self), method_name, method_with_extra)

So if for example the module has a method named "Parse", I would like an instance of tester - tess - to have it as well, and for me to be able to call tess.parse() which should reload the inner module and then call the module's parse(). Instead, I get this error:

Traceback (most recent call last):
  File "<string>", line 1, in <module>
  File "<string>", line 4, in __init__
AttributeError: attribute '__call__' of 'namespace#' object is read-only
Nick Chapman
  • 4,402
  • 1
  • 27
  • 41
felisimo
  • 198
  • 14

1 Answers1

0

If you are allowed to change the source of the target module, and it is small enough, I think the cleanest solution here is rewriting it as a class. And then import the class, inherit from it and customize.

Also be aware that reloading modules in Python has lots of caveats, it's more for playing in Python shell than for production code.

Based on what you said in the comment, I changed your code just a little bit. I used importlib because module imp is deprecated. Note also that "monkey patching" (that's what this kind of technique is called, you want to make a runtime patch in the code) is always tightly coupled with the target code. It there are changes, your patch code can break easily.

I wrote two files module.py and test_module.py:

#-----------
# module.py

a = 100
b = 200

# returns something
def sum3(x,y):
    return x + y + 3

# does something
def print_a_b():
    global a
    print(a,b)
    a = a + 1   # test module reloads. If ok, "a" remains 100

#----------------
# test_module.py

import module
import importlib as imp

def get_method_with_extra(method_name, module):
    def method_with_extra(self, *args):
        imp.reload(module) # comment to see that "a" increases
        func_to_call = getattr(module, method_name)
        if args: # function may not have args
            return func_to_call(*args)
        else: # function may not have args
            return func_to_call()

    return method_with_extra

class tester():
    def __init__(self, module_path):
        self.module = imp.import_module('module', module_path)
        method_list = [func for func in dir(self.module)
            if callable(getattr(self.module, func))]
        for method_name in method_list:
            #print(method_name)
            method_with_extra = \
                get_method_with_extra(method_name, self.module)
            setattr(type(self), method_name, method_with_extra)

t = tester('.')
print(t.sum3(1,2))
t.print_a_b()
t.print_a_b() # checking for the reload, "a" should be 100
progmatico
  • 4,714
  • 1
  • 16
  • 27
  • I can't touch the target module, I just want to dynamically create a class that exposes the module's attributes, and upon invoking them using the class, first reloads the module and then invokes the original method. – felisimo Dec 28 '18 at 16:59
  • @felisimo Well, that happens if you have other client dependent code. See my edited answer. It works. – progmatico Dec 28 '18 at 23:15