Reputation: 197
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.
Upvotes: 2
Views: 3909
Reputation: 13914
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)
Upvotes: 1
Reputation: 23223
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()
Upvotes: 1