Skip to content

Commit aa7f991

Browse files
committed
Add Daira Hopwood's python-modernize patch to avoid introducing redundant parentheses in print() calls
Commit from github.com/python-modernize/python-modernize.git: d9e671c374a5dd12779027fd60aff690027d0d41 Also re-enable some lib2to3 tests
1 parent e0564b3 commit aa7f991

File tree

5 files changed

+211
-111
lines changed

5 files changed

+211
-111
lines changed

docs/futurize.rst

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -187,6 +187,9 @@ The ``fix_print_with_import`` fixer in ``libfuturize.fixes`` changes the code to
187187
use print as a function and also adds ``from __future__ import
188188
print_function`` to the top of modules using ``print()``.
189189

190+
In addition, it avoids adding an extra set of parentheses if these already
191+
exist. So ``print(x)`` does not become ``print((x))``.
192+
190193
.. code-block:: python
191194
192195
lib2to3.fixes.fix_raise

docs/whatsnew.rst

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,12 @@ What's new in version 0.12.5
1212
- Stage 1 of ``futurize`` no longer renames ``next`` methods to ``__next__``
1313
(issue #81). It still converts ``obj.next()`` method calls to
1414
``next(obj)`` correctly.
15-
- Add :ref:`compatible-idioms` from Ed Schofield's PyConAU 2014 talk.
16-
- Add ``int.to_bytes()`` and ``int.from_bytes()`` (issue #85)
17-
- Add ``future.utils.raise_from`` as an equivalent to Py3's ``raise ... from ...`` syntax (issue #86).
15+
- Docs: Add :ref:`compatible-idioms` from Ed Schofield's PyConAU 2014 talk.
16+
- Add ``newint.to_bytes()`` and ``newint.from_bytes()`` (issue #85)
17+
- Add ``future.utils.raise_from`` as an equivalent to Py3's ``raise ... from
18+
...`` syntax (issue #86).
19+
- Prevent introduction of a second set of parentheses in ``print()`` calls in
20+
some further cases.
1821

1922

2023
.. whats-new-0.12.4:

future/tests/test_libfuturize_fixers.py

Lines changed: 107 additions & 107 deletions
Original file line numberDiff line numberDiff line change
@@ -231,113 +231,113 @@ def assert_runs_after(self, *names):
231231
#
232232
# s = "reduce()"
233233
# self.unchanged(s)
234-
#
235-
# class Test_print(FixerTestCase):
236-
# fixer = "print"
237-
#
238-
# def test_prefix_preservation(self):
239-
# b = """print 1, 1+1, 1+1+1"""
240-
# a = """print(1, 1+1, 1+1+1)"""
241-
# self.check(b, a)
242-
#
243-
# def test_idempotency(self):
244-
# s = """print()"""
245-
# self.unchanged(s)
246-
#
247-
# s = """print('')"""
248-
# self.unchanged(s)
249-
#
250-
# def test_idempotency_print_as_function(self):
251-
# self.refactor.driver.grammar = pygram.python_grammar_no_print_statement
252-
# s = """print(1, 1+1, 1+1+1)"""
253-
# self.unchanged(s)
254-
#
255-
# s = """print()"""
256-
# self.unchanged(s)
257-
#
258-
# s = """print('')"""
259-
# self.unchanged(s)
260-
#
261-
# def test_1(self):
262-
# b = """print 1, 1+1, 1+1+1"""
263-
# a = """print(1, 1+1, 1+1+1)"""
264-
# self.check(b, a)
265-
#
266-
# def test_2(self):
267-
# b = """print 1, 2"""
268-
# a = """print(1, 2)"""
269-
# self.check(b, a)
270-
#
271-
# def test_3(self):
272-
# b = """print"""
273-
# a = """print()"""
274-
# self.check(b, a)
275-
#
276-
# def test_4(self):
277-
# # from bug 3000
278-
# b = """print whatever; print"""
279-
# a = """print(whatever); print()"""
280-
# self.check(b, a)
281-
#
282-
# def test_5(self):
283-
# b = """print; print whatever;"""
284-
# a = """print(); print(whatever);"""
285-
# self.check(b, a)
286-
#
287-
# def test_tuple(self):
288-
# b = """print (a, b, c)"""
289-
# a = """print((a, b, c))"""
290-
# self.check(b, a)
291-
#
292-
# # trailing commas
293-
#
294-
# def test_trailing_comma_1(self):
295-
# b = """print 1, 2, 3,"""
296-
# a = """print(1, 2, 3, end=' ')"""
297-
# self.check(b, a)
298-
#
299-
# def test_trailing_comma_2(self):
300-
# b = """print 1, 2,"""
301-
# a = """print(1, 2, end=' ')"""
302-
# self.check(b, a)
303-
#
304-
# def test_trailing_comma_3(self):
305-
# b = """print 1,"""
306-
# a = """print(1, end=' ')"""
307-
# self.check(b, a)
308-
#
309-
# # >> stuff
310-
#
311-
# def test_vargs_without_trailing_comma(self):
312-
# b = """print >>sys.stderr, 1, 2, 3"""
313-
# a = """print(1, 2, 3, file=sys.stderr)"""
314-
# self.check(b, a)
315-
#
316-
# def test_with_trailing_comma(self):
317-
# b = """print >>sys.stderr, 1, 2,"""
318-
# a = """print(1, 2, end=' ', file=sys.stderr)"""
319-
# self.check(b, a)
320-
#
321-
# def test_no_trailing_comma(self):
322-
# b = """print >>sys.stderr, 1+1"""
323-
# a = """print(1+1, file=sys.stderr)"""
324-
# self.check(b, a)
325-
#
326-
# def test_spaces_before_file(self):
327-
# b = """print >> sys.stderr"""
328-
# a = """print(file=sys.stderr)"""
329-
# self.check(b, a)
330-
#
331-
# def test_with_future_print_function(self):
332-
# s = "from __future__ import print_function\n" \
333-
# "print('Hai!', end=' ')"
334-
# self.unchanged(s)
335-
#
336-
# b = "print 'Hello, world!'"
337-
# a = "print('Hello, world!')"
338-
# self.check(b, a)
339-
#
340-
#
234+
235+
class Test_print(FixerTestCase):
236+
fixer = "print"
237+
238+
def test_prefix_preservation(self):
239+
b = """print 1, 1+1, 1+1+1"""
240+
a = """print(1, 1+1, 1+1+1)"""
241+
self.check(b, a)
242+
243+
def test_idempotency(self):
244+
s = """print()"""
245+
self.unchanged(s)
246+
247+
s = """print('')"""
248+
self.unchanged(s)
249+
250+
def test_idempotency_print_as_function(self):
251+
self.refactor.driver.grammar = pygram.python_grammar_no_print_statement
252+
s = """print(1, 1+1, 1+1+1)"""
253+
self.unchanged(s)
254+
255+
s = """print()"""
256+
self.unchanged(s)
257+
258+
s = """print('')"""
259+
self.unchanged(s)
260+
261+
def test_1(self):
262+
b = """print 1, 1+1, 1+1+1"""
263+
a = """print(1, 1+1, 1+1+1)"""
264+
self.check(b, a)
265+
266+
def test_2(self):
267+
b = """print 1, 2"""
268+
a = """print(1, 2)"""
269+
self.check(b, a)
270+
271+
def test_3(self):
272+
b = """print"""
273+
a = """print()"""
274+
self.check(b, a)
275+
276+
def test_4(self):
277+
# from bug 3000
278+
b = """print whatever; print"""
279+
a = """print(whatever); print()"""
280+
self.check(b, a)
281+
282+
def test_5(self):
283+
b = """print; print whatever;"""
284+
a = """print(); print(whatever);"""
285+
self.check(b, a)
286+
287+
def test_tuple(self):
288+
b = """print (a, b, c)"""
289+
a = """print((a, b, c))"""
290+
self.check(b, a)
291+
292+
# trailing commas
293+
294+
def test_trailing_comma_1(self):
295+
b = """print 1, 2, 3,"""
296+
a = """print(1, 2, 3, end=' ')"""
297+
self.check(b, a)
298+
299+
def test_trailing_comma_2(self):
300+
b = """print 1, 2,"""
301+
a = """print(1, 2, end=' ')"""
302+
self.check(b, a)
303+
304+
def test_trailing_comma_3(self):
305+
b = """print 1,"""
306+
a = """print(1, end=' ')"""
307+
self.check(b, a)
308+
309+
# >> stuff
310+
311+
def test_vargs_without_trailing_comma(self):
312+
b = """print >>sys.stderr, 1, 2, 3"""
313+
a = """print(1, 2, 3, file=sys.stderr)"""
314+
self.check(b, a)
315+
316+
def test_with_trailing_comma(self):
317+
b = """print >>sys.stderr, 1, 2,"""
318+
a = """print(1, 2, end=' ', file=sys.stderr)"""
319+
self.check(b, a)
320+
321+
def test_no_trailing_comma(self):
322+
b = """print >>sys.stderr, 1+1"""
323+
a = """print(1+1, file=sys.stderr)"""
324+
self.check(b, a)
325+
326+
def test_spaces_before_file(self):
327+
b = """print >> sys.stderr"""
328+
a = """print(file=sys.stderr)"""
329+
self.check(b, a)
330+
331+
def test_with_future_print_function(self):
332+
s = "from __future__ import print_function\n" \
333+
"print('Hai!', end=' ')"
334+
self.unchanged(s)
335+
336+
b = "print 'Hello, world!'"
337+
a = "print('Hello, world!')"
338+
self.check(b, a)
339+
340+
341341
# class Test_exec(FixerTestCase):
342342
# fixer = "exec"
343343
#

libfuturize/fixes/fix_print.py

Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
# Copyright 2006 Google, Inc. All Rights Reserved.
2+
# Licensed to PSF under a Contributor Agreement.
3+
4+
"""Fixer for print.
5+
6+
Change:
7+
"print" into "print()"
8+
"print ..." into "print(...)"
9+
"print(...)" not changed
10+
"print ... ," into "print(..., end=' ')"
11+
"print >>x, ..." into "print(..., file=x)"
12+
13+
No changes are applied if print_function is imported from __future__
14+
15+
"""
16+
17+
# Local imports
18+
from lib2to3 import patcomp, pytree, fixer_base
19+
from lib2to3.pgen2 import token
20+
from lib2to3.fixer_util import Name, Call, Comma, String
21+
# from libmodernize import add_future
22+
23+
parend_expr = patcomp.compile_pattern(
24+
"""atom< '(' [arith_expr|atom|power|term|STRING|NAME] ')' >"""
25+
)
26+
27+
28+
class FixPrint(fixer_base.BaseFix):
29+
30+
BM_compatible = True
31+
32+
PATTERN = """
33+
simple_stmt< any* bare='print' any* > | print_stmt
34+
"""
35+
36+
def transform(self, node, results):
37+
assert results
38+
39+
bare_print = results.get("bare")
40+
41+
if bare_print:
42+
# Special-case print all by itself.
43+
bare_print.replace(Call(Name(u"print"), [],
44+
prefix=bare_print.prefix))
45+
# The "from __future__ import print_function"" declaration is added
46+
# by the fix_print_with_import fixer, so we skip it here.
47+
# add_future(node, u'print_function')
48+
return
49+
assert node.children[0] == Name(u"print")
50+
args = node.children[1:]
51+
if len(args) == 1 and parend_expr.match(args[0]):
52+
# We don't want to keep sticking parens around an
53+
# already-parenthesised expression.
54+
return
55+
56+
sep = end = file = None
57+
if args and args[-1] == Comma():
58+
args = args[:-1]
59+
end = " "
60+
if args and args[0] == pytree.Leaf(token.RIGHTSHIFT, u">>"):
61+
assert len(args) >= 2
62+
file = args[1].clone()
63+
args = args[3:] # Strip a possible comma after the file expression
64+
# Now synthesize a print(args, sep=..., end=..., file=...) node.
65+
l_args = [arg.clone() for arg in args]
66+
if l_args:
67+
l_args[0].prefix = u""
68+
if sep is not None or end is not None or file is not None:
69+
if sep is not None:
70+
self.add_kwarg(l_args, u"sep", String(repr(sep)))
71+
if end is not None:
72+
self.add_kwarg(l_args, u"end", String(repr(end)))
73+
if file is not None:
74+
self.add_kwarg(l_args, u"file", file)
75+
n_stmt = Call(Name(u"print"), l_args)
76+
n_stmt.prefix = node.prefix
77+
78+
# Note that there are corner cases where adding this future-import is
79+
# incorrect, for example when the file also has a 'print ()' statement
80+
# that was intended to print "()".
81+
# add_future(node, u'print_function')
82+
return n_stmt
83+
84+
def add_kwarg(self, l_nodes, s_kwd, n_expr):
85+
# XXX All this prefix-setting may lose comments (though rarely)
86+
n_expr.prefix = u""
87+
n_argument = pytree.Node(self.syms.argument,
88+
(Name(s_kwd),
89+
pytree.Leaf(token.EQUAL, u"="),
90+
n_expr))
91+
if l_nodes:
92+
l_nodes.append(Comma())
93+
n_argument.prefix = u" "
94+
l_nodes.append(n_argument)

libfuturize/fixes/fix_print_with_import.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
at the top to retain compatibility with Python 2.6+.
99
"""
1010

11-
from lib2to3.fixes.fix_print import FixPrint
11+
from libfuturize.fixes.fix_print import FixPrint
1212
from libfuturize.fixer_util import future_import
1313

1414
class FixPrintWithImport(FixPrint):

0 commit comments

Comments
 (0)