1

I have a string stored in a database stands for a class instance creation for example module1.CustomHandler(filename="abc.csv", mode="rb"), where CustomHandler is a class defined in module1.

I would like to evaluate this string to create a class instance for a one time use. Right now I am using something like this

statement = r'module1.CustomHandler(filename="abc.csv", mode="rb")'  # actually read from db
exec(f'from parent.module import {statement.split(".")[0]}')
func_or_instance = eval(statement)  # this is what I need

Only knowledgable developers can insert such records into database so I am not worried about eval some unwanted codes. But I've read several posts saying eval is unsafe and there is always a better way. Is there a way I can achieve this without using eval?

Christoph Rackwitz
  • 11,317
  • 4
  • 27
  • 36
Foocli
  • 157
  • 1
  • 11
  • 1
    This is a bad idea to begin with. You're essentially storing code which needs to be evaluated as code. Storing code as data is bad, because it ties data to a specific implementation. This makes your actual code inflexible, since you can't rearrange or rename it without also needing to update everything in your database that refers to it. It would be better to store this in the database in a more neutral format, which can be parsed and translated to actual function calls in a controlled way; e.g.: `{"action": "custom", "kwargs": {"filename": "abc.csv", "mode": "rb"}}`. – deceze Jun 29 '22 at 07:00
  • @deceze I totally agree, however I am only doing changes to the code and cannot modify how it actually runs underneath. – Foocli Jun 29 '22 at 07:11
  • the `ast` module is only recommended when you can use `literal_eval` (safer because it doesn't execute code), or when you actually want to inspect the tree that you get, before you execute the code. -- eval() is **unsafe** _not_ because it might throw exceptions, but because it can execute whatever code you give it... which might be code __others__ give you, and they might have bad intentions (vulnerability) – Christoph Rackwitz Jun 29 '22 at 08:57
  • Does this answer your question? [import module from string variable](https://stackoverflow.com/questions/8718885/import-module-from-string-variable) – Christoph Rackwitz Jun 29 '22 at 08:59
  • a proper import, instead of exec() would be a lot safer. please check out the question I linked to. – Christoph Rackwitz Jun 29 '22 at 09:00
  • @ChristophRackwitz thanks for the detailed info, as I mentioned, my case is probably special because the project will be only internally used by developers who know a good deal of python. Moreover, after the statement is read from db, we do some inspections to make sure it's most likely to be what we need. The post doesn't help because the function comes with arguments. I understand that I can store them in a different way and construct the function in a safer way, this is planned for the next major version. Can you please show me an example if you think importlib can help? – Foocli Jun 29 '22 at 11:27

1 Answers1

0

You might want to take a look at the ast Python module, which stands for abstract syntax trees. It's mainly used when you need to process the grammar of the programming language, work with code in string format, and so much more functions available in the official documentation.

  • In this case eval() function looks like the best solution, clear and readable, but safe only under certain conditions. For example, if you try to evaluate a code that contains a class not implemented in the code, it will throw an exception. That's the main reason why eval is sometimes unsafe.
Cardstdani
  • 4,999
  • 3
  • 12
  • 31
  • 2
    Throwing an exception on unimplemented class is actually what's expected, targeting users are only developers so exceptions are not too ugly. I also read about `eval` is bad at performance (like run speed), should I worry about that? – Foocli Jun 29 '22 at 07:15
  • @foocli I don't exactly know its performance against modules as `ast`. But you have its official documentation attached to know more about `ast`. – Cardstdani Jun 29 '22 at 07:17
  • Roughly went through the `ast` documentation, just to make it clear, you meant that I can use `ast` to parse the statement string to get the function/class name, with arguments separately in a dict, so I can assemble the statement in a real python way. Am I understanding this correctly? – Foocli Jun 29 '22 at 07:29
  • 1
    @foocli Yes, `ast` can parse Python code – Cardstdani Jun 29 '22 at 07:38