2

I need to use setattr to read from a configuration file, and set an attribute of a class to a function in another module. Roughly, here's the issue.

This works:

class MyClass:
    def __init__(self):
        self.number_cruncher = anothermodule.some_function

# method works like this, of course;
foo = MyClass()
foo.number_cruncher(inputs)

Ok, this is trivial, but what if I want to read the name of some_function from a configuration file? Reading the file and using setattr is also simple:

def read_ini(target):
    # file reading/parsing stuff here, so we effectively do:
    setattr(target, 'number_cruncher', 'anothermodule.some_function')

bar = MyClass()
read_ini(bar)
bar.number_cruncher(inputs)

This gives me a 'str' object is not callable error. If I understand correctly, it is because in the first case I'm setting the attribute as a reference to the other module, but in the second case it is simply setting the attribute as a string.

Questions: 1) is my understanding of this error correct? 2) How can I use setattr to set the attribute to be a reference to the function rather than simply a string attribute?

I've looked at other questions and found similar questions, but nothing that seemed to address this exactly.

user5747140
  • 197
  • 1
  • 8
  • Is the module known ahead of time, or do you also need to load the module based on a string? – Peter DeGlopper Feb 09 '16 at 23:03
  • Are you saying that the value `anothermodule.some_function` is read from a file? If so, you are looking to use dynamic importing, and `getattr()` on the resulting module object to resolve that string into the function object. – Martijn Pieters Feb 09 '16 at 23:03
  • Otherwise, if `anothermodule.some_function` is static (known at the time you write your code), then just use `etattr(target, 'number_cruncher', anothermodule.some_function)`. That third argument doesn't have to be a string, it is the actual object you want to use as the value for the attribute you are setting. – Martijn Pieters Feb 09 '16 at 23:05
  • Edit: @MartijnPieters basically already said it... The third parameter to `setattr` is the value that you set, so if that is a string you set a string. If it's an int it will be an int etc and then ofc an object for an object. – olofom Feb 09 '16 at 23:07
  • module already known and imported. it's a library of possible functions, basically. looking at answers below but i think they've solved it. – user5747140 Feb 09 '16 at 23:59
  • If the other module is already known and imported, it's `target.number_cruncher = getattr(another_module, 'some_function')`. You only need `setattr` if the attribute name on the target is contained in a string rather than known at the time you write the code. – Peter DeGlopper Feb 10 '16 at 01:20

2 Answers2

1

Yes, your understanding is correct. When you use setattr you are setting the attribute to a string in your current example. So when you then call bar.number_cruncher(inputs) it correctly tells you that a string object is not callable. If instead you want to have bar.number_chruncher be the callable equivalent of the function anothermodule.some_function, then you can import it and set the function (not the string) as the attribute.

def read_ini(target):
    # file reading/parsing stuff here, so we effectively do:
    __import__("anothermodule.some_function") #Allows you to import based on a string parsed from your configuration file.
    setattr(target, 'number_cruncher', anothermodule.some_function)
Michael
  • 13,244
  • 23
  • 67
  • 115
  • that makes sense, but I'm having trouble figuring out how to get 'anothermodule' to refer to a py file in the same directory as the program. – user5747140 Feb 10 '16 at 00:37
1

Most trivial approach would be something like:

module_name, function_name = "module.func".rsplit('.', 1)
f = getattr(sys.modules[module_name], function_name)
f()  # f is now callable, analogous to anothermodule.some_function in your code snippets.

Obviously, a lot potential issues are not addressed. First of all, it assumes that module is already imported. To address that you may refer to Dynamic module import in Python and use return value of __import__. Don't worry, CPython will optimize it internally and same module wouldn't be interpreted more than once.

Slightly better version would be:

module_name, function_name = "module.func".rsplit('.', 1)
f = getattr(__import__(module_name), function_name)
f()
Community
  • 1
  • 1
Łukasz Rogalski
  • 22,092
  • 8
  • 59
  • 93
  • For forward compat, probably best to use [`importlib.import_module`](https://docs.python.org/2/library/importlib.html#importlib.import_module) over `__import__`. – ShadowRanger Feb 09 '16 at 23:48
  • I understand this in concept, but how do I get `importlib.import_module` to simply import from another .py file in the same directory as my program? This is the way a simple `import whatever` would work, but I can't make sense of [link]https://docs.python.org/2/library/importlib.html#importlib.import_module. I'm getting errors such as "No module named... xxx is not a module." How do I simply import from another .py file in the same directory as the program? – user5747140 Feb 10 '16 at 00:22
  • @user5747140 `import whatever` is equivalent to `whatever = importlib.import_module('whatever')`. Second argument (signature is `importlib.import_module(name, package=None)`) is optional and allows you to use relative notation in `name` argument). – Łukasz Rogalski Feb 10 '16 at 08:15