style guide enforcement tools like Flake8. Since ``W503`` is not PEP 8 compliant,
you should tell Flake8 to ignore these warnings.
+### Slices
+
+PEP 8 [recommends](https://www.python.org/dev/peps/pep-0008/#whitespace-in-expressions-and-statements)
+to treat ``:`` in slices as a binary operator with the lowest priority, and to
+leave an equal amount of space on either side, except if a parameter is omitted
+(e.g. ``ham[1 + 1 :]``). It also states that for extended slices, both ``:``
+operators have to have the same amount of spacing, except if a parameter is
+omitted (``ham[1 + 1 ::]``). *Black* enforces these rules consistently.
+
+This behaviour may raise ``E203 whitespace before ':'`` warnings in style guide
+enforcement tools like Flake8. Since ``E203`` is not PEP 8 compliant, you should
+tell Flake8 to ignore these warnings.
+
### Parentheses
Some parentheses are optional in the Python grammar. Any expression can
self.consumed = consumed
def trim_prefix(self, leaf: Leaf) -> None:
- leaf.prefix = leaf.prefix[self.consumed:]
+ leaf.prefix = leaf.prefix[self.consumed :]
def leaf_from_consumed(self, leaf: Leaf) -> Leaf:
"""Returns a new Leaf from the consumed part of the prefix."""
- unformatted_prefix = leaf.prefix[:self.consumed]
+ unformatted_prefix = leaf.prefix[: self.consumed]
return Leaf(token.NEWLINE, unformatted_prefix)
syms.listmaker,
syms.testlist_gexp,
}
+TEST_DESCENDANTS = {
+ syms.test,
+ syms.lambdef,
+ syms.or_test,
+ syms.and_test,
+ syms.not_test,
+ syms.comparison,
+ syms.star_expr,
+ syms.expr,
+ syms.xor_expr,
+ syms.and_expr,
+ syms.shift_expr,
+ syms.arith_expr,
+ syms.trailer,
+ syms.term,
+ syms.power,
+}
COMPREHENSION_PRIORITY = 20
COMMA_PRIORITY = 10
TERNARY_PRIORITY = 7
return False
+ def get_open_lsqb(self) -> Optional[Leaf]:
+ """Return the most recent opening square bracket (if any)."""
+ return self.bracket_match.get((self.depth - 1, token.RSQB))
+
@dataclass
class Line:
if self.leaves and not preformatted:
# Note: at this point leaf.prefix should be empty except for
# imports, for which we only preserve newlines.
- leaf.prefix += whitespace(leaf)
+ leaf.prefix += whitespace(
+ leaf, complex_subscript=self.is_complex_subscript(leaf)
+ )
if self.inside_brackets or not preformatted:
self.bracket_tracker.mark(leaf)
self.maybe_remove_trailing_comma(leaf)
else:
return False
- for leaf in self.leaves[_opening_index + 1:]:
+ for leaf in self.leaves[_opening_index + 1 :]:
if leaf is closing:
break
self.comments[i] = (comma_index - 1, comment)
self.leaves.pop()
+ def is_complex_subscript(self, leaf: Leaf) -> bool:
+ """Return True iff `leaf` is part of a slice with non-trivial exprs."""
+ open_lsqb = (
+ leaf if leaf.type == token.LSQB else self.bracket_tracker.get_open_lsqb()
+ )
+ if open_lsqb is None:
+ return False
+
+ subscript_start = open_lsqb.next_sibling
+ if (
+ isinstance(subscript_start, Node)
+ and subscript_start.type == syms.subscriptlist
+ ):
+ subscript_start = child_towards(subscript_start, leaf)
+ return subscript_start is not None and any(
+ n.type in TEST_DESCENDANTS for n in subscript_start.pre_order()
+ )
+
def __str__(self) -> str:
"""Render the line."""
if not self:
ALWAYS_NO_SPACE = CLOSING_BRACKETS | {token.COMMA, STANDALONE_COMMENT}
-def whitespace(leaf: Leaf) -> str: # noqa C901
- """Return whitespace prefix if needed for the given `leaf`."""
+def whitespace(leaf: Leaf, *, complex_subscript: bool) -> str: # noqa C901
+ """Return whitespace prefix if needed for the given `leaf`.
+
+ `complex_subscript` signals whether the given leaf is part of a subscription
+ which has non-trivial arguments, like arithmetic expressions or function calls.
+ """
NO = ""
SPACE = " "
DOUBLESPACE = " "
return DOUBLESPACE
assert p is not None, f"INTERNAL ERROR: hand-made leaf without parent: {leaf!r}"
- if t == token.COLON and p.type not in {syms.subscript, syms.subscriptlist}:
+ if (
+ t == token.COLON
+ and p.type not in {syms.subscript, syms.subscriptlist, syms.sliceop}
+ ):
return NO
prev = leaf.prev_sibling
return NO
if t == token.COLON:
- return SPACE if prevp.type == token.COMMA else NO
+ if prevp.type == token.COLON:
+ return NO
+
+ elif prevp.type != token.COMMA and not complex_subscript:
+ return NO
+
+ return SPACE
if prevp.type == token.EQUAL:
if prevp.parent:
elif prevp.type == token.COLON:
if prevp.parent and prevp.parent.type in {syms.subscript, syms.sliceop}:
- return NO
+ return SPACE if complex_subscript else NO
elif (
prevp.parent
if prev and prev.type == token.LPAR:
return NO
- elif p.type == syms.subscript:
+ elif p.type in {syms.subscript, syms.sliceop}:
# indexing
if not prev:
assert p.parent is not None, "subscripts are always parented"
return NO
- else:
+ elif not complex_subscript:
return NO
elif p.type == syms.atom:
return None
+def child_towards(ancestor: Node, descendant: LN) -> Optional[LN]:
+ """Return the child of `ancestor` that contains `descendant`."""
+ node: Optional[LN] = descendant
+ while node and node.parent != ancestor:
+ node = node.parent
+ return node
+
+
def is_split_after_delimiter(leaf: Leaf, previous: Leaf = None) -> int:
"""Return the priority of the `leaf` delimiter, given a line break after it.
try:
yield from delimiter_split(new_lines[1], py36)
+
except CannotSplit:
yield new_lines[1]
unescaped_new_quote = re.compile(rf"(([^\\]|^)(\\\\)*){new_quote}")
escaped_new_quote = re.compile(rf"([^\\]|^)\\(\\\\)*{new_quote}")
escaped_orig_quote = re.compile(rf"([^\\]|^)\\(\\\\)*{orig_quote}")
- body = leaf.value[first_quote_pos + len(orig_quote):-len(orig_quote)]
+ body = leaf.value[first_quote_pos + len(orig_quote) : -len(orig_quote)]
if "r" in prefix.casefold():
if unescaped_new_quote.search(body):
# There's at least one unescaped new_quote in this raw string
--- /dev/null
+slice[a.b : c.d]
+slice[d :: d + 1]
+slice[d + 1 :: d]
+slice[d::d]
+slice[0]
+slice[-1]
+slice[:-1]
+slice[::-1]
+slice[:c, c - 1]
+slice[c, c + 1, d::]
+slice[ham[c::d] :: 1]
+slice[ham[cheese ** 2 : -1] : 1 : 1, ham[1:2]]
+slice[:-1:]
+slice[lambda: None : lambda: None]
+slice[lambda x, y, *args, really=2, **kwargs: None :, None::]
+slice[1 or 2 : True and False]
+slice[not so_simple : 1 < val <= 10]
+slice[(1 for i in range(42)) : x]
+slice[:: [i for i in range(42)]]
+
+
+async def f():
+ slice[await x : [i async for i in arange(42)] : 42]
+
+
+# These are from PEP-8:
+ham[1:9], ham[1:9:3], ham[:9:3], ham[1::3], ham[1:9:]
+ham[lower:upper], ham[lower:upper:], ham[lower::step]
+# ham[lower+offset : upper+offset]
+ham[: upper_fn(x) : step_fn(x)], ham[:: step_fn(x)]
+ham[lower + offset : upper + offset]