Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 658c8d8

Browse files
authored
Improve long values in dict literals (psf#3440)
1 parent a282181 commit 658c8d8

7 files changed

Lines changed: 145 additions & 10 deletions

File tree

CHANGES.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,9 @@
1919
- Fix a crash in preview style with assert + parenthesized string (#3415)
2020
- Do not put the closing quotes in a docstring on a separate line, even if the line is
2121
too long (#3430)
22+
- Long values in dict literals are now wrapped in parentheses; correspondingly
23+
unnecessary parentheses around short values in dict literals are now removed; long
24+
string lambda values are now wrapped in parentheses (#3440)
2225

2326
### Configuration
2427

src/black/linegen.py

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -179,6 +179,23 @@ def visit_stmt(
179179

180180
yield from self.visit(child)
181181

182+
def visit_dictsetmaker(self, node: Node) -> Iterator[Line]:
183+
if Preview.wrap_long_dict_values_in_parens in self.mode:
184+
for i, child in enumerate(node.children):
185+
if i == 0:
186+
continue
187+
if node.children[i - 1].type == token.COLON:
188+
if child.type == syms.atom and child.children[0].type == token.LPAR:
189+
if maybe_make_parens_invisible_in_atom(
190+
child,
191+
parent=node,
192+
remove_brackets_around_comma=False,
193+
):
194+
wrap_in_parentheses(node, child, visible=False)
195+
else:
196+
wrap_in_parentheses(node, child, visible=False)
197+
yield from self.visit_default(node)
198+
182199
def visit_funcdef(self, node: Node) -> Iterator[Line]:
183200
"""Visit function definition."""
184201
if Preview.annotation_parens not in self.mode:

src/black/mode.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -157,8 +157,11 @@ class Preview(Enum):
157157
one_element_subscript = auto()
158158
remove_block_trailing_newline = auto()
159159
remove_redundant_parens = auto()
160+
# NOTE: string_processing requires wrap_long_dict_values_in_parens
161+
# for https://github.com/psf/black/issues/3117 to be fixed.
160162
string_processing = auto()
161163
skip_magic_trailing_comma_in_subscript = auto()
164+
wrap_long_dict_values_in_parens = auto()
162165

163166

164167
class Deprecated(UserWarning):

src/black/trans.py

Lines changed: 29 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1638,6 +1638,8 @@ class StringParenWrapper(BaseStringSplitter, CustomSplitMapMixin):
16381638
* The line is a dictionary key assignment where some valid key is being
16391639
assigned the value of some string.
16401640
OR
1641+
* The line is an lambda expression and the value is a string.
1642+
OR
16411643
* The line starts with an "atom" string that prefers to be wrapped in
16421644
parens. It's preferred to be wrapped when the string is surrounded by
16431645
commas (or is the first/last child).
@@ -1683,7 +1685,7 @@ def do_splitter_match(self, line: Line) -> TMatchResult:
16831685
or self._else_match(LL)
16841686
or self._assert_match(LL)
16851687
or self._assign_match(LL)
1686-
or self._dict_match(LL)
1688+
or self._dict_or_lambda_match(LL)
16871689
or self._prefer_paren_wrap_match(LL)
16881690
)
16891691

@@ -1841,22 +1843,23 @@ def _assign_match(LL: List[Leaf]) -> Optional[int]:
18411843
return None
18421844

18431845
@staticmethod
1844-
def _dict_match(LL: List[Leaf]) -> Optional[int]:
1846+
def _dict_or_lambda_match(LL: List[Leaf]) -> Optional[int]:
18451847
"""
18461848
Returns:
18471849
string_idx such that @LL[string_idx] is equal to our target (i.e.
18481850
matched) string, if this line matches the dictionary key assignment
1849-
statement requirements listed in the 'Requirements' section of this
1850-
classes' docstring.
1851+
statement or lambda expression requirements listed in the
1852+
'Requirements' section of this classes' docstring.
18511853
OR
18521854
None, otherwise.
18531855
"""
1854-
# If this line is apart of a dictionary key assignment...
1855-
if syms.dictsetmaker in [parent_type(LL[0]), parent_type(LL[0].parent)]:
1856+
# If this line is a part of a dictionary key assignment or lambda expression...
1857+
parent_types = [parent_type(LL[0]), parent_type(LL[0].parent)]
1858+
if syms.dictsetmaker in parent_types or syms.lambdef in parent_types:
18561859
is_valid_index = is_valid_index_factory(LL)
18571860

18581861
for i, leaf in enumerate(LL):
1859-
# We MUST find a colon...
1862+
# We MUST find a colon, it can either be dict's or lambda's colon...
18601863
if leaf.type == token.COLON:
18611864
idx = i + 2 if is_empty_par(LL[i + 1]) else i + 1
18621865

@@ -1951,6 +1954,25 @@ def do_transform(self, line: Line, string_idx: int) -> Iterator[TResult[Line]]:
19511954
f" (left_leaves={left_leaves}, right_leaves={right_leaves})"
19521955
)
19531956
old_rpar_leaf = right_leaves.pop()
1957+
elif right_leaves and right_leaves[-1].type == token.RPAR:
1958+
# Special case for lambda expressions as dict's value, e.g.:
1959+
# my_dict = {
1960+
# "key": lambda x: f"formatted: {x},
1961+
# }
1962+
# After wrapping the dict's value with parentheses, the string is
1963+
# followed by a RPAR but its opening bracket is lambda's, not
1964+
# the string's:
1965+
# "key": (lambda x: f"formatted: {x}),
1966+
opening_bracket = right_leaves[-1].opening_bracket
1967+
if opening_bracket is not None and opening_bracket in left_leaves:
1968+
index = left_leaves.index(opening_bracket)
1969+
if (
1970+
index > 0
1971+
and index < len(left_leaves) - 1
1972+
and left_leaves[index - 1].type == token.COLON
1973+
and left_leaves[index + 1].value == "lambda"
1974+
):
1975+
right_leaves.pop()
19541976

19551977
append_leaves(string_line, line, right_leaves)
19561978

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
my_dict = {
2+
"something_something":
3+
r"Lorem ipsum dolor sit amet, an sed convenire eloquentiam \t"
4+
r"signiferumque, duo ea vocibus consetetur scriptorem. Facer \t"
5+
r"signiferumque, duo ea vocibus consetetur scriptorem. Facer \t",
6+
}
7+
8+
my_dict = {
9+
"a key in my dict": a_very_long_variable * and_a_very_long_function_call() / 100000.0
10+
}
11+
12+
my_dict = {
13+
"a key in my dict": a_very_long_variable * and_a_very_long_function_call() * and_another_long_func() / 100000.0
14+
}
15+
16+
my_dict = {
17+
"a key in my dict": MyClass.some_attribute.first_call().second_call().third_call(some_args="some value")
18+
}
19+
20+
21+
# output
22+
23+
24+
my_dict = {
25+
"something_something": (
26+
r"Lorem ipsum dolor sit amet, an sed convenire eloquentiam \t"
27+
r"signiferumque, duo ea vocibus consetetur scriptorem. Facer \t"
28+
r"signiferumque, duo ea vocibus consetetur scriptorem. Facer \t"
29+
),
30+
}
31+
32+
my_dict = {
33+
"a key in my dict": (
34+
a_very_long_variable * and_a_very_long_function_call() / 100000.0
35+
)
36+
}
37+
38+
my_dict = {
39+
"a key in my dict": (
40+
a_very_long_variable
41+
* and_a_very_long_function_call()
42+
* and_another_long_func()
43+
/ 100000.0
44+
)
45+
}
46+
47+
my_dict = {
48+
"a key in my dict": (
49+
MyClass.some_attribute.first_call()
50+
.second_call()
51+
.third_call(some_args="some value")
52+
)
53+
}

tests/data/preview/long_strings.py

Lines changed: 25 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -278,6 +278,15 @@ def foo():
278278
"........................................................................... \\N{LAO KO LA}"
279279
)
280280

281+
msg = lambda x: f"this is a very very very long lambda value {x} that doesn't fit on a single line"
282+
283+
dict_with_lambda_values = {
284+
"join": lambda j: (
285+
f"{j.__class__.__name__}({some_function_call(j.left)}, "
286+
f"{some_function_call(j.right)})"
287+
),
288+
}
289+
281290

282291
# output
283292

@@ -362,9 +371,8 @@ def foo():
362371
"A %s %s"
363372
% ("formatted", "string"): (
364373
"This is a really really really long string that has to go inside of a"
365-
" dictionary. It is %s bad (#%d)."
366-
)
367-
% ("soooo", 2),
374+
" dictionary. It is %s bad (#%d)." % ("soooo", 2)
375+
),
368376
}
369377

370378
D5 = { # Test for https://github.com/psf/black/issues/3261
@@ -806,3 +814,17 @@ def foo():
806814
"..........................................................................."
807815
" \\N{LAO KO LA}"
808816
)
817+
818+
msg = (
819+
lambda x: (
820+
f"this is a very very very long lambda value {x} that doesn't fit on a single"
821+
" line"
822+
)
823+
)
824+
825+
dict_with_lambda_values = {
826+
"join": lambda j: (
827+
f"{j.__class__.__name__}({some_function_call(j.left)}, "
828+
f"{some_function_call(j.right)})"
829+
),
830+
}

tests/data/preview/long_strings__regression.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -524,6 +524,13 @@ async def foo(self):
524524
},
525525
)
526526

527+
# Regression test for https://github.com/psf/black/issues/3117.
528+
some_dict = {
529+
"something_something":
530+
r"Lorem ipsum dolor sit amet, an sed convenire eloquentiam \t"
531+
r"signiferumque, duo ea vocibus consetetur scriptorem. Facer \t",
532+
}
533+
527534

528535
# output
529536

@@ -1178,3 +1185,11 @@ async def foo(self):
11781185
),
11791186
},
11801187
)
1188+
1189+
# Regression test for https://github.com/psf/black/issues/3117.
1190+
some_dict = {
1191+
"something_something": (
1192+
r"Lorem ipsum dolor sit amet, an sed convenire eloquentiam \t"
1193+
r"signiferumque, duo ea vocibus consetetur scriptorem. Facer \t"
1194+
),
1195+
}

0 commit comments

Comments
 (0)