1

So recently I learned that eval() is a surprising function that can turn a string into a code command, it could be quite useful when writing a function where an argument is a string of a function's name.

But I wonder what is more pythonic way of using it. Example:

a = [1,2,3,1,2,3,4,1,2,5,6,7]
b = 'count'
target = 2
# regular way
print(a.count(target))  # >>> 3

I tried to write with f-string, which it would work:

print(eval(f'{a}' + '.' + b + f'({target})'))  # >>> 3

What amazed me is that it will work even if I don't use f-string:

print(eval('a' + '.' + b + '(target)'))  # >>> 3

This is now a little questioning to me, because without f-string, 'a' can be confusing, it won't be easy to tell if this is just a string or a variable pretending to be a string.

Not sure how you guys think about this? Which one is more pythonic to you?

Thanks!

Granny Aching
  • 1,295
  • 12
  • 37
jxie0755
  • 1,682
  • 1
  • 16
  • 35
  • Don't ever use eval is more Pythonic. – Alex Huszagh Dec 20 '17 at 03:11
  • If the strings are from trusted input, then there is no reason to use eval: just write code. If the strings are partially or fully from untrusted input, your computer can be wiped completely, even if you try to limit eval. Just don't use eval. https://nedbatchelder.com/blog/201206/eval_really_is_dangerous.html – Alex Huszagh Dec 20 '17 at 03:12
  • @AlexanderHuszagh So what is the best way to write, if a function's argument, is a function's name in string? Suppose potentially 10 functions name could be used, then I wouldn't want to write 10 'if' conditions, right? – jxie0755 Dec 20 '17 at 03:13
  • You can use a mapping for a dictionary very easily and use a lookup. `f = [map, reduce]`, `g = {i.__name__: i for i in f}`. To be fair, there are **some** uses for eval, but you have to make sure every string provided to it is completely trusted. A lot simpler is just minimizing that to almost never. – Alex Huszagh Dec 20 '17 at 03:16
  • If you want to use `eval` for cases of Python literals, you can use `literal_eval` from the `ast` module very securely. But a lot of Python programmers view `eval` as evil, for a reason. Try not to use it, whenever possible. – Alex Huszagh Dec 20 '17 at 03:17
  • @AlexanderHuszagh I understand what you mean, you are saying that if a untrusted user found out that the codes are using eval() for input, he can write a command to sabotage the whole system. I will definitely remember this and be very careful, if I have to use it. – jxie0755 Dec 20 '17 at 03:18
  • Exactly. And even if you provide a lot of safeguards to try to prevent it, they can still subvert those. If you do use eval, apps Hungarian might actually be useful to mark safe strings, so a safe string (only from the program) would be `s_data` and an unsafe string could be `us_data`. Not to be confused with system's Hungarian (which is IMO useless). Sorry, bit of a tangent, but I would never consider `eval` to be Pythonic, for the reasons mentioned above. It has uses, but they're generally done more idiomatically other ways. – Alex Huszagh Dec 20 '17 at 03:21
  • I generally consider Hungarian notation in Python to be a code smell, and eval to be one too, but because of how dangerous eval can be, I would take extraordinary precautions when using it in any meaningful code. – Alex Huszagh Dec 20 '17 at 03:25

1 Answers1

1

As people have mentioned in the comments, eval is dangerous (Alexander Huszagh) and rarely necessary.

However, you seem to have a problem with f-strings.

Before 3.6, there were two ways of constructing strings:

'a' + '.' + b + '(target)'

and

'{}.{}({})'.format(a, b, target)

With your values for a, b, and target, they both evaluate to: '[1,2,3,1,2,3,4,1,2,5,6,7].count(2)'

When you have several variables, the first option is cluttered with pluses and quotes, while the second one has the readability problem; which set of {} matches which variable (or expression).

A variant of the second way is to put names in the {} and refer to those names in format:

'{a}.{b}({target})'.format(a=a, b=b, target=target)

This makes the .format version more readable, but very verbose.

With the introduction of f-strings, we now include expressions inside the {}, similar to the named version of .format, but without the .format(...) part:

f'{a}.{b}({target})'

F-strings are a clear improvement on both + and .format version in readability and maintainability.

The way you are using f-strings, while technically correct, is not pytonic:

f'{a}' + '.' + b + f'({target})'

It seems you basically using f'{a}' as alternative to str(a).

To summarize, f'{a}' + '.' + b + f'({target})' can be simplified as f'{a}.{b}({target})'

Granny Aching
  • 1,295
  • 12
  • 37