After trying to complete the Reversing parentheses challenge on Codesignal for about 2 days and coming up with over 150 lines of code that didn't work, I stumbled upon the following code using eval().
Now I understand that eval() takes a string and interprets it as if it had been entered into the console. But I really don't understand how it achieves its goal here. Can someone please go through it bit by bit and explain what is happening?
Thanks
I've tried googling, reading the docs and searching youtube to get a better understanding. But to no avail.
def reverseInParentheses(s):
return eval('"' + s.replace('(', '"+("').replace(')', '")[::-1]+"') + '"')
inputString = "(bar)"
reverseInParentheses(inputString) # output: "rab"
inputString = "foo(bar)baz"
reverseInParentheses(inputString) # output: "foorabbaz"
inputString = "foo(bar)baz(blim)"
reverseInParentheses(inputString) # output: "foorabbazmilb"
inputString = "foo(bar(baz))blim"
reverseInParentheses(inputString) # output: "foobazrabblim"
Let’s disect this one by one:
eval('"' + s.replace('(', '"+("').replace(')', '")[::-1]+"') + '"')
This is actually the same as the following (which may make it easier to understand):
code = '"' + s.replace('(', '"+("').replace(')', '")[::-1]+"') + '"'
eval(code)
Which is the same as:
innerCode = s.replace('(', '"+("').replace(')', '")[::-1]+"')
code = '"' + innerCode + '"'
eval(code)
innerCode
does simple string manipulation there:
(
by "+("
)
by ")[::-1]+"
So taking your example (bar)
as an example, this is the result: "+("bar")[::-1]+"
If you add the quotes again, you get this string:
""+("bar")[::-1]+""
= ("bar")[::-1]
= "bar"[::-1]
What [::-1]
does on a string is reverse it, essentially by iterating it from the back (that’s what the -1
does):
>>> 'foo'[::-1]
'oof'
>>> 'bar'[::-1]
'rab'
When that resulting code is then executed with eval
, you get your result.
Let’s look at another example: foo(bar)baz(blim)
. After replacing the parentheses, this is what you get:
foo"+("bar")[::-1]+"baz"+("blim")[::-1]+"
Add the quotes around and simplify it and you get this:
"foo"+("bar")[::-1]+"baz"+("blim")[::-1]+""
= "foo" + ("bar")[::-1] + "baz" + ("blim")[::-1]
= "foo" + "bar"[::-1] + "baz" + "blim"[::-1]
When you execute that, you get "foo" + "rab" + "baz" + "milb"
.
Note that while this works to solve the job, using eval
is actually a very bad idea. eval
executes any code, not just string concatenations and string reversals. So if you take the input from a source you don’t trust blindly, attackers could use this to execute bad code.
It’s a much better idea to implement this behavior without eval
at all, which isn’t that difficult since you are just manipulating a string after all.
For example, using regular expressions to quickly find the parentheses:
import re
def reverseInParentheses(s):
for m in re.findall('\((.*?)\)', s):
s = s.replace('(' + m + ')', m[::-1])
return s
>>> reverseInParentheses("(bar)")
'rab'
>>> reverseInParentheses("foo(bar)baz")
'foorabbaz'
>>> reverseInParentheses("foo(bar)baz(blim)")
'foorabbazmilb'
>>> reverseInParentheses("foo(bar(baz))blim")
'foozab(rab)blim'
Note that this does not correctly work for the final example that has nested parentheses. For cases like this, it’s much better to use a proper parser, like pyparsing
. This is described in more detail in this answer on a different question.
I would strongly advise you not to use eval
here though even if it does work for your case.