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.

6
  • Is the module known ahead of time, or do you also need to load the module based on a string? Commented Feb 9, 2016 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. Commented Feb 9, 2016 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. Commented Feb 9, 2016 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. Commented Feb 9, 2016 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. Commented Feb 9, 2016 at 23:59

2 Answers 2

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()
Sign up to request clarification or add additional context in comments.

3 Comments

For forward compat, probably best to use importlib.import_module over __import__.
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]docs.python.org/2/library/…. 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 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).
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)

1 Comment

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.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.