How to fix print((double parentheses)) after 2to3 conversion?
Asked Answered
S

5

17

When migrating my project to Python 3 (2to3-3.7 -w -f print *), I observed that a lot of (but not all) print statements became print((...)), so these statements now print tuples instead of performing the expected behavior. I gather that if I'd used -p, I'd be in a better place right now because from __future__ import print_function is at the top of every affected module.

I'm thinking about trying to use sed to fix this, but before I break my teeth on that, I thought I'd see if anyone else has dealt with this before. Is there a 2to3 feature to clean this up?

I do use version control (git) and have commits immediately before and after (as well as the .bak files 2to3 creates), but I'm not sure how to isolate the changes I've made from the print situations.

Syrupy answered 7/4, 2019 at 14:8 Comment(0)
D
15

You were already printing tuples. If you were not, then you are not now either.

To illustrate, your code would have to have been using print as if it was a function:

# Python 2
print(somestring)

which becomes, after translation

# Python 3
print((somestring))

That's not a tuple, that's just a pair of parentheses. It results in the same output on either version. In fact, 2to3 is smart enough to drop the inner parentheses again; the actual output written is simply

# Python 3
print(somestring)

However, if you were using multiple arguments in Python 2:

# Python 2
print(arg1, arg2)

then you'd already be printing a tuple, because that's really:

value_to_print = (arg1, arg2)
print value_to_print

So it would only be correct to preserve that behaviour in Python 3. If you see the 2to3 tool use print((....)), then it determined that you were already printing tuples.

Demo:

$ cat testprint.py
print('not a tuple')
print('two-value', 'tuple')
$ python2.7 testprint.py
not a tuple
('two-value', 'tuple')
$ 2to3 -w -f print testprint.py
RefactoringTool: Refactored testprint.py
--- testprint.py    (original)
+++ testprint.py    (refactored)
@@ -1,2 +1,2 @@
 print('not a tuple')
-print('two-value', 'tuple')
+print(('two-value', 'tuple'))
RefactoringTool: Files that were modified:
RefactoringTool: testprint.py
$ python3.7 testprint.py
not a tuple
('two-value', 'tuple')

Note that this is different from using from __future__ import print_function in your Python 2 code, to disable the print statement and so making the code call the built-in print() function. The 2to3 tool already detects this case and will pass through print(...) function calls unchanged:

$ cat futureprint.py
from __future__ import print_function
print('not', 'a tuple')
$ python2.7 futureprint.py
not a tuple
$ 2to3 -w -f print futureprint.py
RefactoringTool: No files need to be modified.
$ python3.7 futureprint.py
not a tuple

You can force 2to3 to assume all your files use from __future__ import print_function, regardless, with the -p / --print-function command-line switch:

-p, --print-function  Modify the grammar so that print() is a function

However, any deliberate print (tuple_element1, tuple_element2, ...) print statements would then be mis-translated as function calls:

$ cat printtuple.py
print ('two-value', 'tuple')
$ python2.7 printtuple.py
('two-value', 'tuple')
$ 2to3 -w -f print -p printtuple.py
RefactoringTool: No files need to be modified.
$ python3.7 printtuple.py
two-value tuple
Dekameter answered 7/4, 2019 at 14:14 Comment(6)
Thanks. This is good to know. I made a bunch of edits to the transformed files before realizing the mistake though, so I'm looking for a way to correct the situation without reverting to the pre-migration state.Syrupy
@HaPsantran: that's where you really need to be using a version control system. Commit your working code, translate and test, commit again. Preferably on a branch if you are not certain of your tests.Dekameter
@HaPsantran: otherwise, using sed or another regex tool can easily lead to false-positives and errors. You could take a look at the existing print fixer in 2to3 itself, and base your own fixer off of that. See python3porting.com/fixers.htmlDekameter
Thanks for yet another great post. One question though: Why does 2to3 convert e.g. print('{}'.format(0)) to print(('{}'.format(0)))?Wyattwyche
@djvg: because in Python 2, print('{}'.format(0)) is really value_to_print = ('{}'.format(0)), print value_to_print. That's not an issue when there is just one value, but now compare this to print(42, 81), which prints a tuple of two values. Plus, 2to3 tries to preserve the use of parentheses as much as possible to minimize changes (in case you were using the parentheses for readability, e.g. value = (81 + 42) - 17).Dekameter
"2to3 is smart enough to drop the inner parentheses again" -- no, it isn't, if the expression has any complexity at all e.g. print("foo%s%s" % ("a", "b")) results in an unnecessary (but not harmful) double-parentheses.Castroprauxel
S
7

If your code already has print() functions you can use the -x print argument to 2to3 to skip the conversion.

Sink answered 30/7, 2019 at 22:8 Comment(0)
B
1

Try using the -p flag. See the last note here.

When the -p is passed, 2to3 treats print as a function instead of a statement. This is useful when from __future__ import print_function is being used. If this option is not given, the print fixer will surround print calls in an extra set of parentheses because it cannot differentiate between the print statement with parentheses (such as print ("a" + "b" + "c")) and a true function call.

Borne answered 7/4, 2019 at 14:11 Comment(1)
Thanks; I think I needed that before running 2to3 though. At this point, I've cleaned up everything manually.Syrupy
P
0

This is an option that can be used if you have a mix of print statements and functions in your code. 2to3 doesn't cope well with complex content in the print function so in an editor with regular expression search and replace, you can clean these up in a semi-manual way. For instance in vscode, an example of a search string would be

print\(\((.*\.format.*)\)\)\n

Replace with

print\($1\)

Adjust the content for other cases.

Pentode answered 11/2, 2022 at 6:47 Comment(0)
D
0

Managed to derive a working flow from @PapaKuma's answer when using VSCode:

  1. Stash or commit any changes so far
  2. Run 2to3 -w -n -j N <directory_or_file>, '-w' writes changes directly to source-file, '-n' ditches separate backups and '-j' for parallel execution using N processes
  3. Navigate to VSCode's search-view and click regex and case-sensitivity on
  4. Search for print\(\((.*\.*)\)\)\n
  5. Replace with print($1)\n
  6. Click replace, do it!
Dexterdexterity answered 4/10, 2022 at 12:44 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.