Faller
Faller

Reputation: 1698

Safe use of eval() or alternatives - python

I'm always weary of using eval in any language but I can't think of a better way to do 2 things. From everything I've read evAl is evIl (that was bad). Any thoughts are appreciated. I have module with a dictionary that either calls a function or sets an attribute depending on how you call it from another module

module Config
some_dict = {1: ["desc 1", "callfunction1()"], 
  2: ["desc2", "setattr(object, "attribute", "the fun things"]} 

etc

module other
try:
  i = int(input())
  eval(Config.some_dict[i][1])
except ValueError:
  print("nope")

I'm just wondering if there is a safer way to do this.
Also if I'm trying to debug while the program is running:

try:
  eval(input())
except:
  pass

Is this acceptable or pythonic or is there a better way? I'm new to python (I run mostly JSL so everything is done with eval(parse()).

Upvotes: 1

Views: 2200

Answers (4)

lmjohns3
lmjohns3

Reputation: 7592

I would recommend making your dictionary values into callables of some sort. For example,

some_dict = {
    1: ["desc 1", callfunction1], 
    2: ["desc2", lambda: setattr(object, "attribute", "the fun things")]
}

Then when you want to use elements from your dictionary, just call them :

name, func = some_dict[1]
func()

The important thing is that each value in the dictionary needs to have the same calling interface (e.g., takes no arguments).

Upvotes: 3

Burhan Khalid
Burhan Khalid

Reputation: 174624

Why not simply:

some_dict[2]['function'] = setattr
some_dict[2]['options'] = [object, "attribute", "value"]

Then,

if option = 'foo':
   some_dict[2]['function'](*some_dict[2]['options'])

Upvotes: 0

user395760
user395760

Reputation:

There is a much better way: Use first-class functions.

def func2():
    setattr(object, "attribute", "the fun things")

some_dict = {
    1: ["desc 1", callfunction1],
    2: ["desc2", func2]
}

i = int(input())
Config.some_dict[i][1]()

The clutter can be reduced using lambda or partial, if you're comfortable with them. That said, your existing solution isn't as bad as many uses of eval, because it doesn't evaluate arbitrary user input but rather hard-coded strings. It's just plain unnecessary, pretty slow, and ugly.

And for debugging, there are dedicated debuggers. They work better than an ad-hoc eval-loop.

Upvotes: 6

Andrew Clark
Andrew Clark

Reputation: 208405

You can store functions as values in a dictionary, so just create a function for each operation, look it up in the dictionary, and call it.

def set_object_attr(obj=object, name='attribute', value='the fun things'):
    setattr(obj, name, value)

some_dict = {1: ["desc 1", callfunction1],
             2: ["desc 2", set_object_attr]}

# then wherever you are using this
some_dict[i][1]()

As you can see here, since callfunction1 is already a function you do not need to wrap it in another call. You need to wrap the setattr() call because you need to be able to call the function without any arguments.

I wrote the set_object_attr function above to make it clear what is going on, but in this case it would be better to use functools.partial:

import functools
some_dict = {1: ["desc 1", callfunction1],
             2: ["desc 2", functools.partial(setattr, object,
                                             'attribute', 'the fun things')]}

Upvotes: 0

Related Questions