Skip to content

Commit e5b9d5d

Browse files
committed
Merge pull request #75 from brettcannon/simplification
Simplify various fixers
2 parents ab4b7a8 + bd835e1 commit e5b9d5d

File tree

9 files changed

+75
-244
lines changed

9 files changed

+75
-244
lines changed

libmodernize/__init__.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,3 +67,13 @@ def add_future(node, symbol):
6767
def touch_import(package, name, node):
6868
add_future(node, 'absolute_import')
6969
fixer_util.touch_import(package, name, node)
70+
71+
72+
def is_listcomp(node):
73+
return (isinstance(node, Node) and
74+
node.type == syms.atom and
75+
len(node.children) >= 2 and
76+
isinstance(node.children[0], Leaf) and
77+
node.children[0].value == '[' and
78+
isinstance(node.children[-1], Leaf) and
79+
node.children[-1].value == ']')

libmodernize/fixes/fix_filter.py

Lines changed: 9 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -2,43 +2,18 @@
22
# Licensed to PSF under a Contributor Agreement.
33
from __future__ import absolute_import
44

5-
from lib2to3 import fixer_base
6-
from lib2to3.fixer_util import Call, Name, in_special_context
7-
from libmodernize import touch_import
5+
from lib2to3.fixes import fix_filter
6+
import libmodernize
87

98

10-
class FixFilter(fixer_base.ConditionalFix):
9+
class FixFilter(fix_filter.FixFilter):
1110

12-
BM_compatible = True
13-
order = "pre"
1411
skip_on = "six.moves.filter"
1512

16-
PATTERN = """
17-
power< 'filter'
18-
trailer< '('
19-
arglist< (
20-
not(argument<any '=' any>) any ','
21-
not(argument<any '=' any>) any
22-
) >
23-
')' >
24-
>
25-
"""
26-
2713
def transform(self, node, results):
28-
if self.should_skip(node):
29-
# Make the fixer idempotent - if six.moves.filter is already imported,
30-
# skip it. should_skip() caches the state the first time we check,
31-
# so it doesn't skip after *we've* added the six.moves import.
32-
return
33-
34-
touch_import(u'six.moves', u'filter', node)
35-
if in_special_context(node):
36-
# The node is somewhere where it only needs to be an iterator,
37-
# e.g. a for loop - don't wrap in list()
38-
return
39-
40-
new = node.clone()
41-
new.prefix = ""
42-
new = Call(Name("list"), [new])
43-
new.prefix = node.prefix
44-
return new
14+
result = super(FixFilter, self).transform(node, results)
15+
if not libmodernize.is_listcomp(result):
16+
# Keep performance improvement from six.moves.filter in iterator
17+
# contexts on Python 2.7.
18+
libmodernize.touch_import(u'six.moves', u'filter', node)
19+
return result

libmodernize/fixes/fix_map.py

Lines changed: 9 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -2,43 +2,17 @@
22
# Licensed to PSF under a Contributor Agreement.
33
from __future__ import absolute_import
44

5-
from lib2to3 import fixer_base
6-
from lib2to3.fixer_util import Call, Name, in_special_context
7-
from libmodernize import touch_import
5+
from lib2to3.fixes import fix_map
6+
import libmodernize
87

8+
class FixMap(fix_map.FixMap):
99

10-
class FixMap(fixer_base.ConditionalFix):
11-
12-
BM_compatible = True
13-
order = "pre"
1410
skip_on = "six.moves.map"
1511

16-
PATTERN = """
17-
power< 'map'
18-
trailer< '('
19-
arglist< (
20-
not(argument<any '=' any>) any ','
21-
(not(argument<any '=' any>) any)+
22-
) >
23-
')' >
24-
>
25-
"""
26-
2712
def transform(self, node, results):
28-
if self.should_skip(node):
29-
# Make the fixer idempotent - if six.moves.map is already imported,
30-
# skip it. should_skip() caches the state the first time we check,
31-
# so it doesn't skip after *we've* added the six.moves import.
32-
return
33-
34-
touch_import(u'six.moves', u'map', node)
35-
if in_special_context(node):
36-
# The node is somewhere where it only needs to be an iterator,
37-
# e.g. a for loop - don't wrap in list()
38-
return
39-
40-
new = node.clone()
41-
new.prefix = ""
42-
new = Call(Name("list"), [new])
43-
new.prefix = node.prefix
44-
return new
13+
result = super(FixMap, self).transform(node, results)
14+
if not libmodernize.is_listcomp(result):
15+
# Always use the import even if no change is required so as to have
16+
# improved performance in iterator contexts even on Python 2.7.
17+
libmodernize.touch_import(u'six.moves', u'map', node)
18+
return result

libmodernize/fixes/fix_print.py

Lines changed: 6 additions & 85 deletions
Original file line numberDiff line numberDiff line change
@@ -1,92 +1,13 @@
11
# Copyright 2006 Google, Inc. All Rights Reserved.
22
# Licensed to PSF under a Contributor Agreement.
33

4-
"""Fixer for print.
4+
from lib2to3.fixes import fix_print
5+
import libmodernize
56

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)"
127

13-
No changes are applied if print_function is imported from __future__
14-
15-
"""
16-
from __future__ import absolute_import
17-
18-
# Local imports
19-
from lib2to3 import patcomp, pytree, fixer_base
20-
from lib2to3.pgen2 import token
21-
from lib2to3.fixer_util import Name, Call, Comma, String
22-
from libmodernize import add_future
23-
24-
parend_expr = patcomp.compile_pattern(
25-
"""atom< '(' [arith_expr|atom|power|term|STRING|NAME] ')' >"""
26-
)
27-
28-
29-
class FixPrint(fixer_base.BaseFix):
30-
31-
BM_compatible = True
32-
33-
PATTERN = """
34-
simple_stmt< any* bare='print' any* > | print_stmt
35-
"""
8+
class FixPrint(fix_print.FixPrint):
369

3710
def transform(self, node, results):
38-
assert results
39-
40-
bare_print = results.get("bare")
41-
42-
if bare_print:
43-
# Special-case print all by itself.
44-
bare_print.replace(Call(Name(u"print"), [],
45-
prefix=bare_print.prefix))
46-
add_future(node, u'print_function')
47-
return
48-
assert node.children[0] == Name(u"print")
49-
args = node.children[1:]
50-
if len(args) == 1 and parend_expr.match(args[0]):
51-
# We don't want to keep sticking parens around an
52-
# already-parenthesised expression.
53-
return
54-
55-
end = file = None
56-
if args and args[-1] == Comma():
57-
args = args[:-1]
58-
end = " "
59-
if args and args[0] == pytree.Leaf(token.RIGHTSHIFT, u">>"):
60-
assert len(args) >= 2
61-
file = args[1].clone()
62-
args = args[3:] # Strip a possible comma after the file expression
63-
# Now synthesize a print(args, end=..., file=...) node
64-
# (sep is not used).
65-
l_args = [arg.clone() for arg in args]
66-
if l_args:
67-
l_args[0].prefix = u""
68-
if end is not None or file is not None:
69-
if end is not None:
70-
self.add_kwarg(l_args, u"end", String(repr(end)))
71-
if file is not None:
72-
self.add_kwarg(l_args, u"file", file)
73-
n_stmt = Call(Name(u"print"), l_args)
74-
n_stmt.prefix = node.prefix
75-
76-
# Note that there are corner cases where adding this future-import is
77-
# incorrect, for example when the file also has a 'print ()' statement
78-
# that was intended to print "()".
79-
add_future(node, u'print_function')
80-
return n_stmt
81-
82-
def add_kwarg(self, l_nodes, s_kwd, n_expr):
83-
# XXX All this prefix-setting may lose comments (though rarely)
84-
n_expr.prefix = u""
85-
n_argument = pytree.Node(self.syms.argument,
86-
(Name(s_kwd),
87-
pytree.Leaf(token.EQUAL, u"="),
88-
n_expr))
89-
if l_nodes:
90-
l_nodes.append(Comma())
91-
n_argument.prefix = u" "
92-
l_nodes.append(n_argument)
11+
result = super(FixPrint, self).transform(node, results)
12+
libmodernize.add_future(node, u'print_function')
13+
return result

libmodernize/fixes/fix_zip.py

Lines changed: 8 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -2,41 +2,16 @@
22
# Licensed to PSF under a Contributor Agreement.
33
from __future__ import absolute_import
44

5-
from lib2to3 import fixer_base
6-
from lib2to3.fixer_util import Name, Call, in_special_context
7-
from libmodernize import touch_import
5+
from lib2to3.fixes import fix_zip
6+
import libmodernize
87

8+
class FixZip(fix_zip.FixZip):
99

10-
class FixZip(fixer_base.ConditionalFix):
11-
12-
BM_compatible = True
13-
order = "pre"
1410
skip_on = "six.moves.zip"
1511

16-
PATTERN = """
17-
power< 'zip'
18-
trailer< '('
19-
( not(arglist | argument<any '=' any>) any* |
20-
arglist < not(argument<any '=' any>) any* > )
21-
')' >
22-
>
23-
"""
24-
2512
def transform(self, node, results):
26-
if self.should_skip(node):
27-
# Make the fixer idempotent - if six.moves.zip is already imported,
28-
# skip it. should_skip() caches the state the first time we check,
29-
# so it doesn't skip after *we've* added the six.moves import.
30-
return
31-
32-
touch_import(u'six.moves', u'zip', node)
33-
if in_special_context(node):
34-
# The node is somewhere where it only needs to be an iterator,
35-
# e.g. a for loop - don't wrap in list()
36-
return
37-
38-
new = node.clone()
39-
new.prefix = ""
40-
new = Call(Name("list"), [new])
41-
new.prefix = node.prefix
42-
return new
13+
result = super(FixZip, self).transform(node, results)
14+
# Always use six.moves.zip so that even Python 2.7 gets performance
15+
# boost from using itertools in iterator contexts.
16+
libmodernize.touch_import(u'six.moves', u'zip', node)
17+
return result

tests/test_fix_filter.py

Lines changed: 13 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -4,53 +4,36 @@
44

55

66
FILTER_CALL = ("""\
7-
filter(None, [1])
7+
filter(func, [1])
88
""", """\
99
from __future__ import absolute_import
1010
from six.moves import filter
11-
list(filter(None, [1]))
11+
list(filter(func, [1]))
1212
""")
1313

14-
FILTER_TOO_FEW_ARGS = ("""\
15-
filter(None)
16-
""", """\
17-
filter(None)
18-
""")
19-
20-
FILTER_TOO_MANY_ARGS = ("""\
21-
filter(None, [1], [2])
22-
""", """\
23-
filter(None, [1], [2])
24-
""")
25-
26-
FILTER_KWARGS = ("""\
27-
filter(function=None, [1])
28-
""", """\
29-
filter(function=None, [1])
30-
""")
3114

3215
FILTER_ITERATOR_CONTEXT = ("""\
33-
for a in filter(None, [1]):
16+
for a in filter(func, [1]):
3417
pass
3518
""", """\
3619
from __future__ import absolute_import
3720
from six.moves import filter
38-
for a in filter(None, [1]):
21+
for a in filter(func, [1]):
3922
pass
4023
""")
4124

25+
FILTER_NONE = ("""\
26+
filter(None, x)
27+
""", """\
28+
[_f for _f in x if _f]
29+
""")
30+
4231

4332
def test_filter_call():
4433
check_on_input(*FILTER_CALL)
4534

46-
def test_filter_too_few_args():
47-
check_on_input(*FILTER_TOO_FEW_ARGS)
48-
49-
def test_filter_too_many_args():
50-
check_on_input(*FILTER_TOO_MANY_ARGS)
51-
52-
def test_filter_kwargs():
53-
check_on_input(*FILTER_KWARGS)
54-
5535
def test_filter_iterator_context():
5636
check_on_input(*FILTER_ITERATOR_CONTEXT)
37+
38+
def test_filter_None():
39+
check_on_input(*FILTER_NONE)

0 commit comments

Comments
 (0)