2

I'm attempting to instantiate an object from a string. Specifically, I'm trying to change this:

from node.mapper import Mapper
mapper = Mapper(file)
mapper.map(src, dst)

into something like this:

with open('C:.../node/mapper.py', 'r') as f:
    mapping_script = f.read()

eval(mapping_script)
mapper = Mapper(file)
mapper.map(src, dst)

The motivation for this seemingly bizarre task is to be able to store different versions of mapping scripts in a database and then retrieve/use them as needed (with emphasis on the polymorphism of the map() method).

The above does not work. For some reason, eval() throws SyntaxError: invalid syntax. I don't understand this since it's the same file that's being imported in the first case. Is there some reason why eval() cannot be used to define classes?

I should note that I am aware of the security concerns around eval(). I would love to hear of alternative approaches if there are any. The only other thing I can think of is to fetch the script, physically save it into the node package directory, and then import it, but that seems even crazier.

Mike Müller
  • 82,630
  • 20
  • 166
  • 161
Gadzooks34
  • 1,718
  • 2
  • 20
  • 29

1 Answers1

1

You need to use exec:

exec(mapping_script)

eval() works only for expressions. exec() works for statements. A typical Python script contains statements.

For example:

code = """class Mapper: pass"""
exec(code)
mapper = Mapper()
print(mapper)

Output:

<__main__.Mapper object at 0x10ae326a0>

Make sure you either call exec() (Python 3, in Python 2 it is a statement) at the module level. When you call it in a function, you need to add globals(), for example exec(code, globals()), to make the objects available in the global scope and to the rest of the function as discussed here.

Community
  • 1
  • 1
Mike Müller
  • 82,630
  • 20
  • 166
  • 161
  • It doesn't work. Even though the Mapper class is defined in the mapping_script, the following instantiation call fails and complains that Mapper isn't defined. – Gadzooks34 Feb 05 '16 at 22:41
  • Yeah. I confirmed it via the debugger. I also threw a simple variable definition into the file and that gets defined just fine. The problem seems be related to the fact the Mapper inherits from a base class, and that class does not get imported despite the fact the there's an import statement in mapper.py. I also tried adding the base class import to the script in which exec() is running and that doesn't work either. Still trying to dig into it... – Gadzooks34 Feb 05 '16 at 22:59
  • @Gadzooks34: What Python version are you on? And is this inside a function? – user2357112 Feb 05 '16 at 23:06
  • @user2357112: I'm using 3.4. It's actually occurring within a static class method. It's bizarre. The debugger (PyCharm) shows that Mapper is successfully defined as a type after exec() but the instantiation call still fails with "'Mapper' is not defined." – Gadzooks34 Feb 05 '16 at 23:10
  • Did you try on the command line rather than in PyCharm? Maybe the IDE interferes here. – Mike Müller Feb 05 '16 at 23:16
  • @Mike: Yes, I just confirmed that the same error occurs when run from the CLI (i.e. independent of the IDE). Also, for kicks I copied in the class definition and instantiation code you used in your example above and the same thing happens (I renamed to Mapper2). The type Mapper2 shows as successfully defined after exec() but then mapper2 = Mapper2() fails. – Gadzooks34 Feb 05 '16 at 23:27
  • 1
    Is `exec` within a function? If so, try `exec(code, globals())` per [this link](http://stackoverflow.com/questions/12505047/in-python-why-doesnt-an-import-in-an-exec-in-a-function-work). – Jared Goguen Feb 05 '16 at 23:29
  • Good point. `exec` needs to be in the same scope as your instantiation. – Mike Müller Feb 05 '16 at 23:32
  • Eureka! That did it (adding globals()). To whom shall I send my first-born? – Gadzooks34 Feb 05 '16 at 23:40